nous-token 0.1.0 → 0.2.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/PROTOCOL.md +5 -6
- package/README.md +7 -9
- package/cli/setup.ts +3 -57
- package/package.json +1 -1
package/PROTOCOL.md
CHANGED
|
@@ -79,11 +79,11 @@ Users identify themselves with an Ethereum wallet address. The wallet is the per
|
|
|
79
79
|
|
|
80
80
|
### How it works
|
|
81
81
|
|
|
82
|
-
1. User runs `npx nous-token setup`, enters wallet address
|
|
83
|
-
2. CLI
|
|
84
|
-
3.
|
|
85
|
-
4.
|
|
86
|
-
5.
|
|
82
|
+
1. User runs `npx nous-token setup`, enters wallet address
|
|
83
|
+
2. CLI configures AI tools with base URL: `https://gateway.nousai.cc/{provider}/w/{wallet}/...`
|
|
84
|
+
3. First API call through the gateway: gateway hashes the auth token, records usage with wallet
|
|
85
|
+
4. Wallet binding is automatic — no separate linking step, no API key transmission
|
|
86
|
+
5. Old records for the same auth token hash are backfilled to the wallet
|
|
87
87
|
|
|
88
88
|
### Binding rules
|
|
89
89
|
|
|
@@ -161,7 +161,6 @@ Both options use self-to-self transactions with data as calldata. No smart contr
|
|
|
161
161
|
| `/api/wallet/{address}` | GET | Usage for a wallet (all linked hashes aggregated) |
|
|
162
162
|
| `/api/user/{hash}` | GET | Single user aggregated stats |
|
|
163
163
|
| `/api/user/{hash}/receipts` | GET | Signed receipts (paginated) |
|
|
164
|
-
| `/api/link` | POST | Link hash to wallet (`{api_key, wallet}`) — first bind wins |
|
|
165
164
|
| `/api/records` | GET | Raw records for sentinel verification |
|
|
166
165
|
| `/api/chain` | GET | Merkle root and MMR state |
|
|
167
166
|
| `/api/sign` | POST | Signed summary (optional aggregation) |
|
package/README.md
CHANGED
|
@@ -12,11 +12,9 @@ One command to track all your AI spending across every provider. Your data is si
|
|
|
12
12
|
npx nous-token setup
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The CLI asks
|
|
16
|
-
1. **Wallet address** (0x...) — your permanent identity across API keys
|
|
17
|
-
2. **API key** — used locally to compute your hash, never sent anywhere
|
|
15
|
+
The CLI asks one thing: your **wallet address** (0x...) — your permanent identity.
|
|
18
16
|
|
|
19
|
-
Then it auto-detects your AI tools (Claude Code, Cursor, Python SDKs, etc.) and routes them through the gateway.
|
|
17
|
+
Then it auto-detects your AI tools (Claude Code, Cursor, Python SDKs, etc.) and routes them through the gateway. Works with both API keys and subscription plans (Claude Max, etc.).
|
|
20
18
|
|
|
21
19
|
## How It Works
|
|
22
20
|
|
|
@@ -56,9 +54,10 @@ Works with any OpenAI-compatible API out of the box:
|
|
|
56
54
|
Your wallet address is your permanent identity. API keys expire and rotate — your wallet doesn't.
|
|
57
55
|
|
|
58
56
|
- Run `npx nous-token setup` with your wallet address
|
|
59
|
-
- All usage across any API key links to your wallet
|
|
60
|
-
- Switch keys, switch machines — your history follows you
|
|
61
|
-
- First bind is permanent (prevents rebind with leaked keys)
|
|
57
|
+
- All usage across any API key or OAuth token links to your wallet
|
|
58
|
+
- Switch keys, switch machines, switch plans — your history follows you
|
|
59
|
+
- First bind is permanent per auth token (prevents rebind with leaked keys)
|
|
60
|
+
- Works with API keys and subscription plans (Claude Max, ChatGPT Plus, etc.)
|
|
62
61
|
|
|
63
62
|
## Privacy by Structure
|
|
64
63
|
|
|
@@ -66,7 +65,7 @@ Not by promise — by code. [Audit the source](gateway/src/index.ts).
|
|
|
66
65
|
|
|
67
66
|
| Data | What happens |
|
|
68
67
|
|------|-------------|
|
|
69
|
-
|
|
|
68
|
+
| Auth Token | Hashed (SHA-256) for identity. Never stored. Works with API keys and OAuth tokens. |
|
|
70
69
|
| Prompts | `request.body` piped directly to provider. Never read. |
|
|
71
70
|
| Responses (streaming) | Tee'd. Only last 4KB buffered to extract `.usage`. |
|
|
72
71
|
| Responses (non-streaming) | In V8 isolate memory. Only `.usage` accessed. GC'd after request. |
|
|
@@ -107,7 +106,6 @@ Gateway: `https://gateway.nousai.cc`
|
|
|
107
106
|
| `/api/wallet/{address}` | GET | Usage for a wallet (all linked hashes) |
|
|
108
107
|
| `/api/user/{hash}` | GET | Single user stats |
|
|
109
108
|
| `/api/user/{hash}/receipts` | GET | Signed receipts (paginated) |
|
|
110
|
-
| `/api/link` | POST | Link hash to wallet (`{api_key, wallet}`) |
|
|
111
109
|
| `/api/records` | GET | Raw records for sentinel verification |
|
|
112
110
|
| `/api/chain` | GET | Merkle root and MMR state |
|
|
113
111
|
| `/api/sign` | POST | Signed summary for on-chain storage |
|
package/cli/setup.ts
CHANGED
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
// Scans for installed AI tools, configures them to route through
|
|
8
8
|
// the nous-token gateway for usage tracking.
|
|
9
9
|
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { join } from "path";
|
|
13
13
|
import { execSync } from "child_process";
|
|
14
|
-
import { createHash } from "crypto";
|
|
15
14
|
|
|
16
15
|
const GATEWAY = "https://gateway.nousai.cc";
|
|
17
16
|
const HOME = homedir();
|
|
@@ -26,7 +25,7 @@ function saveWallet(wallet: string): void {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
function gatewayUrl(path: string, wallet: string): string {
|
|
29
|
-
return wallet ? `${GATEWAY}${path}
|
|
28
|
+
return wallet ? `${GATEWAY}${path}/w/${wallet}` : `${GATEWAY}${path}`;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
interface Tool {
|
|
@@ -181,27 +180,6 @@ function setShellEnv(key: string, value: string, toolName: string): ConfigResult
|
|
|
181
180
|
return { success: true, message: `set ${key} in ${rcName}` };
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
// ── Find first available API key ──
|
|
185
|
-
|
|
186
|
-
function findFirstApiKey(): string | null {
|
|
187
|
-
const keys = [
|
|
188
|
-
process.env.OPENAI_API_KEY,
|
|
189
|
-
process.env.ANTHROPIC_API_KEY,
|
|
190
|
-
process.env.DEEPSEEK_API_KEY,
|
|
191
|
-
process.env.GEMINI_API_KEY,
|
|
192
|
-
process.env.GROQ_API_KEY,
|
|
193
|
-
];
|
|
194
|
-
for (const key of keys) {
|
|
195
|
-
if (key) return key.replace(/^Bearer\s+/i, "");
|
|
196
|
-
}
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function computeUserHash(): string | null {
|
|
201
|
-
const key = findFirstApiKey();
|
|
202
|
-
if (!key) return null;
|
|
203
|
-
return createHash("sha256").update(key).digest("hex").slice(0, 32);
|
|
204
|
-
}
|
|
205
183
|
|
|
206
184
|
// ── Prompt helper ──
|
|
207
185
|
|
|
@@ -242,39 +220,7 @@ if (wallet) {
|
|
|
242
220
|
process.exit(0);
|
|
243
221
|
}
|
|
244
222
|
|
|
245
|
-
// Step 2:
|
|
246
|
-
let apiKey = findFirstApiKey();
|
|
247
|
-
if (!apiKey) {
|
|
248
|
-
const input = await ask(" API key (sk-...): ");
|
|
249
|
-
if (input) {
|
|
250
|
-
apiKey = input.replace(/^Bearer\s+/i, "");
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Step 3: Link hash → wallet via gateway
|
|
255
|
-
if (apiKey) {
|
|
256
|
-
const hash = createHash("sha256").update(apiKey).digest("hex").slice(0, 32);
|
|
257
|
-
console.log(` \x1b[90mUser hash: ${hash}\x1b[0m`);
|
|
258
|
-
try {
|
|
259
|
-
const res = await fetch(`${GATEWAY}/api/link`, {
|
|
260
|
-
method: "POST",
|
|
261
|
-
headers: { "Content-Type": "application/json" },
|
|
262
|
-
body: JSON.stringify({ api_key: apiKey, wallet }),
|
|
263
|
-
});
|
|
264
|
-
const data = await res.json() as { ok?: boolean; error?: string };
|
|
265
|
-
if (data.ok) {
|
|
266
|
-
console.log(` \x1b[32m✓ Linked to wallet\x1b[0m\n`);
|
|
267
|
-
} else {
|
|
268
|
-
console.log(` \x1b[90m${data.error || "No existing records to link — they'll link automatically on first use."}\x1b[0m\n`);
|
|
269
|
-
}
|
|
270
|
-
} catch {
|
|
271
|
-
console.log(" \x1b[90mCouldn't reach gateway — linking will happen on first use.\x1b[0m\n");
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
console.log(" \x1b[90mNo API key found. Wallet will link on first use through the gateway.\x1b[0m\n");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Step 4: Configure tools
|
|
223
|
+
// Step 2: Configure tools
|
|
278
224
|
console.log(" Scanning for AI tools...\n");
|
|
279
225
|
|
|
280
226
|
let configured = 0;
|