@lightninglabs/pi-402 0.0.0-stage → 0.0.2

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/INSTALL.md ADDED
@@ -0,0 +1,270 @@
1
+ # Installing pi-402
2
+
3
+ `pi-402` is a standard [Pi](https://pi.dev) extension. Pi can load an extension
4
+ from a managed package, from an auto-discovered directory, or from a per-run
5
+ flag, and pi-402 works with all three. Pi itself is not patched or forked. We
6
+ have verified the install paths below against Pi 1.1.0.
7
+
8
+ ## Prerequisites
9
+
10
+ You need Pi (`pi --version`) and Node.js 22.19 or newer. That is all for the
11
+ install. To make a payment you also need two things that pi-402 does not
12
+ provide:
13
+
14
+ - An L402 proxy that fronts a model, reachable at the URL you put in
15
+ `L402_PROXY_URL`. There is no hosted endpoint, so you run one yourself or use
16
+ one run by a provider. See [Bring your own seller](#bring-your-own-seller).
17
+ - A funded wallet. The wallet is built in and creates itself on first use, and
18
+ you fund it with `/fund` once Pi is running. See [Wallet](#wallet).
19
+
20
+ ## Option 1: pi install from npm (recommended)
21
+
22
+ Pi's package manager downloads the package, records it in your settings, and
23
+ loads it on every run. The package carries the embedded wallet's WASM bundle
24
+ (about 137 MB unpacked, 22 MB to download), so nothing else needs building.
25
+
26
+ ```bash
27
+ # Global (user scope, ~/.pi/agent): available in every project.
28
+ pi install npm:@lightninglabs/pi-402
29
+
30
+ # Project scope only (recorded in ./.pi):
31
+ pi install npm:@lightninglabs/pi-402 -l
32
+ ```
33
+
34
+ The package declares its entry point in `package.json`, so Pi finds the
35
+ extension without any further wiring:
36
+
37
+ ```json
38
+ "pi": { "extensions": ["./src/index.ts"] }
39
+ ```
40
+
41
+ To update or remove it later:
42
+
43
+ ```bash
44
+ pi update --extensions
45
+ pi remove npm:@lightninglabs/pi-402 # alias: pi uninstall
46
+ ```
47
+
48
+ ### Installing from git instead
49
+
50
+ ```bash
51
+ pi install git:github.com/lightninglabs/pi-402
52
+ ```
53
+
54
+ A git checkout does not contain the wallet's WASM bundle. It is about 137 MB, so
55
+ it ships in the npm package and not in the repository. After a git install, stage
56
+ a bundle built from [Wavelength](https://github.com/lightninglabs/wavelength)
57
+ (`make wasm-wallet` writes it to `bin/wasm`) into the package, or point
58
+ `PI402_WASM_DIR` at it:
59
+
60
+ ```bash
61
+ scripts/stage-wasm.sh /path/to/wavelength/bin/wasm # copies into ./wasm
62
+ # or, without copying anything:
63
+ export PI402_WASM_DIR=/path/to/wavelength/bin/wasm
64
+ ```
65
+
66
+ ## Option 2: auto-discovered directory
67
+
68
+ Pi scans two directories on startup and loads any extension it finds there:
69
+
70
+ ```bash
71
+ # Global: every project.
72
+ git clone https://github.com/lightninglabs/pi-402.git ~/.pi/agent/extensions/pi-402
73
+
74
+ # Project-local: only when Pi runs in this project.
75
+ git clone https://github.com/lightninglabs/pi-402.git ./.pi/extensions/pi-402
76
+ ```
77
+
78
+ Install its dependencies once with `npm install` inside the clone, and stage the
79
+ WASM bundle as described above.
80
+
81
+ ## Option 3: per-run flag
82
+
83
+ Point Pi at a checkout for a single session, which is the usual way to develop
84
+ the extension:
85
+
86
+ ```bash
87
+ pi -e /path/to/pi-402 # -e is short for --extension
88
+ ```
89
+
90
+ ## Where Pi looks for extensions
91
+
92
+ | Location | Scope | How it gets there |
93
+ |---|---|---|
94
+ | `./.pi/extensions/` | Project | Option 2 (project) |
95
+ | `~/.pi/agent/extensions/` | Global | Option 2 (global) |
96
+ | Recorded packages in settings | Project or global | `pi install` (Option 1) |
97
+ | `--extension <path>` / `-e <path>` | This run only | Option 3 |
98
+
99
+ The agent directory is overridable via `PI_CODING_AGENT_DIR`.
100
+
101
+ ## Bring your own seller
102
+
103
+ pi-402 is the buyer. The seller is an [aperture](https://github.com/lightninglabs/aperture)
104
+ proxy that fronts an OpenAI-compatible model endpoint, answers an unpaid request
105
+ with a `402` challenge carrying a Lightning invoice, and meters the paid bundle
106
+ as the model serves tokens. Aperture's
107
+ [`docs/metering.md`](https://github.com/lightninglabs/aperture/blob/master/docs/metering.md)
108
+ describes how that works, and
109
+ [lightmeter](https://github.com/lightninglabs/lightmeter) is the metering
110
+ controller that prices bundles. If you want to run the seller on your own
111
+ machine to try pi-402, `dev/regtest/README.md` and `dev/signet-demo.md` walk
112
+ through a complete stack.
113
+
114
+ Whoever runs the seller, you point pi-402 at it with one variable:
115
+
116
+ ```bash
117
+ export L402_PROXY_URL="https://your-aperture.example/v1"
118
+ ```
119
+
120
+ The URL is the base that the OpenAI client appends `/chat/completions` to, so it
121
+ includes the API prefix (`/v1` for aperture). If it is unset, pi-402 uses
122
+ `http://localhost:8402`. The proxy holds the model credential. Pi never sees an
123
+ API key.
124
+
125
+ ## Wallet
126
+
127
+ The wallet is Wavelength's, running inside the Pi process as WASM. There is no
128
+ daemon to start. On the first session it creates itself under
129
+ `~/.pi402/wallet` (override with `PI402_WALLET_DIR`) and writes its seed words
130
+ to `seed.txt` there, readable only by you. Back that file up. It is the only
131
+ copy, and the wallet cannot be recovered without it.
132
+
133
+ A running native `waved` can be used instead of the embedded wallet. Set
134
+ `PI402_WALLETD_GATEWAY` to its HTTP/JSON gateway (for example
135
+ `http://localhost:10031`) and pi-402 drives that daemon and loads no WASM.
136
+
137
+ ### Choosing a network
138
+
139
+ `PI402_NETWORK` selects the network and defaults to `regtest`, which is for the
140
+ local dev stack. For anything real, set it explicitly. On `signet` and the
141
+ other test networks the embedded daemon finds the hosted Ark operator, swap
142
+ server and chain endpoints by itself. Only `regtest` needs them given, and it
143
+ defaults to the local compose stack's addresses; `PI402_ARK_SERVER`,
144
+ `PI402_SWAP_SERVER` and `PI402_ESPLORA_URL` override them on any network.
145
+
146
+ On non-mainnet networks the wallet uses a throwaway default password and
147
+ generates a seed without asking. Mainnet is stricter, because the funds are real:
148
+
149
+ - `PI402_WALLET_PASSWORD` must be set to a secret of your own. The extension
150
+ refuses to start a mainnet wallet with the built-in default.
151
+ - A fresh mainnet wallet will not generate a seed that would be written to disk
152
+ in the clear. Either restore a seed you already hold by setting
153
+ `PI402_WALLET_MNEMONIC` to its words, or set `PI402_ALLOW_PLAINTEXT_SEED=1` to
154
+ accept a plaintext `seed.txt` beside the wallet database.
155
+ - If `seed.txt` is present but the wallet database is gone, creation is refused
156
+ so the old words are never mistaken for the new wallet's backup. Restore from
157
+ the words with `PI402_WALLET_MNEMONIC`, or move the file away to start fresh.
158
+
159
+ ### Funding
160
+
161
+ Inside Pi:
162
+
163
+ ```
164
+ /fund 50000 a Lightning invoice; pay it from the Wavelength app
165
+ /deposit an on-chain boarding address
166
+ /balance confirmed and pending balance
167
+ /wallet wallet status, and the balance in the status line
168
+ /receipts every payment made: count, all-time sats, and today's sats
169
+ ```
170
+
171
+ `/fund` is the quick path, since an invoice settles through a swap in seconds
172
+ while an on-chain deposit needs a confirmation and an Ark round. Whatever you
173
+ fund is the most the agent can ever spend.
174
+
175
+ ## Configure and use
176
+
177
+ ```bash
178
+ export L402_PROXY_URL="https://your-aperture.example/v1"
179
+ export PI402_NETWORK=signet
180
+ export PI402_MAX_SAT_PER_REQUEST=20000 # see the note below
181
+ export PI402_DAILY_CAP_SAT=100000
182
+
183
+ pi
184
+ # then, inside Pi:
185
+ /model l402-baseten/kimi-k3
186
+ ```
187
+
188
+ The per-request ceiling defaults to 1000 sats and is checked against the quoted
189
+ total outflow, meaning the invoice plus the swap fee. A seller that sells a
190
+ large token bundle in one invoice can quote more than that, and then the budget
191
+ policy declines the payment. Set the ceiling above the bundle price you expect.
192
+
193
+ | Env var | Default | Purpose |
194
+ |---------|---------|---------|
195
+ | `L402_PROXY_URL` | `http://localhost:8402` | Base URL of the L402 proxy, including the API prefix |
196
+ | `PI402_MAX_SAT_PER_REQUEST` | `1000` | Per-request spend ceiling, checked on the quoted total outflow |
197
+ | `PI402_DAILY_CAP_SAT` | `100000` | Daily spend ceiling, seeded from the receipt ledger on restart |
198
+ | `PI402_PAYMENT_SCHEME` | `auto` | `auto` takes a Payment offer when the seller makes one and falls back to L402; `l402` pins L402 |
199
+ | `PI402_MPP_INTENTS` | `charge` | Payment intents to accept. `session` is refused, since the swap-backed wallet cannot mint the refund invoice it needs |
200
+ | `PI402_NETWORK` | `regtest` | Network the embedded wallet runs on (`signet`, `mainnet`, and so on) |
201
+ | `PI402_WALLET_PASSWORD` | dev default | Wallet password; required on mainnet |
202
+ | `PI402_WALLET_MNEMONIC` | unset | Seed words to restore on first run instead of generating a wallet |
203
+ | `PI402_ALLOW_PLAINTEXT_SEED` | unset | Set to `1` to let a new mainnet wallet write a plaintext `seed.txt` |
204
+ | `PI402_WALLET_DIR` | `$PI402_DATA_DIR/wallet` | Where the embedded wallet keeps its databases and `seed.txt` |
205
+ | `PI402_DATA_DIR` | `~/.pi402` | Token cache, credential cache, receipt ledger and wallet |
206
+ | `PI402_WASM_DIR` | `<package>/wasm` | Directory holding the WASM bundle, overriding the bundled copy |
207
+ | `PI402_WALLETD_GATEWAY` | unset | Use a native `waved` at this gateway URL instead of the embedded wallet |
208
+ | `PI402_ARK_SERVER` | regtest: `127.0.0.1:7070` | Ark operator address; other networks resolve it by name |
209
+ | `PI402_SWAP_SERVER` | regtest: `127.0.0.1:8082` | Swap server address; other networks resolve it by name |
210
+ | `PI402_ESPLORA_URL` | regtest: `http://127.0.0.1:3002` | Esplora API; other networks resolve it by name |
211
+ | `PI402_SERVER_INSECURE` | `1` on regtest, else `0` | Plaintext transport to the operator and swap server |
212
+ | `PI402_DEBUG_LEVEL` | `error` | Embedded daemon log level (`debug`, `trace`, or `info,ROND=debug`) |
213
+ | `PI402_MOCK_PAYER` | unset | Set to `1` to skip Lightning and "pay" with a mock, against a mock proxy |
214
+
215
+ The embedded daemon logs through the Pi process, so it logs errors only by
216
+ default to keep its output off the TUI. Raise `PI402_DEBUG_LEVEL` when a swap
217
+ sticks.
218
+
219
+ ## Trying it without a seller
220
+
221
+ `PI402_MOCK_PAYER=1` runs the buyer loop with no wallet and no Lightning, against
222
+ any mock L402 proxy such as `dev/mock-l402-proxy.ts`:
223
+
224
+ ```bash
225
+ export PI402_MOCK_PAYER=1
226
+ export L402_PROXY_URL="http://localhost:8402"
227
+ pi -e /path/to/pi-402
228
+ ```
229
+
230
+ ## Developing against a local aperture-ts checkout
231
+
232
+ The buyer loop comes from `@lightninglabs/aperture-client` and
233
+ `@lightninglabs/aperture-core`, which `npm install` fetches from npm. To work
234
+ against an unreleased [aperture-ts](https://github.com/lightninglabs/aperture-ts)
235
+ instead, build it and link the two packages into your checkout:
236
+
237
+ ```bash
238
+ cd aperture-ts && pnpm install && pnpm -r build
239
+ cd ../pi-402 && npm install --legacy-peer-deps
240
+ mkdir -p node_modules/@lightninglabs
241
+ ln -sfn ../../../aperture-ts/packages/core node_modules/@lightninglabs/aperture-core
242
+ ln -sfn ../../../aperture-ts/packages/client node_modules/@lightninglabs/aperture-client
243
+ ```
244
+
245
+ The Pi packages (`@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`) are
246
+ peers. Pi provides them when it loads the extension, but `npm run check` needs
247
+ them installed in the checkout.
248
+
249
+ ## Troubleshooting
250
+
251
+ ```bash
252
+ pi -ne # start with no extensions, to isolate a load failure
253
+ ```
254
+
255
+ If the extension fails to load, Pi prints `Failed to load extension "<path>"`
256
+ and the paid models never appear. The usual cause:
257
+
258
+ - `Cannot find module '@lightninglabs/aperture-client'`: dependencies are not
259
+ installed. Reinstall from npm, or run `npm install` in a checkout.
260
+
261
+ If the extension loads but the wallet cannot start, Pi keeps running and prints
262
+ `Extension "<path>" error: ...` at session start. The usual causes:
263
+
264
+ - `wasm bundle at ... is missing ...`: the bundle is incomplete or absent. Stage
265
+ one with `scripts/stage-wasm.sh`, or set `PI402_WASM_DIR`.
266
+ - `wasm bundle at ... would load as ES modules`: the bundle sits inside a
267
+ `"type": "module"` package without a `package.json` of its own. Re-run
268
+ `scripts/stage-wasm.sh`, or add a `package.json` containing
269
+ `{"type":"commonjs"}` to the bundle directory.
270
+ - A payment declined with a budget error: raise `PI402_MAX_SAT_PER_REQUEST`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lightning Labs
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 CHANGED
@@ -1,3 +1,193 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <a href="https://pi.dev">
3
+ <img alt="pi logo" src="https://pi.dev/logo-auto.svg" width="128">
4
+ </a>
5
+ </p>
6
+ <p align="center">
7
+ <strong>pi-402</strong>: pay for inference with Lightning
8
+ </p>
2
9
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
10
+ # pi-402
11
+
12
+ `pi-402` is a [Pi](https://pi.dev) extension that pays for LLM inference over
13
+ Lightning. It loads an embedded
14
+ [Wavelength](https://github.com/lightninglabs/wavelength) wallet, registers a
15
+ paid model provider, and settles each inference call against an
16
+ [aperture](https://github.com/lightninglabs/aperture) proxy, so you can run Pi
17
+ against a metered model with sats instead of an API key.
18
+
19
+ No changes to Pi itself: this is a self-contained extension you drop into your
20
+ own Pi install.
21
+
22
+ Inference is priced by the token rather than by the request. One payment buys a
23
+ bundle of tokens, and the proxy debits what the model actually reported serving,
24
+ weighted by direction, until the bundle runs out and a fresh challenge arrives.
25
+ See [`docs/metering.md`](https://github.com/lightninglabs/aperture/blob/master/docs/metering.md)
26
+ in aperture for how that works end to end.
27
+
28
+ ## How it works
29
+
30
+ ```
31
+ Pi ──▶ pi-402 provider ──▶ OpenAI client (fetch = payingFetch) ──▶ aperture ──▶ Baseten
32
+ │ │ 402 → pay → retry │
33
+ └── Wavelength wallet ◀───┘ pays the BOLT11 invoice └── holds the model key
34
+ (Ark↔Lightning swap)
35
+ ```
36
+
37
+ The extension registers a provider whose `streamSimple` owns the request path,
38
+ wrapping the model call in a paying `fetch`. When the proxy answers `402`, the
39
+ buyer loop runs your budget policy, pays the invoice through the embedded
40
+ wallet, caches the credential, and retries. Streaming included, and the agent
41
+ loop above it never learns that a payment happened.
42
+
43
+ The proxy offers two doors on the same `402` and the buyer takes whichever it
44
+ speaks: **L402** (a macaroon bound to the invoice's payment hash, which you can
45
+ attenuate offline and hand to a sub-agent) and the **Payment HTTP
46
+ Authentication Scheme** (`draft-httpauth-payment-00`, the IETF draft co-authored
47
+ by Stripe and Tempo). `PI402_PAYMENT_SCHEME` picks; the default takes a Payment
48
+ offer when the seller makes one and falls back to L402.
49
+
50
+ No API key ever reaches the buyer. Aperture holds the model credential and
51
+ injects it server-side, so the buyer holds sats and the seller holds the key.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pi install npm:@lightninglabs/pi-402
57
+ ```
58
+
59
+ Pi's package manager downloads the package and loads it on every run. The
60
+ package carries the embedded wallet's WASM bundle, so there is nothing to build
61
+ and no daemon to start. You need Node.js 22.19 or newer, and we have verified it
62
+ against Pi 1.1.0. [INSTALL.md](INSTALL.md) covers the alternatives (a git
63
+ install, an auto-discovered directory, and `pi -e` for development) and
64
+ troubleshooting.
65
+
66
+ pi-402 is the buyer half. It pays a seller, and there is no hosted seller to
67
+ point it at yet, so you bring your own.
68
+
69
+ ## Bring your own seller
70
+
71
+ The seller is an [aperture](https://github.com/lightninglabs/aperture) proxy in
72
+ front of an OpenAI-compatible model endpoint, with metering turned on so a
73
+ payment buys a bundle of tokens that is drawn down as the model serves them.
74
+ Aperture's
75
+ [`docs/metering.md`](https://github.com/lightninglabs/aperture/blob/master/docs/metering.md)
76
+ explains the mechanism, and [lightmeter](https://github.com/lightninglabs/lightmeter)
77
+ is the controller that prices the bundles. Run your own, or use one a provider
78
+ runs, and point pi-402 at it:
79
+
80
+ ```bash
81
+ export L402_PROXY_URL="https://your-aperture.example/v1" # include the /v1
82
+ export PI402_NETWORK=signet # defaults to regtest
83
+ export PI402_MAX_SAT_PER_REQUEST=20000 # above the bundle price
84
+ ```
85
+
86
+ The per-request ceiling defaults to 1000 sats and is checked against the quoted
87
+ total outflow, so raise it above whatever your seller's bundle costs or the
88
+ budget policy will decline the payment. `dev/regtest/README.md` and
89
+ `dev/signet-demo.md` bring up a complete seller and buyer on one machine, which
90
+ is the way to try pi-402 end to end.
91
+
92
+ ## Use
93
+
94
+ Select a paid model and prompt:
95
+
96
+ ```
97
+ /model l402-baseten/kimi-k3 # 1M context, natively multimodal
98
+ /model l402-baseten/kimi-k3.1-pro # 1M context, cheaper than K3 upstream
99
+ /model l402-baseten/glm-4.7 # 200K context
100
+ /model l402-baseten/glm-5.2 # 200K context
101
+ /model l402-baseten/deepseek-v4-flash # 1M context, the cheap one
102
+ ```
103
+
104
+ These are the models the extension registers. What a given seller actually serves
105
+ is up to the seller.
106
+
107
+ Wallet commands:
108
+
109
+ ```
110
+ /wallet show wallet status and balance
111
+ /fund <sats> show a Lightning invoice to fund the wallet (settles in seconds)
112
+ /deposit show an on-chain boarding address to fund the wallet
113
+ /balance show confirmed balance
114
+ /receipts show the payment ledger: count, all-time and today's sats
115
+ ```
116
+
117
+ `/fund` is the path to use. It mints a BOLT11 invoice that you pay from the
118
+ [Wavelength](https://wavelength.money) app on your phone, and the sats land in
119
+ seconds through an inbound swap. The wallet is an Ark wallet, so there are no
120
+ channels to open, no inbound liquidity to source, and nothing to keep online.
121
+
122
+ Whatever you fund is the ceiling on what the agent can ever spend, which is a
123
+ more comfortable thing to hand an autonomous process than a card.
124
+
125
+ ## The wallet
126
+
127
+ The wallet runs inside the Pi process as WASM, shipped in the package. On the
128
+ first session it creates itself under `~/.pi402/wallet` and writes its seed
129
+ words to `seed.txt` there. Back that file up: it is the only copy.
130
+
131
+ On mainnet we are stricter, because the funds are real. `PI402_WALLET_PASSWORD`
132
+ must be set to a secret of your own, and a new wallet will not write a plaintext
133
+ seed unless you either restore words you already hold with
134
+ `PI402_WALLET_MNEMONIC` or accept the plaintext file with
135
+ `PI402_ALLOW_PLAINTEXT_SEED=1`.
136
+
137
+ Two overrides exist. `PI402_WASM_DIR` points at a different WASM bundle than the
138
+ bundled one, for example a fresh build from Wavelength. `PI402_WALLETD_GATEWAY`
139
+ points at a native `waved` that is already running, and pi-402 then drives that
140
+ daemon and loads no WASM.
141
+
142
+ ## Configuration
143
+
144
+ | Env var | Default | Description |
145
+ |---------|---------|-------------|
146
+ | `L402_PROXY_URL` | `http://localhost:8402` | Base URL of the L402 proxy fronting the model, including the API prefix (for aperture, `/v1`) |
147
+ | `PI402_MAX_SAT_PER_REQUEST` | `1000` | Per-request spend ceiling, checked on the quoted total outflow |
148
+ | `PI402_DAILY_CAP_SAT` | `100000` | Daily spend ceiling, seeded from the receipt ledger on restart |
149
+ | `PI402_PAYMENT_SCHEME` | `auto` | `auto` takes a Payment offer when the seller makes one, else L402; `l402` forces the L402 door |
150
+ | `PI402_MPP_INTENTS` | `charge` | Comma-separated Payment intents to accept. Sessions need a return-invoice minter, which the swap-backed wallet cannot supply yet, so asking for `session` here throws. |
151
+ | `PI402_NETWORK` | `regtest` | Network the wallet runs on, for example `signet` or `mainnet` |
152
+ | `PI402_WALLET_PASSWORD` | dev default | Wallet password; mainnet refuses the default |
153
+ | `PI402_WALLET_MNEMONIC` | unset | Seed words to restore on first run instead of generating a wallet |
154
+ | `PI402_ALLOW_PLAINTEXT_SEED` | unset | `1` lets a new mainnet wallet write a plaintext `seed.txt` |
155
+ | `PI402_WALLET_DIR` | `$PI402_DATA_DIR/wallet` | Wallet databases and `seed.txt` |
156
+ | `PI402_DATA_DIR` | `~/.pi402` | Token cache, credential cache, receipt ledger, and the wallet |
157
+ | `PI402_WASM_DIR` | `<package>/wasm` | WASM bundle directory, overriding the bundled copy |
158
+ | `PI402_WALLETD_GATEWAY` | unset | Use a native `waved` at this HTTP/JSON gateway (e.g. `http://localhost:10031`) instead of the embedded wallet |
159
+ | `PI402_ARK_SERVER` | regtest only | Ark operator address; other networks resolve it by name |
160
+ | `PI402_SWAP_SERVER` | regtest only | Swap server address; other networks resolve it by name |
161
+ | `PI402_ESPLORA_URL` | regtest only | Esplora API; other networks resolve it by name |
162
+ | `PI402_SERVER_INSECURE` | `1` on regtest, else `0` | Plaintext transport to the operator and swap server |
163
+ | `PI402_DEBUG_LEVEL` | `error` | Embedded daemon log level. Quiet by default so it stays off the TUI; raise it (`debug`, or `info,ROND=debug`) when a swap sticks |
164
+ | `PI402_MOCK_PAYER` | unset | `1` uses the mock payer (no Lightning; dev only) |
165
+
166
+ ## Development
167
+
168
+ This repo is also the dev environment for the extension.
169
+
170
+ ```bash
171
+ npm install --legacy-peer-deps
172
+ npm run check # biome + tsc
173
+ npm test
174
+ ```
175
+
176
+ The buyer loop reuses [`@lightninglabs/aperture-client`](https://github.com/lightninglabs/aperture-ts)
177
+ (`createPayingFetch`) and [`@lightninglabs/aperture-core`](https://github.com/lightninglabs/aperture-ts)
178
+ (`InvoicePayer`, budget policies), both from npm. INSTALL.md has a note on linking
179
+ a local aperture-ts checkout instead.
180
+
181
+ The WASM bundle is not in git. To work on the embedded wallet, build it with
182
+ `make wasm-wallet` in Wavelength and run `scripts/stage-wasm.sh <path-to-bin/wasm>`.
183
+ `npm publish` refuses to run unless that staged bundle is complete.
184
+
185
+ ## Docs
186
+
187
+ - [`docs/architecture.md`](docs/architecture.md): a map of the extension: components, request and payment pipelines, storage, and concurrency.
188
+ - [`docs/l402-walletdk-inference-payments.md`](docs/l402-walletdk-inference-payments.md): end-to-end design of the extension.
189
+ - [`docs/l402-protocol-extensions.md`](docs/l402-protocol-extensions.md): furthering L402 for metered / streaming inference: keep the macaroon + preimage atomic core, add the session economic model (verifiable receipts, HTLC-native refunds, a BOLT12 payment plane), informed by the Machine Payment Protocol.
190
+
191
+ ## License
192
+
193
+ MIT
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
- "name": "@lightninglabs/pi-402",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@lightninglabs/pi-402",
3
+ "version": "0.0.2",
4
+ "description": "Pay for Pi inference with Lightning: an L402 + walletdk extension for the Pi coding agent",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lightninglabs/pi-402.git"
10
+ },
11
+ "pi": {
12
+ "extensions": [
13
+ "./src/index.ts"
14
+ ]
15
+ },
16
+ "files": [
17
+ "src",
18
+ "wasm",
19
+ "README.md",
20
+ "INSTALL.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "stage-wasm": "scripts/stage-wasm.sh",
25
+ "prepublishOnly": "scripts/stage-wasm.sh --verify",
26
+ "check": "biome check --write --error-on-warnings . && tsc --noEmit",
27
+ "test": "node --test 'test/**/*.test.ts'",
28
+ "format": "biome format --write .",
29
+ "clean": "echo 'nothing to clean'"
30
+ },
31
+ "dependencies": {
32
+ "@lightninglabs/aperture-client": "~0.0.1",
33
+ "@lightninglabs/aperture-core": "~0.0.1",
34
+ "openai": "6.26.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@earendil-works/pi-ai": "*",
38
+ "@earendil-works/pi-coding-agent": "*"
39
+ },
40
+ "devDependencies": {
41
+ "@biomejs/biome": "2.3.5",
42
+ "@types/node": "22.19.19",
43
+ "typescript": "5.9.3"
44
+ },
45
+ "engines": {
46
+ "node": ">=22.19.0"
47
+ }
48
+ }
package/src/CLAUDE.md ADDED
@@ -0,0 +1,57 @@
1
+ # pi-402/src
2
+
3
+ ## Purpose
4
+
5
+ The pi-402 extension itself: the code Pi loads to register a paid, L402-settled
6
+ model provider backed by an embedded Wavelength wallet.
7
+
8
+ ## Module Structure
9
+
10
+ - `index.ts` — extension entry (`export default function (pi)`): registers the
11
+ provider, bootstraps the wallet on `session_start`, and wires the wallet
12
+ commands.
13
+ - `provider.ts` — `makeStreamSimple`: the provider's `streamSimple`. Builds an
14
+ OpenAI client whose `fetch` is `createPayingFetch(...)` and translates the
15
+ OpenAI SSE stream into Pi's `AssistantMessageEventStream`, text, reasoning
16
+ and tool calls alike (`BlockWriter`). This is the seam that injects payment
17
+ without touching Pi core.
18
+ - `payer.ts` — `InvoicePayer` implementations: `MockInvoicePayer` (dev) and
19
+ `WalletInvoicePayer` (delegates to `WalletService`).
20
+ - `wallet.ts` — `WalletService`: the embedded Wavelength WASM wrapper
21
+ (`prepareSend` → `sendPrepared` → await preimage; balance; boarding address).
22
+ - `policy.ts` — `buildBudgetPolicy`: per-request and daily spend ceilings; the
23
+ daily counter seeds from the receipt ledger on first assertion.
24
+ - `store.ts` — durable state under `PI402_DATA_DIR` (default `~/.pi402`):
25
+ `FileTokenStore` (paid L402 tokens, atomic rewrite, 0600) and
26
+ `FileReceiptLog` (append-only NDJSON spend ledger).
27
+ - `commands.ts` — `/wallet`, `/fund`, `/deposit`, `/balance`, `/receipts`. Two
28
+ funding paths: `/fund <sats>` returns a BOLT11 invoice (wallet `receive`,
29
+ settled through an inbound swap in seconds), `/deposit` returns an on-chain
30
+ boarding address (a confirmation plus an Ark round before it is spendable).
31
+
32
+ ## Invariants
33
+
34
+ - `streamSimple` owns the whole request path; payment is injected here, nowhere
35
+ in Pi core. The OpenAI client sets `maxRetries: 0` so the paying fetch owns
36
+ retry semantics and the SDK never re-enters it to double-pay.
37
+ - The token cache is per provider instance and file-backed outside mock mode,
38
+ so paid tokens are reused until expiry, across process restarts, and retries
39
+ never re-pay. Every settled payment appends a self-verifying receipt
40
+ (`sha256(preimage) == payment_hash`) to the ledger; mock-mode spends never
41
+ touch either file.
42
+ - The budget policy runs before any wallet action, and the wallet re-asserts the
43
+ per-request ceiling on the *quoted total outflow* (invoice principal plus swap
44
+ fee) after `prepareSend`, before dispatching `send`/`sendPrepared`.
45
+
46
+ ## Status / TODO
47
+
48
+ - `provider.ts` streams text, reasoning and tool calls. Declaring Pi's tools on
49
+ the request is not optional: Pi's system prompt tells the model it has tools
50
+ either way, so a request that omits them leaves a model like Kimi K3 emitting
51
+ its own native tool-call syntax as ordinary assistant text. Images on the
52
+ input side are still unmapped.
53
+ - `wallet.ts` implements both backends: the sidecar HTTP/JSON gateway
54
+ (`PI402_WALLETD_GATEWAY`) and the embedded WASM bridge. Both run
55
+ `prepareSend` → open settle stream → `send`/`sendPrepared` → read preimage,
56
+ which arrives on the settled entry, so the real payer path is live.
57
+ - Run with `PI402_MOCK_PAYER=1` to exercise the loop without spending sats.