pywrit 0.2.3__tar.gz → 0.2.5__tar.gz
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.
- pywrit-0.2.5/PKG-INFO +216 -0
- pywrit-0.2.5/README.md +194 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/pyproject.toml +10 -1
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit/__init__.py +1 -1
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit/cli.py +497 -3
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit/client.py +1 -1
- pywrit-0.2.5/src/pywrit.egg-info/PKG-INFO +216 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit.egg-info/SOURCES.txt +2 -1
- pywrit-0.2.5/src/pywrit.egg-info/requires.txt +12 -0
- pywrit-0.2.5/tests/test_scan_tsjs.py +243 -0
- pywrit-0.2.3/PKG-INFO +0 -97
- pywrit-0.2.3/README.md +0 -83
- pywrit-0.2.3/src/pywrit.egg-info/PKG-INFO +0 -97
- pywrit-0.2.3/src/pywrit.egg-info/requires.txt +0 -1
- {pywrit-0.2.3 → pywrit-0.2.5}/LICENSE +0 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/setup.cfg +0 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit/errors.py +0 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit.egg-info/dependency_links.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit.egg-info/entry_points.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/src/pywrit.egg-info/top_level.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.5}/tests/test_client.py +0 -0
pywrit-0.2.5/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pywrit
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Python client + CLI for Writ — the gate before the write
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/AvenueDAdmin/pywrit
|
|
7
|
+
Project-URL: Documentation, https://withwrit.com/docs
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Provides-Extra: polyglot
|
|
13
|
+
Requires-Dist: tree-sitter>=0.23; extra == "polyglot"
|
|
14
|
+
Requires-Dist: tree-sitter-typescript>=0.23; extra == "polyglot"
|
|
15
|
+
Requires-Dist: tree-sitter-javascript>=0.23; extra == "polyglot"
|
|
16
|
+
Provides-Extra: keywords
|
|
17
|
+
Requires-Dist: ai-agents; extra == "keywords"
|
|
18
|
+
Requires-Dist: policy; extra == "keywords"
|
|
19
|
+
Requires-Dist: audit-log; extra == "keywords"
|
|
20
|
+
Requires-Dist: guardrails; extra == "keywords"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# pywrit
|
|
24
|
+
|
|
25
|
+
**Find the dangerous writes in your Python agent's code, then gate it.** `pywrit` is the Python
|
|
26
|
+
client and `writ` CLI for [Writ](https://withwrit.com): an allow/deny gate that sits
|
|
27
|
+
in front of your agent's consequential writes (database, HTTP, files, email, queues,
|
|
28
|
+
AWS) and records a hash-chained receipt for every decision.
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
`writ scan` is **local, deterministic, and free**: it parses your code with Python's
|
|
33
|
+
`ast` module, makes no network calls, and needs no API key.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install pywrit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Python 3.9+. Installs the `writ` command and the `pywrit` Python client.
|
|
42
|
+
|
|
43
|
+
## 60-second quickstart: scan → apply → gate
|
|
44
|
+
|
|
45
|
+
**1. Scan.** See which functions write, and how many of them are gated.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
writ scan .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
writ scan: /path/to/support-agent
|
|
53
|
+
files scanned: 4 skipped: 0
|
|
54
|
+
write sites: 4 in 3 function(s)
|
|
55
|
+
gated: 0/4 (0%)
|
|
56
|
+
verbs discovered: 3
|
|
57
|
+
crm.update
|
|
58
|
+
payments.refund
|
|
59
|
+
tickets.close
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
It also prints a **risk report** (0-100, weighted by risk tier), writes the discovered
|
|
63
|
+
verbs to `writ-policy.json`, and shows the instrumentation it would add as a unified
|
|
64
|
+
diff. Nothing in your code changes yet. `writ scan . --score` prints just the risk report.
|
|
65
|
+
|
|
66
|
+
**2. Apply.** Insert a gate at the top of each writing function.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
writ scan . --apply # shows the diff, then asks before writing
|
|
70
|
+
writ scan . --apply --yes # no prompt (e.g. in CI)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Each gated function now asks Writ before it writes, and fails closed:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
def issue_refund(charge_id, amount_cents):
|
|
77
|
+
if _writ_check("payments.refund") != "ALLOW":
|
|
78
|
+
raise PermissionError("writ denied payments.refund")
|
|
79
|
+
...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Re-run `writ scan .` and you'll see `gated: 4/4 (100%)`.
|
|
83
|
+
|
|
84
|
+
**3. Gate.** Get a free API key, load the discovered policy, and run your agent.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
writ key --email you@example.com # free API key + tenant
|
|
88
|
+
export WRIT_API_KEY=writ_... # read by the inserted gate
|
|
89
|
+
export WRIT_SPONSOR=acme WRIT_AGENT=support-agent # optional: who is acting
|
|
90
|
+
writ scan . --push-policy --key "$WRIT_API_KEY" # upload the discovered verb policy
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Every gated write now gets `ALLOW`, `DENY`, or `STEP_UP` (a human sponsor must
|
|
94
|
+
approve), and every decision is written to your tenant's tamper-evident audit log:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
writ receipts --key "$WRIT_API_KEY" # latest receipts
|
|
98
|
+
writ verify-chain --key "$WRIT_API_KEY" # verify the receipt hash chain
|
|
99
|
+
writ stream --key "$WRIT_API_KEY" # tail decisions live
|
|
100
|
+
writ report --key "$WRIT_API_KEY" # Agent Action Report
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## What `writ scan` detects
|
|
104
|
+
|
|
105
|
+
Python (`.py`) files, parsed with the stdlib `ast` (no extra dependencies):
|
|
106
|
+
|
|
107
|
+
| Category | Examples |
|
|
108
|
+
|---|---|
|
|
109
|
+
| Database | `cursor.execute(...)` / `executemany` / `executescript` (write SQL only; `SELECT`/`WITH`/... skipped), `session.add` / `commit` / `delete` / `merge` / `flush` |
|
|
110
|
+
| HTTP | `requests.post` / `put` / `patch`, `client.delete(...)`, `session.request(...)` |
|
|
111
|
+
| Files | `open(..., "w"/"a"/"x"/"+")`, `Path.write_text` / `write_bytes` / `unlink` / `rename`, `os.remove` / `rename` / `makedirs`, `shutil.rmtree` / `move` / `copy` |
|
|
112
|
+
| Email | `sendmail`, `send_message`, `send_email` |
|
|
113
|
+
| Queues | `publish`, `produce`, `enqueue`, `queue.send(...)` |
|
|
114
|
+
| AWS SDK | `put_object`, `put_item`, `delete_item`, `upload_file`, `send_message`, `start_execution`, ... |
|
|
115
|
+
|
|
116
|
+
- Each write is mapped to a verb such as `payments.refund` or `crm.update`, inferred
|
|
117
|
+
from the file path and function name.
|
|
118
|
+
- A function that already calls `writ_check(...)` / `_writ_check(...)` counts as gated.
|
|
119
|
+
- Skipped: tests, hidden directories, virtualenvs, `node_modules`, `dist`, `build`.
|
|
120
|
+
Use `--exclude SUBSTR` (repeatable) to skip more.
|
|
121
|
+
- Not covered (review by hand): writes behind dynamically built SQL, third-party SDK
|
|
122
|
+
calls such as `stripe.Refund.create(...)`, deferred task queues, or shared clients
|
|
123
|
+
several layers down. The risk report lists these gaps every time.
|
|
124
|
+
|
|
125
|
+
### TypeScript / JavaScript (scan-only)
|
|
126
|
+
|
|
127
|
+
`writ scan` also finds write sites in TypeScript and JavaScript. It needs the
|
|
128
|
+
optional extra (tree-sitter based):
|
|
129
|
+
|
|
130
|
+
pip install 'pywrit[polyglot]'
|
|
131
|
+
writ scan .
|
|
132
|
+
|
|
133
|
+
Without the extra, the scanner prints a one-line hint and keeps going —
|
|
134
|
+
Python scanning never needs it.
|
|
135
|
+
|
|
136
|
+
TS/JS is **scan-only**: findings are listed with verbs for your policy file,
|
|
137
|
+
but `writ scan --apply` never rewrites TS/JS files — gate those by hand.
|
|
138
|
+
Covered patterns: `fetch`/`axios` writes, `fs` writes, SQL through knex-style
|
|
139
|
+
clients, Prisma writes, and JS SDK calls such as `stripe.refunds.create(...)`.
|
|
140
|
+
(Still not covered: the *Python* SDK equivalents like `stripe.Refund.create(...)`
|
|
141
|
+
— see "Not covered" above.)
|
|
142
|
+
|
|
143
|
+

