@connortessaro/pai 0.4.3 → 0.5.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/README.md +108 -69
- package/dist/pai.mjs +402 -44
- package/package.json +7 -7
- package/skills/phantom-ai/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
17
|
<a href="https://github.com/connortessaro/pai/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/connortessaro/pai/ci.yml?branch=main&label=CI&color=161b22&labelColor=0d1117" alt="CI"></a>
|
|
18
|
-
<a href="package
|
|
18
|
+
<a href="https://www.npmjs.com/package/@connortessaro/pai"><img src="https://img.shields.io/npm/v/@connortessaro/pai?label=version&color=161b22&labelColor=0d1117" alt="npm version"></a>
|
|
19
19
|
<a href="package.json"><img src="https://img.shields.io/badge/node-%3E%3D24-161b22?labelColor=0d1117" alt="Node >= 24"></a>
|
|
20
|
-
<a href="docs/reference.md#mcp-tools"><img src="https://img.shields.io/badge/MCP-
|
|
20
|
+
<a href="docs/reference.md#mcp-tools"><img src="https://img.shields.io/badge/MCP-server-161b22?labelColor=0d1117" alt="MCP Compatible"></a>
|
|
21
21
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-161b22?labelColor=0d1117" alt="MIT License"></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
@@ -28,25 +28,29 @@
|
|
|
28
28
|
<a href="#features">Features</a> •
|
|
29
29
|
<a href="#commands">Commands</a> •
|
|
30
30
|
<a href="#use-with-an-mcp-client">MCP Server</a> •
|
|
31
|
-
<a href="
|
|
31
|
+
<a href="https://connortessaro.github.io/pai/">Docs</a>
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
36
|
`pai` runs on your machine and manages a [Phantom AI](https://phantom.codes) key.
|
|
37
|
-
Your agent can
|
|
38
|
-
|
|
37
|
+
Your agent can give each subagent a child key with its own spending limit, lifetime, and rate cap.
|
|
38
|
+
`pai` also sets plans and model routing, buys credit from an agent wallet, and checks which model answered a call.
|
|
39
39
|
|
|
40
|
-
`pi`, `Claude Code`, `Codex`, and `Cursor` drive it through a skill
|
|
40
|
+
`pi`, `Claude Code`, `Codex`, and `Cursor` drive it through a skill. Any MCP client can use `pai mcp`.
|
|
41
41
|
|
|
42
42
|
The CLI is MIT licensed. The Phantom AI API it calls is a hosted service.
|
|
43
43
|
|
|
44
|
+
**Docs:** [connortessaro.github.io/pai](https://connortessaro.github.io/pai/) has guides to how
|
|
45
|
+
keys, payments, caps and the local tools behave, the full interface reference (commands,
|
|
46
|
+
flags, MCP tools, HTTP calls, environment variables), and the code reference.
|
|
47
|
+
|
|
44
48
|
## How It Works
|
|
45
49
|
|
|
46
50
|
```text
|
|
47
51
|
┌────────────────────────┐
|
|
48
52
|
│ Main Phantom Key │
|
|
49
|
-
│
|
|
53
|
+
│ (prepaid, no account) │
|
|
50
54
|
└───────────┬────────────┘
|
|
51
55
|
│
|
|
52
56
|
┌───────────────────────┼───────────────────────┐
|
|
@@ -62,18 +66,18 @@ The CLI is MIT licensed. The Phantom AI API it calls is a hosted service.
|
|
|
62
66
|
└───────────┬───────────┘
|
|
63
67
|
▼
|
|
64
68
|
┌───────────────────────┐
|
|
65
|
-
│
|
|
66
|
-
│
|
|
69
|
+
│ Phantom AI API │ ──> https://phantom.codes/v1
|
|
70
|
+
│ (stores no prompts) │ ──> signed receipt per call
|
|
67
71
|
└───────────────────────┘
|
|
68
72
|
```
|
|
69
73
|
|
|
70
74
|
## Features
|
|
71
75
|
|
|
72
|
-
- **
|
|
73
|
-
- **
|
|
74
|
-
- **
|
|
75
|
-
- **
|
|
76
|
-
- **Local
|
|
76
|
+
- **Child keys.** `pai child` creates a key that spends the parent's balance up to a limit, with a lifetime and a per-minute cap. `pai burn` revokes one.
|
|
77
|
+
- **Agent wallet.** `pai wallet` keeps Solana wallets on this machine, so an agent can buy its own credit. `PHANTOM_WALLET_MAX_USD` caps each payment, and only the environment can set it.
|
|
78
|
+
- **Skill and MCP server.** `pai setup` installs a skill for `pi`, `Claude Code`, `Codex`, and `Cursor`. `pai mcp` runs an MCP server over stdio.
|
|
79
|
+
- **Signed receipts.** Phantom AI signs each paid response with the model that answered, the token counts, and the cost. `pai verify` checks that signature.
|
|
80
|
+
- **Local tools.** Markdown notes, throwaway Docker containers, and a mail client that drafts by default. None of them send data to Phantom AI.
|
|
77
81
|
|
|
78
82
|
## Install
|
|
79
83
|
|
|
@@ -88,32 +92,32 @@ The package also installs the same CLI as `phantom-key`, its earlier name.
|
|
|
88
92
|
## Quickstart
|
|
89
93
|
|
|
90
94
|
```bash
|
|
91
|
-
# 1.
|
|
92
|
-
pai login # paste your key
|
|
93
|
-
pai setup #
|
|
95
|
+
# 1. Save your key once, and install the skill for the agents you have
|
|
96
|
+
pai login # paste your key; pai saves it (see Saved secrets below)
|
|
97
|
+
pai setup # sets up pi, Claude Code, Codex, and Cursor, where found
|
|
94
98
|
|
|
95
|
-
# 2. Check balance and
|
|
99
|
+
# 2. Check the balance and any caps
|
|
96
100
|
pai balance
|
|
97
101
|
|
|
98
|
-
# 3.
|
|
102
|
+
# 3. Create a child key for a subagent: $0.50, 6 hours, $0.10 a minute
|
|
99
103
|
pai child --limit 0.50 --ttl 6 --rate 0.10 --save researcher
|
|
100
104
|
|
|
101
|
-
# 4.
|
|
105
|
+
# 4. Start the subagent as that child
|
|
102
106
|
PHANTOM_KEY_NAME=researcher claude
|
|
103
107
|
```
|
|
104
108
|
|
|
105
109
|
## Use It From an Agent
|
|
106
110
|
|
|
107
111
|
`pai setup` installs the `phantom-ai` skill for each agent it finds in your
|
|
108
|
-
home
|
|
109
|
-
plain words, for example:
|
|
112
|
+
home folder (`pi`, `Claude Code`, `Codex`, `Cursor`). Then you can ask your
|
|
113
|
+
agent in plain words, for example:
|
|
110
114
|
|
|
111
115
|
> *"Give a subagent $0.50 for 6 hours with a $0.10/min rate cap."*
|
|
112
116
|
|
|
113
117
|
- `--agent pi` (or `claude`, `codex`, `cursor`) sets up one agent.
|
|
114
|
-
- `--mcp` also
|
|
115
|
-
- `--agent claude --provider`
|
|
116
|
-
- `pi` has no MCP and uses the CLI through the skill
|
|
118
|
+
- `--mcp` also adds the MCP server to Claude Code, Codex, and Cursor.
|
|
119
|
+
- `--agent claude --provider` sends Claude Code's own model calls to Phantom AI. It sets `ANTHROPIC_BASE_URL`, turns on tool search, and gives Claude Code the key through `apiKeyHelper` (`pai key show`), so the key stays out of `settings.json`. Add `--model auto` or any model id. `--provider off` undoes it.
|
|
120
|
+
- `pi` has no MCP support and uses the CLI through the skill. Install it there with:
|
|
117
121
|
```bash
|
|
118
122
|
pi install npm:@connortessaro/pai
|
|
119
123
|
```
|
|
@@ -121,16 +125,17 @@ plain words, for example:
|
|
|
121
125
|
## Commands
|
|
122
126
|
|
|
123
127
|
`pai --help` prints every command and flag. [docs/reference.md](docs/reference.md)
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
lists every command with the flags it reads, every MCP tool with its inputs, every
|
|
129
|
+
call `pai` makes to Phantom AI, and the environment variables. `npm run docs`
|
|
130
|
+
writes that file from the code.
|
|
126
131
|
|
|
127
132
|
| Group | Commands | What they do |
|
|
128
133
|
| --- | --- | --- |
|
|
129
134
|
| **Setup** | `login`, `logout`, `setup` | Save your key, and install the skill for your agents |
|
|
130
135
|
| **Keys** | `balance`, `child`, `children`, `key`, `rotate`, `burn` | Show credit, create child keys paid from this key, save keys by name, and replace or revoke a key |
|
|
131
|
-
| **Caps and plans** | `budget`, `plan` | Set a monthly cap and a per-minute cap, or
|
|
136
|
+
| **Caps and plans** | `budget`, `plan` | Set a monthly cap and a per-minute cap, or an amount for a set period |
|
|
132
137
|
| **Routing** | `route` | Choose which models `model: "auto"` can run, and the rules that pick one |
|
|
133
|
-
| **Credit** | `buy`, `payment` | Buy credit with
|
|
138
|
+
| **Credit** | `buy`, `payment` | Buy credit with USDC, USDT, or SOL on Solana |
|
|
134
139
|
| **Agent wallet** | `wallet`, `autotopup`, `buy --pay` | Keep Solana wallets and pay for credit from them. See [Let the agent pay](#let-the-agent-pay) |
|
|
135
140
|
| **Receipts** | `verify` | Check which model answered a call, from its signed receipt |
|
|
136
141
|
| **Local tools** | `memory`, `mail`, `browser`, `sandbox` | See the sections below |
|
|
@@ -142,7 +147,7 @@ as `PHANTOM_API_KEY`, then as the `login` key. A subagent started with
|
|
|
142
147
|
|
|
143
148
|
## Memory
|
|
144
149
|
|
|
145
|
-
Each agent gets
|
|
150
|
+
Each agent gets its own notebook on this machine. Notes are markdown files in
|
|
146
151
|
`~/.config/phantom-key/memory/<space>/`, so you can read, edit, or commit them.
|
|
147
152
|
`pai` sends none of them anywhere.
|
|
148
153
|
|
|
@@ -158,18 +163,24 @@ key name (`PHANTOM_KEY_NAME`), then `main`, so each subagent keeps its own.
|
|
|
158
163
|
|
|
159
164
|
## Mail
|
|
160
165
|
|
|
161
|
-
`pai` reads your own mailbox over IMAP and
|
|
162
|
-
password
|
|
163
|
-
iCloud, and Fastmail addresses have presets
|
|
164
|
-
`--imap host:port --smtp host:port`.
|
|
166
|
+
`pai` reads your own mailbox over IMAP and sends over SMTP, with an app
|
|
167
|
+
password. It saves the login as described in [Saved secrets](#saved-secrets).
|
|
168
|
+
Gmail, Outlook, iCloud, and Fastmail addresses have presets. For any other
|
|
169
|
+
provider, pass `--imap host:port --smtp host:port`.
|
|
165
170
|
|
|
166
171
|
| Command | What it does |
|
|
167
172
|
| --- | --- |
|
|
168
|
-
| `mail setup --user you@gmail.com` | Checks the login by listing one message, then saves it.
|
|
173
|
+
| `mail setup --user you@gmail.com` | Checks the login by listing one message, then saves it. `pai` does not save a wrong password. The password comes from a prompt or `PAI_MAIL_PASSWORD` |
|
|
169
174
|
| `mail status` | Shows the mailbox and whether sending is on |
|
|
170
175
|
| `mail list [--unread] [--from x] [--limit n] [--folder f]` / `mail search <words>` / `mail read <uid>` | Reads mail from `INBOX` by default. `list` returns 20 messages by default. `read` cuts a message at 20,000 characters |
|
|
171
|
-
| `mail draft --to a --subject s [--reply <uid>] [--body "..."]` | Saves a draft in your Drafts folder and sends nothing. Without `--body`, pai reads the body from stdin |
|
|
172
|
-
| `mail send` (same flags) | Sends only when `PAI_MAIL_SEND=1`, to at most `PAI_MAIL_MAX_PER_DAY` recipients (default 10) in 24 hours, and only to `PAI_MAIL_SEND_TO` (addresses or `@domain`s) when set
|
|
176
|
+
| `mail draft --to a --subject s [--reply <uid>] [--body "..."]` | Saves a draft in your Drafts folder and sends nothing. Without `--body`, `pai` reads the body from stdin |
|
|
177
|
+
| `mail send` (same flags) | Sends only when `PAI_MAIL_SEND=1`, to at most `PAI_MAIL_MAX_PER_DAY` recipients (default 10) in 24 hours, and only to `PAI_MAIL_SEND_TO` (addresses or `@domain`s) when set |
|
|
178
|
+
|
|
179
|
+
`pai` reads the three `mail send` settings from the environment only, so an
|
|
180
|
+
agent can't turn sending on with a flag. While sending is on, the
|
|
181
|
+
`create_child_key` MCP tool requires `save_as`, which keeps a new key out of the
|
|
182
|
+
agent's context. On any port other than 993 or 465, `pai` requires STARTTLS
|
|
183
|
+
(switching the connection to encrypted) before it sends the password.
|
|
173
184
|
|
|
174
185
|
## Browser and Sandbox
|
|
175
186
|
|
|
@@ -184,9 +195,9 @@ iCloud, and Fastmail addresses have presets; for any other provider, pass
|
|
|
184
195
|
|
|
185
196
|
## Output Format
|
|
186
197
|
|
|
187
|
-
Commands print JSON on stdout, so you can pipe into `jq`. `--table` prints
|
|
188
|
-
a
|
|
189
|
-
command they ran.
|
|
198
|
+
Commands print JSON on stdout, so you can pipe them into `jq`. `--table` prints
|
|
199
|
+
a table for people to read. `browser` and `sandbox run` pass through the exit
|
|
200
|
+
code of the command they ran.
|
|
190
201
|
|
|
191
202
|
| Exit code | Meaning |
|
|
192
203
|
| --- | --- |
|
|
@@ -209,39 +220,47 @@ command they ran.
|
|
|
209
220
|
}
|
|
210
221
|
```
|
|
211
222
|
|
|
212
|
-
[docs/reference.md](docs/reference.md#mcp-tools) lists
|
|
213
|
-
inputs and
|
|
223
|
+
[docs/reference.md](docs/reference.md#mcp-tools) lists each tool with its
|
|
224
|
+
inputs, and marks the read-only and destructive ones.
|
|
214
225
|
|
|
215
|
-
`delete_key`
|
|
226
|
+
`delete_key` signs in as the key you pass in `api_key` or `key_name` and
|
|
216
227
|
deletes that key. It refuses the key the server runs as; use `pai burn` for
|
|
217
228
|
that.
|
|
218
229
|
|
|
219
230
|
## Let the Agent Pay
|
|
220
231
|
|
|
221
|
-
The agent can
|
|
222
|
-
is a Solana keypair in
|
|
223
|
-
pays Phantom AI's Solana address, and Phantom AI finds the payment
|
|
232
|
+
The agent can keep its own Solana wallets and buy credit with them. Each wallet
|
|
233
|
+
is a Solana keypair, saved as described in [Saved secrets](#saved-secrets).
|
|
234
|
+
The wallet pays Phantom AI's Solana address, and Phantom AI finds the payment
|
|
235
|
+
on chain.
|
|
224
236
|
|
|
225
|
-
1. **Create a wallet**, and back up its
|
|
237
|
+
1. **Create a wallet**, and back up its secret key:
|
|
226
238
|
```bash
|
|
227
239
|
pai wallet create --table # saved as "main"
|
|
228
240
|
pai wallet create --name work --table # a second one
|
|
229
241
|
```
|
|
230
|
-
|
|
242
|
+
On macOS the file in `~/.config/phantom-key/wallets/` only points to the
|
|
243
|
+
Keychain, so copying it backs up nothing. Export the key itself:
|
|
244
|
+
```bash
|
|
245
|
+
security find-generic-password -s pai -w \
|
|
246
|
+
-a "$(cut -d: -f2 ~/.config/phantom-key/wallets/main.json)" | base64 -d > main-backup.json
|
|
247
|
+
```
|
|
248
|
+
With `PAI_KEYCHAIN=0`, or on other systems, `wallets/main.json` holds the key, and a copy of it is the backup.
|
|
249
|
+
2. **Fund the address** with USDC on Solana, plus about 0.003 SOL for fees.
|
|
231
250
|
3. **Set the safety cap**:
|
|
232
251
|
```bash
|
|
233
252
|
export PHANTOM_WALLET_MAX_USD=10
|
|
234
253
|
```
|
|
235
|
-
Without this variable, `pai` refuses to pay.
|
|
236
|
-
|
|
237
|
-
|
|
254
|
+
Without this variable, `pai` refuses to pay. `PHANTOM_WALLET_MAX_USD_PER_DAY`
|
|
255
|
+
caps the total over 24 hours and defaults to the same amount, which allows
|
|
256
|
+
one payment a day. Raise it for `autotopup`.
|
|
238
257
|
4. **Pay**:
|
|
239
258
|
```bash
|
|
240
259
|
pai buy --amount 5 --pay
|
|
241
260
|
```
|
|
242
261
|
With more than one wallet saved, `pai` asks in a terminal which one pays. It
|
|
243
262
|
then reports each step:
|
|
244
|
-
1. Getting a payment request from Phantom AI (amount, recipient, and unique reference)
|
|
263
|
+
1. Getting a payment request from Phantom AI (amount, recipient, and a unique reference)
|
|
245
264
|
2. Sending USDC or SOL from your wallet
|
|
246
265
|
3. Confirming on Solana
|
|
247
266
|
4. Phantom AI finding the payment on chain by its reference
|
|
@@ -249,10 +268,10 @@ pays Phantom AI's Solana address, and Phantom AI finds the payment on chain.
|
|
|
249
268
|
|
|
250
269
|
A payment request expires after 30 minutes.
|
|
251
270
|
|
|
252
|
-
You can also
|
|
253
|
-
Over MCP it calls `list_wallets`, asks which wallet if
|
|
271
|
+
You can also ask your agent: *"Top up my Phantom AI key with $5 from my Phantom agent wallet."*
|
|
272
|
+
Over MCP it calls `list_wallets`, asks which wallet if you didn't say, then calls `pay_for_credit`.
|
|
254
273
|
|
|
255
|
-
To refill
|
|
274
|
+
To refill when the balance runs low:
|
|
256
275
|
|
|
257
276
|
```bash
|
|
258
277
|
pai autotopup --below 1 --amount 5 --wallet main --every 10
|
|
@@ -261,35 +280,55 @@ pai autotopup --below 1 --amount 5 --wallet main --every 10
|
|
|
261
280
|
### Safeguards
|
|
262
281
|
|
|
263
282
|
- `pai` pays the recipient and exact amount the payment request names, with that request's reference attached.
|
|
264
|
-
- `pai` reads `PHANTOM_WALLET_MAX_USD` and `PHANTOM_WALLET_MAX_USD_PER_DAY`
|
|
283
|
+
- `pai` reads `PHANTOM_WALLET_MAX_USD` and `PHANTOM_WALLET_MAX_USD_PER_DAY` from the environment only; no flag or tool argument can raise them.
|
|
265
284
|
- Before signing, `pai` checks the payment request against what it asked for: the coin, the USDC mint, and an amount no more than requested (for SOL, within 10% of Coinbase's price). A wrong or hostile `PHANTOM_BASE_URL` cannot make it sign more.
|
|
266
|
-
-
|
|
267
|
-
- `pai`
|
|
268
|
-
- `pai` records
|
|
269
|
-
- These limits bind an agent that reaches pai through MCP. An agent with its own shell can set environment variables and edit the state folder, so give such an agent a wallet
|
|
270
|
-
-
|
|
285
|
+
- `pai` runs one payment at a time, across processes.
|
|
286
|
+
- `pai` checks the wallet balance before it asks for a payment request.
|
|
287
|
+
- `pai` records each payment in `~/.config/phantom-key` before sending, and keeps the record until Phantom AI reports the payment credited or dead. If a run crashes, times out, or loses the connection, the next run waits for that payment instead of paying again.
|
|
288
|
+
- These limits bind an agent that reaches `pai` through MCP. An agent with its own shell can set environment variables and edit the state folder, so give such an agent a wallet that holds only what you are willing to let it spend.
|
|
289
|
+
- To keep a wallet's secret out of `pai`'s state folder, pass it at run time in `PHANTOM_WALLET_KEY`, for example from a local secrets manager such as [KRU](https://github.com/omaekumiko2-create/kru).
|
|
290
|
+
|
|
291
|
+
## Saved Secrets
|
|
292
|
+
|
|
293
|
+
`pai` keeps its files in `~/.config/phantom-key`, or in `PHANTOM_STATE_DIR` if
|
|
294
|
+
you set it. The paths in this README assume the default.
|
|
295
|
+
|
|
296
|
+
On macOS, `pai` puts saved keys, wallets, and the mail login in your login
|
|
297
|
+
Keychain. The file in the state folder then holds only a pointer to the
|
|
298
|
+
Keychain item. The Keychain keeps these secrets out of files, backups, and sync
|
|
299
|
+
folders. Any program running as you can still read them. If the Keychain is
|
|
300
|
+
locked, as it often is over SSH, run `security unlock-keychain`.
|
|
301
|
+
|
|
302
|
+
Set `PAI_KEYCHAIN=0` to save them to files instead. On other systems `pai`
|
|
303
|
+
uses files. Only your user can read them (mode 600).
|
|
271
304
|
|
|
272
305
|
## Configuration
|
|
273
306
|
|
|
274
307
|
`pai` reads its settings from environment variables. Run `pai --help` or see
|
|
275
|
-
[docs/reference.md
|
|
276
|
-
including secrets, environment-only
|
|
308
|
+
[docs/reference.md](docs/reference.md#environment-variables) for the full list,
|
|
309
|
+
including secrets, environment-only settings, and caps.
|
|
310
|
+
|
|
311
|
+
`PHANTOM_BASE_URL` must start with `https://`. `pai` sends your API key there,
|
|
312
|
+
so it refuses plain `http://`, even on localhost.
|
|
277
313
|
|
|
278
314
|
## Requirements
|
|
279
315
|
|
|
280
316
|
- **Node 24** or newer
|
|
281
|
-
- Runtime dependencies: `@modelcontextprotocol/sdk` and `zod` for `mcp`, `@solana/kit` for wallet payments, and `imapflow`, `mailparser`, `nodemailer` for `mail`.
|
|
317
|
+
- Runtime dependencies: `@modelcontextprotocol/sdk` and `zod` for `mcp`, `@solana/kit` for wallet payments, and `imapflow`, `mailparser`, and `nodemailer` for `mail`.
|
|
282
318
|
|
|
283
|
-
|
|
319
|
+
[phantom.codes/docs/concepts](https://phantom.codes/docs/concepts) explains how keys and child keys work on the Phantom AI side.
|
|
284
320
|
|
|
285
321
|
## Development
|
|
286
322
|
|
|
287
323
|
```bash
|
|
288
324
|
npm ci
|
|
289
|
-
npm
|
|
325
|
+
npm run build # compile src/ to dist/
|
|
290
326
|
npm run typecheck
|
|
291
|
-
npm
|
|
292
|
-
|
|
327
|
+
npm test # vitest; never touches the real Keychain
|
|
328
|
+
npm run coverage # vitest with coverage; fails under 80%
|
|
329
|
+
npm run docs # rewrite docs/reference.md and build the docs site in docs/site (not committed)
|
|
330
|
+
node scripts/gen-docs.mjs --check # fail if docs/reference.md is out of date or the docs disagree with the code
|
|
331
|
+
node src/pai.mts --help # run from source
|
|
293
332
|
```
|
|
294
333
|
|
|
295
334
|
## Contributing
|
|
@@ -299,7 +338,7 @@ command or MCP tool. [CHANGELOG.md](CHANGELOG.md) lists changes by release.
|
|
|
299
338
|
|
|
300
339
|
## Security
|
|
301
340
|
|
|
302
|
-
Report vulnerabilities
|
|
341
|
+
Report vulnerabilities through a private
|
|
303
342
|
[GitHub security advisory](https://github.com/connortessaro/pai/security/advisories/new).
|
|
304
343
|
See [SECURITY.md](SECURITY.md).
|
|
305
344
|
|