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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ n0brains = n0brains_cli:main
@@ -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()