|
|
144
|
+
|
|
145
|
+
## Python client
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from pywrit import WritClient
|
|
149
|
+
|
|
150
|
+
client = WritClient(api_key="writ_...")
|
|
151
|
+
|
|
152
|
+
result = client.check(
|
|
153
|
+
sponsor_id="acme",
|
|
154
|
+
agent_id="agent-7",
|
|
155
|
+
verb="db.write",
|
|
156
|
+
target="prod.customers",
|
|
157
|
+
purpose="backfill region field",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if result.decision == "ALLOW":
|
|
161
|
+
# result.auth_token is a short-lived token bound to this exact write
|
|
162
|
+
perform_write(...)
|
|
163
|
+
elif result.decision == "STEP_UP":
|
|
164
|
+
# a human sponsor must approve first: client.grant(...), then re-check
|
|
165
|
+
...
|
|
166
|
+
else:
|
|
167
|
+
# DENY
|
|
168
|
+
...
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
No API key yet? Try the keyless sandbox:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
client = WritClient()
|
|
175
|
+
client.sandbox({
|
|
176
|
+
"sponsorId": "acme",
|
|
177
|
+
"agentId": "agent-7",
|
|
178
|
+
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
179
|
+
"target": "demo-customers", # ... on targets starting with demo-
|
|
180
|
+
"purpose": "trying the gate",
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
What's covered:
|
|
185
|
+
|
|
186
|
+
- `check(...)`: the gate. `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
187
|
+
- `verify_token(...)`: validate an `ALLOW` auth token (catches purpose drift)
|
|
188
|
+
- `grant(...)`: human-sponsor approval for the `STEP_UP` path
|
|
189
|
+
- `get_policy()` / `set_policy(...)`: manage the tenant policy
|
|
190
|
+
- `revoke(...)` / `reinstate(...)` / `revoked()`: the kill switch
|
|
191
|
+
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()`: the audit log
|
|
192
|
+
- `sandbox(...)`: keyless trial, no API key required
|
|
193
|
+
|
|
194
|
+
## CLI reference
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
198
|
+
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
199
|
+
writ policy --key writ_... --set payments.refund require_grant
|
|
200
|
+
writ revoke --sponsor acme --agent agent-7 --reason "runaway loop" # kill switch (sponsor token)
|
|
201
|
+
writ grant --sponsor acme --agent agent-7 --verb payments.refund \
|
|
202
|
+
--target ch_123 --purpose "approved refund" # STEP_UP approval (sponsor token)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Sponsor-token commands (`revoke`, `reinstate`, `revoked`, `grant`) read
|
|
206
|
+
`--sponsor-token` or `WRIT_SPONSOR_TOKEN`. Run `writ --help` for the full command list.
|
|
207
|
+
|
|
208
|
+
## Docs
|
|
209
|
+
|
|
210
|
+
Full docs: [docs.withwrit.com](https://docs.withwrit.com) ·
|
|
211
|
+
Quickstart: [docs.withwrit.com/quickstart](https://docs.withwrit.com/quickstart) ·
|
|
212
|
+
Site: [withwrit.com](https://withwrit.com)
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
pywrit-0.2.5/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# pywrit
|
|
2
|
+
|
|
3
|
+
**Find the dangerous writes in your Python agent's code, then gate it.** `pywrit` is the Python
|
|
4
|
+
client and `writ` CLI for [Writ](https://withwrit.com): an allow/deny gate that sits
|
|
5
|
+
in front of your agent's consequential writes (database, HTTP, files, email, queues,
|
|
6
|
+
AWS) and records a hash-chained receipt for every decision.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
`writ scan` is **local, deterministic, and free**: it parses your code with Python's
|
|
11
|
+
`ast` module, makes no network calls, and needs no API key.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install pywrit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.9+. Installs the `writ` command and the `pywrit` Python client.
|
|
20
|
+
|
|
21
|
+
## 60-second quickstart: scan → apply → gate
|
|
22
|
+
|
|
23
|
+
**1. Scan.** See which functions write, and how many of them are gated.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
writ scan .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
writ scan: /path/to/support-agent
|
|
31
|
+
files scanned: 4 skipped: 0
|
|
32
|
+
write sites: 4 in 3 function(s)
|
|
33
|
+
gated: 0/4 (0%)
|
|
34
|
+
verbs discovered: 3
|
|
35
|
+
crm.update
|
|
36
|
+
payments.refund
|
|
37
|
+
tickets.close
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
It also prints a **risk report** (0-100, weighted by risk tier), writes the discovered
|
|
41
|
+
verbs to `writ-policy.json`, and shows the instrumentation it would add as a unified
|
|
42
|
+
diff. Nothing in your code changes yet. `writ scan . --score` prints just the risk report.
|
|
43
|
+
|
|
44
|
+
**2. Apply.** Insert a gate at the top of each writing function.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
writ scan . --apply # shows the diff, then asks before writing
|
|
48
|
+
writ scan . --apply --yes # no prompt (e.g. in CI)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Each gated function now asks Writ before it writes, and fails closed:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
def issue_refund(charge_id, amount_cents):
|
|
55
|
+
if _writ_check("payments.refund") != "ALLOW":
|
|
56
|
+
raise PermissionError("writ denied payments.refund")
|
|
57
|
+
...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Re-run `writ scan .` and you'll see `gated: 4/4 (100%)`.
|
|
61
|
+
|
|
62
|
+
**3. Gate.** Get a free API key, load the discovered policy, and run your agent.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
writ key --email you@example.com # free API key + tenant
|
|
66
|
+
export WRIT_API_KEY=writ_... # read by the inserted gate
|
|
67
|
+
export WRIT_SPONSOR=acme WRIT_AGENT=support-agent # optional: who is acting
|
|
68
|
+
writ scan . --push-policy --key "$WRIT_API_KEY" # upload the discovered verb policy
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every gated write now gets `ALLOW`, `DENY`, or `STEP_UP` (a human sponsor must
|
|
72
|
+
approve), and every decision is written to your tenant's tamper-evident audit log:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
writ receipts --key "$WRIT_API_KEY" # latest receipts
|
|
76
|
+
writ verify-chain --key "$WRIT_API_KEY" # verify the receipt hash chain
|
|
77
|
+
writ stream --key "$WRIT_API_KEY" # tail decisions live
|
|
78
|
+
writ report --key "$WRIT_API_KEY" # Agent Action Report
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## What `writ scan` detects
|
|
82
|
+
|
|
83
|
+
Python (`.py`) files, parsed with the stdlib `ast` (no extra dependencies):
|
|
84
|
+
|
|
85
|
+
| Category | Examples |
|
|
86
|
+
|---|---|
|
|
87
|
+
| Database | `cursor.execute(...)` / `executemany` / `executescript` (write SQL only; `SELECT`/`WITH`/... skipped), `session.add` / `commit` / `delete` / `merge` / `flush` |
|
|
88
|
+
| HTTP | `requests.post` / `put` / `patch`, `client.delete(...)`, `session.request(...)` |
|
|
89
|
+
| Files | `open(..., "w"/"a"/"x"/"+")`, `Path.write_text` / `write_bytes` / `unlink` / `rename`, `os.remove` / `rename` / `makedirs`, `shutil.rmtree` / `move` / `copy` |
|
|
90
|
+
| Email | `sendmail`, `send_message`, `send_email` |
|
|
91
|
+
| Queues | `publish`, `produce`, `enqueue`, `queue.send(...)` |
|
|
92
|
+
| AWS SDK | `put_object`, `put_item`, `delete_item`, `upload_file`, `send_message`, `start_execution`, ... |
|
|
93
|
+
|
|
94
|
+
- Each write is mapped to a verb such as `payments.refund` or `crm.update`, inferred
|
|
95
|
+
from the file path and function name.
|
|
96
|
+
- A function that already calls `writ_check(...)` / `_writ_check(...)` counts as gated.
|
|
97
|
+
- Skipped: tests, hidden directories, virtualenvs, `node_modules`, `dist`, `build`.
|
|
98
|
+
Use `--exclude SUBSTR` (repeatable) to skip more.
|
|
99
|
+
- Not covered (review by hand): writes behind dynamically built SQL, third-party SDK
|
|
100
|
+
calls such as `stripe.Refund.create(...)`, deferred task queues, or shared clients
|
|
101
|
+
several layers down. The risk report lists these gaps every time.
|
|
102
|
+
|
|
103
|
+
### TypeScript / JavaScript (scan-only)
|
|
104
|
+
|
|
105
|
+
`writ scan` also finds write sites in TypeScript and JavaScript. It needs the
|
|
106
|
+
optional extra (tree-sitter based):
|
|
107
|
+
|
|
108
|
+
pip install 'pywrit[polyglot]'
|
|
109
|
+
writ scan .
|
|
110
|
+
|
|
111
|
+
Without the extra, the scanner prints a one-line hint and keeps going —
|
|
112
|
+
Python scanning never needs it.
|
|
113
|
+
|
|
114
|
+
TS/JS is **scan-only**: findings are listed with verbs for your policy file,
|
|
115
|
+
but `writ scan --apply` never rewrites TS/JS files — gate those by hand.
|
|
116
|
+
Covered patterns: `fetch`/`axios` writes, `fs` writes, SQL through knex-style
|
|
117
|
+
clients, Prisma writes, and JS SDK calls such as `stripe.refunds.create(...)`.
|
|
118
|
+
(Still not covered: the *Python* SDK equivalents like `stripe.Refund.create(...)`
|
|
119
|
+
— see "Not covered" above.)
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
## Python client
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pywrit import WritClient
|
|
127
|
+
|
|
128
|
+
client = WritClient(api_key="writ_...")
|
|
129
|
+
|
|
130
|
+
result = client.check(
|
|
131
|
+
sponsor_id="acme",
|
|
132
|
+
agent_id="agent-7",
|
|
133
|
+
verb="db.write",
|
|
134
|
+
target="prod.customers",
|
|
135
|
+
purpose="backfill region field",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if result.decision == "ALLOW":
|
|
139
|
+
# result.auth_token is a short-lived token bound to this exact write
|
|
140
|
+
perform_write(...)
|
|
141
|
+
elif result.decision == "STEP_UP":
|
|
142
|
+
# a human sponsor must approve first: client.grant(...), then re-check
|
|
143
|
+
...
|
|
144
|
+
else:
|
|
145
|
+
# DENY
|
|
146
|
+
...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
No API key yet? Try the keyless sandbox:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
client = WritClient()
|
|
153
|
+
client.sandbox({
|
|
154
|
+
"sponsorId": "acme",
|
|
155
|
+
"agentId": "agent-7",
|
|
156
|
+
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
157
|
+
"target": "demo-customers", # ... on targets starting with demo-
|
|
158
|
+
"purpose": "trying the gate",
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
What's covered:
|
|
163
|
+
|
|
164
|
+
- `check(...)`: the gate. `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
165
|
+
- `verify_token(...)`: validate an `ALLOW` auth token (catches purpose drift)
|
|
166
|
+
- `grant(...)`: human-sponsor approval for the `STEP_UP` path
|
|
167
|
+
- `get_policy()` / `set_policy(...)`: manage the tenant policy
|
|
168
|
+
- `revoke(...)` / `reinstate(...)` / `revoked()`: the kill switch
|
|
169
|
+
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()`: the audit log
|
|
170
|
+
- `sandbox(...)`: keyless trial, no API key required
|
|
171
|
+
|
|
172
|
+
## CLI reference
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
176
|
+
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
177
|
+
writ policy --key writ_... --set payments.refund require_grant
|
|
178
|
+
writ revoke --sponsor acme --agent agent-7 --reason "runaway loop" # kill switch (sponsor token)
|
|
179
|
+
writ grant --sponsor acme --agent agent-7 --verb payments.refund \
|
|
180
|
+
--target ch_123 --purpose "approved refund" # STEP_UP approval (sponsor token)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Sponsor-token commands (`revoke`, `reinstate`, `revoked`, `grant`) read
|
|
184
|
+
`--sponsor-token` or `WRIT_SPONSOR_TOKEN`. Run `writ --help` for the full command list.
|
|
185
|
+
|
|
186
|
+
## Docs
|
|
187
|
+
|
|
188
|
+
Full docs: [docs.withwrit.com](https://docs.withwrit.com) ·
|
|
189
|
+
Quickstart: [docs.withwrit.com/quickstart](https://docs.withwrit.com/quickstart) ·
|
|
190
|
+
Site: [withwrit.com](https://withwrit.com)
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -4,12 +4,21 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pywrit"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.5"
|
|
8
8
|
description = "Python client + CLI for Writ — the gate before the write"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.9"
|
|
12
12
|
dependencies = ["httpx>=0.27"]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
# `pip install 'pywrit[polyglot]'`: lets `writ scan` parse TypeScript and
|
|
16
|
+
# JavaScript (scan only). Python scanning never needs it.
|
|
17
|
+
polyglot = [
|
|
18
|
+
"tree-sitter>=0.23",
|
|
19
|
+
"tree-sitter-typescript>=0.23",
|
|
20
|
+
"tree-sitter-javascript>=0.23",
|
|
21
|
+
]
|
|
13
22
|
keywords = ["ai-agents", "policy", "audit-log", "guardrails"]
|
|
14
23
|
|
|
15
24
|
[project.urls]
|