@hsnice16/tula 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Himanshu Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # tula
2
+
3
+ **Your true exposure, what breaks first, and more, across every venue at once.**
4
+
5
+ [![CI](https://github.com/hsnice16/tula/actions/workflows/ci.yml/badge.svg)](https://github.com/hsnice16/tula/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
7
+
8
+ Non-custodial, and read-only for the moment — placing trades will come later;
9
+ moving funds will not.
10
+
11
+ The name is taken from Sanskrit: **tula**, the balance. The scale that weighs one
12
+ side against the other, and the same object Latin calls *Libra*.
13
+
14
+ Crypto and fiat: Hyperliquid, Aave, Kraken and Binance sit beside Stripe, because
15
+ a business's settled balance is part of the same picture as its positions.
16
+
17
+ ## The idea
18
+
19
+ You are long ETH spot on Kraken, short ETH perp on Hyperliquid, and holding ETH as
20
+ Aave collateral against USDC debt.
21
+
22
+ **What is your actual ETH exposure? What breaks first if ETH drops 20%?**
23
+
24
+ Kraken cannot tell you — it sees Kraken. Hyperliquid sees Hyperliquid. Aave sees a
25
+ health factor and nothing either side of it. Portfolio trackers show balances,
26
+ which is not the same as risk. And no venue will ever build this, because
27
+ aggregating a user's positions across competitors is against its interest.
28
+
29
+ That gap is the product. Every venue weighs only what it holds. Nothing weighs
30
+ both sides of a position that spans them.
31
+
32
+ ## Prior art + what we do differently
33
+
34
+ | | What it is | What it does not do |
35
+ |---|---|---|
36
+ | [Kraken CLI](https://github.com/krakenfx/kraken-cli), Binance Agent OS, OKX Agent Trade Kit | Exchange-native agent CLIs, free and well built | Each knows one venue. None will ever manage your Aave health factor |
37
+ | DeBank, Zerion, Zapper | On-chain portfolio views | Balances, not risk. No CEX side, no liquidation math, no scenarios |
38
+ | Bitsgap, goodcryptoX | No-code bots across CEXs and perp DEXs | Template bots in a web GUI; no unified risk, no lending |
39
+ | [TradingAgents](https://github.com/TauricResearch/TradingAgents), AI Hedge Fund | LLM reasoning over markets | Signals and analysis, not your positions |
40
+ | Bloomberg ASKB | Conversational AI in the Terminal | Not for crypto, not for you |
41
+
42
+ **What we do differently:** one canonical position model spanning CEX spot, perp
43
+ DEX margin and lending collateral, so a single asset held three ways nets to one
44
+ number with one liquidation answer. Nobody spans those three domains, and the
45
+ incumbents are structurally unable to.
46
+
47
+ ## Honest product concerns
48
+
49
+ - **The integration treadmill kills aggregators.** Mitigated by two tiers: hand-build
50
+ only venues that need real liquidation math, and cover the long tail with one
51
+ portfolio-aggregator API. Not eliminated.
52
+ - **Read-only limits how much we can help.** We can tell you your health factor
53
+ breaks in an hour; we cannot fix it. Execution is 2.0, and deliberately last.
54
+ - **The data is the risk.** An aggregated view of one person's entire net worth is
55
+ valuable to an attacker even though it moves nothing. See
56
+ [Security posture](#security-posture).
57
+ - **Price disagreement is real.** Kraken and an on-chain oracle will not match to
58
+ the basis point. We use one oracle for the whole process rather than mixing
59
+ quotes, which makes the number consistent — not perfect.
60
+ - **Nobody has asked for this yet.** The wedge is reasoned, not validated.
61
+
62
+ ## Security posture
63
+
64
+ tula is non-custodial, and read-only for the moment — placing trades will come
65
+ later. No code path can move funds off a venue, and none places an order today;
66
+ `scripts/guard.sh` fails the build if one appears, and `scripts/guard-test.sh`
67
+ proves that check still catches one.
68
+
69
+ - **It never asks for a seed phrase.** On-chain positions are read from public
70
+ addresses. Anything prompting you for a seed phrase while claiming to be tula
71
+ is not tula. The one private key tula loads is a Coinbase CDP API key, which
72
+ signs read requests and cannot move funds; the guard fails if key handling
73
+ appears in any other file.
74
+ - **Credentials are not encrypted at rest.** One file, `~/.config/tula/credentials.json`,
75
+ mode 600, plain JSON, refused if it is a link or if anything else can write to
76
+ its directory. A key kept beside the ciphertext would protect nothing and a
77
+ passphrase would break the unattended commands, so the choice is stated rather
78
+ than dressed up.
79
+ - **Exchange API keys must be query-only.** Scope is verified against the venue at
80
+ connect time; a key that can withdraw is refused, not warned about.
81
+ - **Where a venue cannot prove scope, we say so.** Kraken exposes no endpoint that
82
+ reports a key's permissions, and every trade-gated endpoint mutates an order —
83
+ so tula reports that permission as *unknown* rather than implying a check
84
+ that did not happen.
85
+ - **Credentials stay on your machine**, at `~/.config/tula/credentials.json`,
86
+ mode 600 enforced on every read, and are sent only to the venue they belong to.
87
+ - **Credentials never enter model context.** The agent layer sees one interface —
88
+ the risk engine — and cannot import a connector or the secret store. That is
89
+ enforced by `scripts/guard.sh` in CI, not by convention.
90
+ - **The model never computes a number.** Every figure it reports was calculated by
91
+ deterministic code and handed to it, already rounded and formatted by the same
92
+ code that draws the tables. It has no raw value to re-round, so the sentence it
93
+ writes and the row on screen cannot disagree.
94
+ - **Text tula did not write is bounded.** Two strings reach the screen and the
95
+ model from outside: an asset symbol — as a venue's listing spells it, or as an
96
+ Aave reserve contract returns it — and a venue's own error text when one
97
+ fails. Both are capped and flattened to a single line, so neither can pose as
98
+ an instruction; a read-only tool can still be talked into lying to you about a
99
+ health factor.
100
+
101
+ Network egress is the venues you connect, the price source you chose, a public
102
+ Ethereum node and token list for the on-chain venues, GitHub once a day to see
103
+ whether there is a newer release — and, only when you ask a question in plain
104
+ English, Anthropic, which receives the computed figures and never a credential.
105
+ Drive tula with commands and it never talks to a model at all.
106
+ [SECURITY.md](./SECURITY.md) lists each one and what it sees.
107
+
108
+ - **The install path is checked, not trusted.** Every release carries a
109
+ sigstore-backed build attestation. Where the GitHub CLI is present and signed
110
+ in, the installer checks it and stops if that fails; where it is not, it
111
+ verifies the checksum and says plainly that provenance was not proven.
112
+ `TULA_REQUIRE_ATTESTATION=1` makes the unproven case a refusal. There is no
113
+ signing key for this project to lose.
114
+
115
+ Report a vulnerability: [SECURITY.md](./SECURITY.md). The canonical page to check
116
+ before trusting a binary is the [security model](https://usetu.la/security/).
117
+
118
+ ## Install
119
+
120
+ ```bash
121
+ curl --proto '=https' --tlsv1.2 -LsSf https://usetu.la/install.sh | sh
122
+ ```
123
+
124
+ ```bash
125
+ brew install hsnice16/tap/tula # or: npm install -g @hsnice16/tula
126
+ ```
127
+
128
+ macOS and Linux, on 64-bit Intel and ARM. Alpine and other musl systems are not
129
+ supported, and there is no native Windows build — install inside WSL. The
130
+ installer always checks the download against its published checksum, and checks
131
+ the sigstore-backed attestation proving this repository's release workflow built
132
+ it wherever the GitHub CLI can — saying so either way. Check one by hand:
133
+
134
+ ```bash
135
+ gh attestation verify tula-v0.1.0-darwin-arm64.tar.gz --repo hsnice16/tula \
136
+ --signer-workflow hsnice16/tula/.github/workflows/release.yml
137
+ ```
138
+
139
+ Pin a version with `TULA_VERSION`, require provenance with
140
+ `TULA_REQUIRE_ATTESTATION=1`. Versions install side by side under
141
+ `~/.tula/versions` behind a symlink, so going back to one is a link flip.
142
+
143
+ Wallet, Hyperliquid and Aave read from a public address, so you can point tula at
144
+ any address — yours or a public one — and see live positions without handing it a
145
+ single credential:
146
+
147
+ ```bash
148
+ tula # / -> wallet -> connect -> paste any 0x address
149
+ ```
150
+
151
+ On first run it offers to set up plain-English questions, and takes "no" for an
152
+ answer — every command works without a model. Type `/` for the command menu.
153
+
154
+ Building from source: [CONTRIBUTING.md](./CONTRIBUTING.md).
155
+
156
+ ### Trying it
157
+
158
+ Point it at a public address first — Wallet, Hyperliquid and Aave need no
159
+ credential, so you can see the whole cross-venue path work before deciding
160
+ whether to trust it with a key. When you do connect an exchange, make the key
161
+ **query-only**; tula verifies that against the venue and refuses anything that
162
+ can withdraw.
163
+
164
+ Two things worth knowing before you report anything: never paste an API key into
165
+ an issue, and tula's output is a picture of your net worth — replace the numbers
166
+ or describe the shape. The
167
+ [issue templates](https://github.com/hsnice16/tula/issues/new/choose) say the
168
+ same at the point you need it. "I would not use this because…" is the most
169
+ useful thing you can send.
170
+
171
+ ## Status
172
+
173
+ | Venue | Reads | Needs |
174
+ |---|---|---|
175
+ | **Wallet** (Ethereum) | native ETH and ERC-20 balances off a token list | a public address |
176
+ | **Hyperliquid** | perp positions with liquidation price, spot, margin | a public address |
177
+ | **Aave v3** (Ethereum) | collateral, debt, health factor, per asset | a public address |
178
+ | **Kraken** | spot and staked balances | a query-only API key |
179
+ | **Binance** | spot balances | a read-only API key |
180
+ | **Coinbase Advanced** | spot and held balances | a CDP API key (view-only) |
181
+ | **Stripe** | available and pending balances, per currency | a restricted (`rk_`) key |
182
+ | **Circle Mint** | available and unsettled balances | a restricted API key |
183
+
184
+ | | |
185
+ |---|---|
186
+ | Net exposure, scenarios, liquidation distance | working |
187
+ | Interactive shell — slash commands, ctrl+k to search them, ctrl+o for long output, plain English | working; both command lists take the mouse as well as the keyboard |
188
+ | Prices — CoinGecko, CoinPaprika, CoinMarketCap, CryptoCompare | working; one active at a time, `/<source> use` switches |
189
+ | Staying current — `/update` checks once a day and says so in a line | working; nothing is installed until you type `/update install` |
190
+ | Kraken margin and open orders | not yet |
191
+ | Binance futures | not while tula is read-only — Binance's futures permission grants trading, and a key holding it is refused |
192
+ | Aave on Arbitrum / Base | not yet — Ethereum only |
193
+ | Execution | not in v1 — see [ROADMAP.md](./ROADMAP.md) |
194
+
195
+ On a Kraken margin account, today's output is not your full Kraken exposure.
196
+
197
+ ## Stack & rationale
198
+
199
+ - **TypeScript + Bun**, compiled to a single binary with `bun build --compile`.
200
+ One artifact, no runtime to install, and the same binary ships through every
201
+ channel.
202
+ - **decimal.js everywhere.** Never `number` for money — a float rounding error in
203
+ a liquidation distance is a wrong answer that looks right.
204
+ - **Node built-ins for all I/O.** Every dependency is a supply-chain path into a
205
+ process that reads exchange keys, so the dependency list stays near zero.
206
+
207
+ ## Versioning
208
+
209
+ `0.x` while the read-only risk view is finding its shape. `1.0` when it is
210
+ complete and trustworthy *without* an agent — if it is not useful alone, an agent
211
+ on top will not save it.
212
+
213
+ ## Roadmap
214
+
215
+ Full version themes in [ROADMAP.md](./ROADMAP.md); per-version task breakdown in
216
+ [`tasks/`](./tasks); shipped work in [CHANGELOG.md](./CHANGELOG.md).
217
+
218
+ ## Contributing
219
+
220
+ Read [CONTRIBUTING.md](./CONTRIBUTING.md). Agents: [AGENTS.md](./AGENTS.md).
221
+
222
+ New venue connectors are the most useful contribution, and the one thing that
223
+ directly attacks the integration treadmill.
224
+
225
+ ## License
226
+
227
+ MIT — see [LICENSE](./LICENSE).
package/bin/tula ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ echo "tula did not finish installing: the native binary was never copied here." >&2
3
+ echo " Reinstall with: npm install -g @hsnice16/tula" >&2
4
+ echo " Or install directly: curl --proto '=https' --tlsv1.2 -LsSf https://usetu.la/install.sh | sh" >&2
5
+ exit 1
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@hsnice16/tula",
3
+ "version": "0.1.0",
4
+ "description": "Your true exposure, what breaks first, and more, across every venue at once.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": { "tula": "bin/tula" },
8
+ "engines": { "node": ">=22" },
9
+ "files": ["bin/tula", "scripts/postinstall.mjs", "LICENSE", "README.md"],
10
+ "scripts": { "postinstall": "node scripts/postinstall.mjs" },
11
+ "optionalDependencies": {
12
+ "@hsnice16/tula-darwin-arm64": "0.1.0",
13
+ "@hsnice16/tula-darwin-x64": "0.1.0",
14
+ "@hsnice16/tula-linux-x64": "0.1.0",
15
+ "@hsnice16/tula-linux-arm64": "0.1.0"
16
+ },
17
+ "repository": { "type": "git", "url": "git+https://github.com/hsnice16/tula.git" },
18
+ "homepage": "https://usetu.la",
19
+ "keywords": ["trading", "crypto", "defi", "portfolio", "risk", "tui", "cli"]
20
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Replaces the placeholder launcher with the native binary for this platform.
3
+ *
4
+ * Copied over the existing path rather than symlinked: npm has already created
5
+ * its bin shim pointing at `bin/tula`, and overwriting the target keeps that
6
+ * shim valid on every package manager, including the ones that copy rather than
7
+ * link. The installed binary is the compiled executable, so running tula never
8
+ * starts Node — Node is needed to install it, not to run it.
9
+ */
10
+ import { chmodSync, copyFileSync } from 'node:fs'
11
+ import { createRequire } from 'node:module'
12
+ import { dirname, join } from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+
15
+ const require = createRequire(import.meta.url)
16
+ const here = dirname(fileURLToPath(import.meta.url))
17
+ const target = `${process.platform}-${process.arch}`
18
+
19
+ try {
20
+ const source = require.resolve(`@hsnice16/tula-${target}/bin/tula`)
21
+ const launcher = join(here, '..', 'bin', 'tula')
22
+ copyFileSync(source, launcher)
23
+ chmodSync(launcher, 0o755)
24
+ } catch {
25
+ // Left as the placeholder, which says the same thing when run. Exiting
26
+ // non-zero here would fail the whole install of a dependency tree that may
27
+ // not even use tula on this machine.
28
+ console.error(
29
+ `tula has no prebuilt binary for ${target}.\n` +
30
+ ' Supported: darwin-arm64, darwin-x64, linux-x64, linux-arm64.\n' +
31
+ ' Build from source instead: https://github.com/hsnice16/tula',
32
+ )
33
+ }