n0brains-cli 1.0.0__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.
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: n0brains-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Token-efficient CLI for the n0brains crypto intelligence API — built for trading agents.
|
|
5
|
+
Author: n0brains
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://n0brains.com
|
|
8
|
+
Project-URL: API, https://api.n0brains.com
|
|
9
|
+
Keywords: crypto,trading,signals,agent,intelligence,mcp
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Provides-Extra: stream
|
|
13
|
+
Requires-Dist: websockets>=12.0; extra == "stream"
|
|
14
|
+
|
|
15
|
+
# n0brains CLI
|
|
16
|
+
|
|
17
|
+
Token-efficient command-line access to the [n0brains](https://n0brains.com) crypto
|
|
18
|
+
intelligence API — built for trading agents, scripts, and humans. Every command
|
|
19
|
+
prints raw JSON to stdout, so it composes with `jq`, pipes into an LLM, or feeds a
|
|
20
|
+
bot directly. Zero third-party dependencies (Python 3.8+ stdlib only); the optional
|
|
21
|
+
`stream` command needs `websockets`.
|
|
22
|
+
|
|
23
|
+
## Why a CLI (vs the MCP server)
|
|
24
|
+
|
|
25
|
+
n0brains also ships an [MCP server](https://api.n0brains.com/mcp) for Claude /
|
|
26
|
+
Cursor / Cline. The CLI is the better fit when you're driving a **bot or shell
|
|
27
|
+
pipeline**: plain JSON with no protocol envelope is more token-efficient, it
|
|
28
|
+
composes with Unix tools, and it runs anywhere `curl` does. Same data, both
|
|
29
|
+
surfaces.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install n0brains-cli # once published
|
|
35
|
+
# or run the single file directly:
|
|
36
|
+
python3 n0brains_cli.py --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Auth
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export N0BRAINS_API_KEY=intel_sk_... # get one at https://n0brains.com
|
|
43
|
+
# or pass --api-key on any command. Public endpoints (proof) work without a key.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
| Command | What it returns |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `signals [--proven-only] [--asset BTC] [--type hack] [--limit N]` | Active classified signals |
|
|
51
|
+
| `signal <id>` | One signal with full context |
|
|
52
|
+
| `similar <query>` | Semantic similarity search (pro) |
|
|
53
|
+
| `levels <coin>` | Support/resistance levels |
|
|
54
|
+
| `liquidation-map <coin>` | Estimated liquidation-cluster zones |
|
|
55
|
+
| `correlation [coin] [--vs BTC] [--coins BTC,ETH,SOL]` | Correlation matrix or one coin's corr+beta |
|
|
56
|
+
| `rotation` | Altseason/rotation read (into_alts / into_btc / neutral) |
|
|
57
|
+
| `macro` | Daily BTC/ETH macro bias |
|
|
58
|
+
| `narrative <coin>` | Narrative context |
|
|
59
|
+
| `trust <coin>` | Trust/rug score |
|
|
60
|
+
| `manipulation <coin>` | Manipulation score |
|
|
61
|
+
| `proof [--equity]` | Live forward-return proof per signal type (+ equity curve) |
|
|
62
|
+
| `performance [--days 30] [--tier N]` | Backtest stats |
|
|
63
|
+
| `stream` | Live signal stream over WebSocket (pro) |
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Only proven-edge signals, as JSON
|
|
69
|
+
n0brains signals --proven-only --limit 10
|
|
70
|
+
|
|
71
|
+
# Is it altseason? one-line answer
|
|
72
|
+
n0brains rotation | jq -r .regime
|
|
73
|
+
|
|
74
|
+
# Where are BTC liquidation magnets?
|
|
75
|
+
n0brains liquidation-map BTC | jq '.densest_long_liq_zone, .densest_short_liq_zone'
|
|
76
|
+
|
|
77
|
+
# Feed proven-edge signals into a bot loop
|
|
78
|
+
while n0brains signals --tradeable-only --limit 5 | jq -c '.signals[]'; do sleep 60; done
|
|
79
|
+
|
|
80
|
+
# Live stream (needs: pip install websockets)
|
|
81
|
+
N0BRAINS_API_KEY=... n0brains stream | jq -c 'select(.conviction=="strong")'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Exit code is `2` on an API error (so scripts can detect failures), `0` on success.
|
|
85
|
+
|
|
86
|
+
## For agent frameworks
|
|
87
|
+
|
|
88
|
+
See [`SKILL.md`](SKILL.md) for a capability description you can drop into an agent
|
|
89
|
+
framework (the same tools are also exposed via MCP and REST).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
n0brains_cli.py,sha256=8kvlU5PMLo06vZu9fCSwNv61z0dWj-fNGk5l9B4DVAA,8122
|
|
2
|
+
n0brains_cli-1.0.0.dist-info/METADATA,sha256=VtzI4G_ZcFh2vmvJkYit-O8YC4cHvCCp9UJFvW3HtrI,3248
|
|
3
|
+
n0brains_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
4
|
+
n0brains_cli-1.0.0.dist-info/entry_points.txt,sha256=EG6sSNTexKofPdgk_ZAIFKEskkvELmVuug-A0Nuq3BY,47
|
|
5
|
+
n0brains_cli-1.0.0.dist-info/top_level.txt,sha256=KP4ASdC3YmkI3H6M2ftoIQvQ7Qrpo6DnP-319S7iXXM,13
|
|
6
|
+
n0brains_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
n0brains_cli
|
n0brains_cli.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""n0brains CLI — token-efficient command-line access to the n0brains crypto
|
|
3
|
+
intelligence API for trading agents, scripts, and humans.
|
|
4
|
+
|
|
5
|
+
Every command prints raw JSON to stdout (pipe into jq, an LLM, or a bot). Zero
|
|
6
|
+
third-party dependencies — Python 3.8+ stdlib only. Auth via the N0BRAINS_API_KEY
|
|
7
|
+
env var or --api-key. Base URL overridable via N0BRAINS_BASE_URL (default the
|
|
8
|
+
public API).
|
|
9
|
+
|
|
10
|
+
Why a CLI when there's an MCP server: for bots and shell pipelines a CLI is more
|
|
11
|
+
token-efficient than MCP (plain JSON, no protocol envelope, composes with Unix
|
|
12
|
+
tools) and works anywhere curl does. Same data as the REST API + MCP tools.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
N0BRAINS_API_KEY=intel_sk_... n0brains signals --proven-only --limit 10
|
|
16
|
+
n0brains rotation | jq .regime
|
|
17
|
+
n0brains liquidation-map BTC
|
|
18
|
+
n0brains proof
|
|
19
|
+
n0brains stream # live signal stream (WebSocket, pro tier)
|
|
20
|
+
"""
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import urllib.parse
|
|
26
|
+
import urllib.request
|
|
27
|
+
import urllib.error
|
|
28
|
+
|
|
29
|
+
DEFAULT_BASE = os.getenv("N0BRAINS_BASE_URL", "https://api.n0brains.com")
|
|
30
|
+
__version__ = "1.0.0"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _key(args) -> str:
|
|
34
|
+
k = args.api_key or os.getenv("N0BRAINS_API_KEY", "")
|
|
35
|
+
return k
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get(path: str, params: dict, args) -> dict:
|
|
39
|
+
url = args.base.rstrip("/") + path
|
|
40
|
+
q = {k: v for k, v in (params or {}).items() if v is not None}
|
|
41
|
+
if q:
|
|
42
|
+
url += "?" + urllib.parse.urlencode(q)
|
|
43
|
+
req = urllib.request.Request(url, method="GET")
|
|
44
|
+
key = _key(args)
|
|
45
|
+
if key:
|
|
46
|
+
req.add_header("X-API-Key", key)
|
|
47
|
+
req.add_header("User-Agent", f"n0brains-cli/{__version__}")
|
|
48
|
+
try:
|
|
49
|
+
with urllib.request.urlopen(req, timeout=args.timeout) as resp:
|
|
50
|
+
return json.loads(resp.read().decode())
|
|
51
|
+
except urllib.error.HTTPError as e:
|
|
52
|
+
body = e.read().decode(errors="replace")
|
|
53
|
+
try:
|
|
54
|
+
detail = json.loads(body)
|
|
55
|
+
except Exception:
|
|
56
|
+
detail = {"detail": body[:300]}
|
|
57
|
+
return {"error": True, "status": e.code, **detail}
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return {"error": True, "detail": str(e)}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _out(obj):
|
|
63
|
+
json.dump(obj, sys.stdout, indent=2, default=str)
|
|
64
|
+
sys.stdout.write("\n")
|
|
65
|
+
# non-zero exit on API error so scripts/bots can detect failure
|
|
66
|
+
if isinstance(obj, dict) and obj.get("error"):
|
|
67
|
+
sys.exit(2)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ── command handlers ───────────────────────────────────────────────────────────
|
|
71
|
+
def cmd_signals(a):
|
|
72
|
+
_out(_get("/signals", {
|
|
73
|
+
"asset": a.asset, "signal_type": a.type, "direction": a.direction,
|
|
74
|
+
"urgency": a.urgency, "limit": a.limit, "min_confidence": a.min_confidence,
|
|
75
|
+
"min_score": a.min_score, "band": a.band,
|
|
76
|
+
"proven_only": "true" if a.proven_only else None,
|
|
77
|
+
"tradeable_only": "true" if a.tradeable_only else None,
|
|
78
|
+
"include_noise": "true" if a.include_noise else None,
|
|
79
|
+
}, a))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cmd_signal(a):
|
|
83
|
+
_out(_get(f"/signals/{a.id}", {}, a))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def cmd_similar(a):
|
|
87
|
+
_out(_get("/signals/similar", {"query": a.query, "asset": a.asset, "limit": a.limit}, a))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cmd_levels(a):
|
|
91
|
+
_out(_get(f"/levels/{a.coin}", {}, a))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def cmd_liqmap(a):
|
|
95
|
+
_out(_get(f"/liquidation-map/{a.coin}", {}, a))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def cmd_correlation(a):
|
|
99
|
+
if a.coin:
|
|
100
|
+
_out(_get(f"/correlation/{a.coin}", {}, a))
|
|
101
|
+
else:
|
|
102
|
+
_out(_get("/correlation", {"vs": a.vs, "coins": a.coins}, a))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_rotation(a):
|
|
106
|
+
_out(_get("/rotation", {}, a))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def cmd_macro(a):
|
|
110
|
+
_out(_get("/macro", {}, a))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def cmd_narrative(a):
|
|
114
|
+
_out(_get(f"/narrative/{a.coin}", {}, a))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def cmd_trust(a):
|
|
118
|
+
_out(_get(f"/trust/{a.coin}", {}, a))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def cmd_manipulation(a):
|
|
122
|
+
_out(_get(f"/manipulation/{a.coin}", {}, a))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def cmd_proof(a):
|
|
126
|
+
_out(_get("/proof/equity" if a.equity else "/proof", {}, a))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def cmd_performance(a):
|
|
130
|
+
_out(_get("/performance", {"days": a.days, "tier": a.tier}, a))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def cmd_stream(a):
|
|
134
|
+
# Lazy import — only needed for streaming, keeps the base CLI dep-free.
|
|
135
|
+
try:
|
|
136
|
+
from websockets.sync.client import connect # type: ignore
|
|
137
|
+
except Exception:
|
|
138
|
+
sys.stderr.write(
|
|
139
|
+
"stream needs the 'websockets' package: pip install websockets\n"
|
|
140
|
+
"Or poll instead: n0brains signals --limit 50\n")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
key = _key(a)
|
|
143
|
+
base = a.base.replace("https://", "wss://").replace("http://", "ws://").rstrip("/")
|
|
144
|
+
url = f"{base}/stream?api_key={urllib.parse.quote(key)}"
|
|
145
|
+
try:
|
|
146
|
+
with connect(url) as ws:
|
|
147
|
+
for msg in ws:
|
|
148
|
+
try:
|
|
149
|
+
obj = json.loads(msg)
|
|
150
|
+
except Exception:
|
|
151
|
+
continue
|
|
152
|
+
if obj.get("type") in ("ping", "pong"):
|
|
153
|
+
continue
|
|
154
|
+
json.dump(obj, sys.stdout)
|
|
155
|
+
sys.stdout.write("\n")
|
|
156
|
+
sys.stdout.flush()
|
|
157
|
+
except KeyboardInterrupt:
|
|
158
|
+
pass
|
|
159
|
+
except Exception as e:
|
|
160
|
+
sys.stderr.write(f"stream error: {e}\n")
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
165
|
+
p = argparse.ArgumentParser(prog="n0brains", description="n0brains crypto intelligence CLI")
|
|
166
|
+
p.add_argument("--api-key", help="API key (or set N0BRAINS_API_KEY)")
|
|
167
|
+
p.add_argument("--base", default=DEFAULT_BASE, help="API base URL")
|
|
168
|
+
p.add_argument("--timeout", type=float, default=20.0)
|
|
169
|
+
p.add_argument("--version", action="version", version=f"n0brains-cli {__version__}")
|
|
170
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
171
|
+
|
|
172
|
+
s = sub.add_parser("signals", help="List active signals")
|
|
173
|
+
s.add_argument("--asset"); s.add_argument("--type"); s.add_argument("--direction")
|
|
174
|
+
s.add_argument("--urgency"); s.add_argument("--limit", type=int, default=50)
|
|
175
|
+
s.add_argument("--min-confidence", type=float); s.add_argument("--min-score", type=float)
|
|
176
|
+
s.add_argument("--band"); s.add_argument("--proven-only", action="store_true")
|
|
177
|
+
s.add_argument("--tradeable-only", action="store_true")
|
|
178
|
+
s.add_argument("--include-noise", action="store_true")
|
|
179
|
+
s.set_defaults(fn=cmd_signals)
|
|
180
|
+
|
|
181
|
+
g = sub.add_parser("signal", help="Get one signal by id"); g.add_argument("id"); g.set_defaults(fn=cmd_signal)
|
|
182
|
+
|
|
183
|
+
sm = sub.add_parser("similar", help="Semantic similarity search (pro)")
|
|
184
|
+
sm.add_argument("query", nargs="?"); sm.add_argument("--asset"); sm.add_argument("--limit", type=int, default=10)
|
|
185
|
+
sm.set_defaults(fn=cmd_similar)
|
|
186
|
+
|
|
187
|
+
lv = sub.add_parser("levels", help="Support/resistance for a coin"); lv.add_argument("coin"); lv.set_defaults(fn=cmd_levels)
|
|
188
|
+
lm = sub.add_parser("liquidation-map", help="Estimated liquidation zones"); lm.add_argument("coin"); lm.set_defaults(fn=cmd_liqmap)
|
|
189
|
+
|
|
190
|
+
c = sub.add_parser("correlation", help="Correlation matrix or one coin's corr/beta")
|
|
191
|
+
c.add_argument("coin", nargs="?"); c.add_argument("--vs"); c.add_argument("--coins")
|
|
192
|
+
c.set_defaults(fn=cmd_correlation)
|
|
193
|
+
|
|
194
|
+
sub.add_parser("rotation", help="Altseason/rotation read").set_defaults(fn=cmd_rotation)
|
|
195
|
+
sub.add_parser("macro", help="Daily BTC/ETH macro bias").set_defaults(fn=cmd_macro)
|
|
196
|
+
|
|
197
|
+
nv = sub.add_parser("narrative", help="Narrative context for a coin"); nv.add_argument("coin"); nv.set_defaults(fn=cmd_narrative)
|
|
198
|
+
tr = sub.add_parser("trust", help="Trust/rug score for a coin"); tr.add_argument("coin"); tr.set_defaults(fn=cmd_trust)
|
|
199
|
+
mn = sub.add_parser("manipulation", help="Manipulation score for a coin"); mn.add_argument("coin"); mn.set_defaults(fn=cmd_manipulation)
|
|
200
|
+
|
|
201
|
+
pr = sub.add_parser("proof", help="Forward-return proof (--equity for the curve)")
|
|
202
|
+
pr.add_argument("--equity", action="store_true"); pr.set_defaults(fn=cmd_proof)
|
|
203
|
+
|
|
204
|
+
pf = sub.add_parser("performance", help="Backtest stats")
|
|
205
|
+
pf.add_argument("--days", type=int, default=30); pf.add_argument("--tier", type=int)
|
|
206
|
+
pf.set_defaults(fn=cmd_performance)
|
|
207
|
+
|
|
208
|
+
sub.add_parser("stream", help="Live signal stream over WebSocket (pro)").set_defaults(fn=cmd_stream)
|
|
209
|
+
return p
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main(argv=None):
|
|
213
|
+
args = build_parser().parse_args(argv)
|
|
214
|
+
args.fn(args)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|