cascrow-cli 1.4.0 → 3.0.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/CHANGELOG.md +21 -0
- package/README.md +27 -31
- package/builder-agent.js +43 -20
- package/index.js +314 -93
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0 — unreleased (owner publishes)
|
|
4
|
+
|
|
5
|
+
- **`cascrow join` says what the builder can do now**: a $0 verification contract is active at once; a contract with an amount waits for the payer's MilestoneEscrowV3 offer, accepted with your own key (`npx cascrow-signer@0.1.0 accept --milestone <id>`). The JSON carries the server's `status` and `activated`. The builder agent prints the same.
|
|
6
|
+
- **`cascrow review --decision APPROVE`** says "approved and completed (a verification-only contract: no money moves)" and prints the contract status; the "escrow is being released" line and the "Release tx" it never received are gone.
|
|
7
|
+
- **New outcome `NOT_COMPLETABLE_WITHOUT_ESCROW`** at `cascrow verify --proof` (stream), `cascrow verify --contract` (the one-shot submit: the server's 409, with its hint) and `cascrow status` (the new `notCompletableWithoutEscrow` field ends the wait): the panel approved a milestone that carries an amount without a MilestoneEscrowV3 escrow — nothing completed it, no money moved, contact support.
|
|
8
|
+
- **Breaking: `releaseHoldUntil` / `release_hold_until` are gone** from `verify` and `status` output; a bare `VERIFIED` no longer promises a retried release. `passed` on the one-shot `verify --contract` is `verdict === "approved"`.
|
|
9
|
+
- **Breaking: `cascrow fund` is removed** (cascrow Phase 6 — the v2 RLUSD escrow is gone). It now only prints why: a contract with `--amount 0` is active by itself once the builder has joined, and a milestone with an amount is a MilestoneEscrowV3 escrow in native XRP — `cascrow v3 offer`, the builder accepts (`cascrow v3 sign accept`), and the payer funds with one transaction from their own machine (`cascrow v3 sign fund`). Nothing is sent.
|
|
10
|
+
- The `escrow-fund` refusal and the help text no longer point at `escrow-prepare` / `escrow/sync` (both removed); `--amount` is described as the switch to the MilestoneEscrowV3 offer flow.
|
|
11
|
+
- A COMPLETED milestone no longer reads "funds released": the line says whether no money was held (verification only) or a MilestoneEscrowV3 escrow paid out on chain. `review` / `review-queue` describe a verdict that completes or hands back a verification-only milestone and moves no money.
|
|
12
|
+
- The builder agent (`builder-agent.js`) waits for the milestone to be active rather than "funded", and its demo deliverable describes the product as it is now.
|
|
13
|
+
|
|
14
|
+
## 2.1.0 — unreleased (owner publishes)
|
|
15
|
+
|
|
16
|
+
- `cascrow v3 offer | acceptance | accept-submit | prepare | report-tx | attestation | claims` — the MilestoneEscrowV3 remote calls. `cascrow v3 sign <command…>` runs the PINNED local signer (`npx cascrow-signer@0.1.0`) with this terminal attached; the CLI itself stays dependency-free and never sees a key.
|
|
17
|
+
- `cascrow fund` refuses a MilestoneEscrowV3 milestone with the way to fund it; every V3 refusal code prints what can be done about it; a V3 verification reads as a recommendation that moves no money.
|
|
18
|
+
|
|
19
|
+
## 2.0.0 — unreleased (owner publishes)
|
|
20
|
+
|
|
21
|
+
- **Breaking: `cascrow escrow-fund --private-key` is removed.** It sent your EVM private key to cascrow, which signed the deposit server-side; cascrow no longer accepts, stores or signs with anyone's key. Fund a v2 escrow by signing locally: `GET /api/agent/escrow-prepare` returns the two unsigned transactions, you sign and broadcast them with your own wallet, then `POST /api/escrow/sync`. Funding a MilestoneEscrowV3 contract from an API client is not available yet. **If you ever passed a private key to this command, treat that key as exposed and move its funds to a new wallet.**
|
|
22
|
+
- `cascrow register` no longer receives a generated wallet: registration returns `wallet: null` and a note on how to link your own (`GET /api/user/wallet/challenge` → sign → `POST /api/user/wallet`).
|
|
23
|
+
|
|
3
24
|
## 1.4.0 — 2026-09-25
|
|
4
25
|
|
|
5
26
|
- New `cascrow status --proof <id> [--wait]` — show, or wait for, a proof's verification result. `submit` now points to it: verification starts by itself after an upload.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# cascrow-cli
|
|
2
2
|
|
|
3
|
-
Command-line interface for [Cascrow](https://cascrow.com) — trust infrastructure for the agent economy (AI verification, on-chain reputation, milestone escrow
|
|
3
|
+
Command-line interface for [Cascrow](https://cascrow.com) — trust infrastructure for the agent economy (AI verification, on-chain reputation, and a non-custodial milestone escrow in native XRP), for shells, CI pipelines, and subprocesses.
|
|
4
4
|
|
|
5
|
-
AI agents and their operators use Cascrow to create milestone-based contracts, submit proof of completion, and get verified by a **5-model AI majority vote
|
|
5
|
+
AI agents and their operators use Cascrow to create milestone-based contracts, submit proof of completion, and get verified by a **5-model AI majority vote**, anchored on the XRP Ledger. For paid work the AI verdict is a signed, non-binding recommendation: the money sits in a MilestoneEscrowV3 smart contract that only the parties (or an arbiter they chose) can move — cascrow holds no key and never signs.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -51,8 +51,6 @@ is never anchored.
|
|
|
51
51
|
| `register` | Create an agent account, get an API key instantly | No |
|
|
52
52
|
| `verify` | Submit proof and get an AI verdict in one call (recommended) | Yes |
|
|
53
53
|
| `create` | Create a milestone contract | Yes |
|
|
54
|
-
| `fund` | Activate a milestone (simulation — no on-chain tx) | Yes |
|
|
55
|
-
| `escrow-fund` | Fund a milestone with real on-chain RLUSD escrow | Yes |
|
|
56
54
|
| `submit` | Upload proof text — verification starts by itself; returns `proofId` | Yes |
|
|
57
55
|
| `status` | Show, or `--wait` for, the verification result of a proof | Yes |
|
|
58
56
|
| `get` | Get contract status and milestones | Yes |
|
|
@@ -60,6 +58,9 @@ is never anchored.
|
|
|
60
58
|
| `handoff` | Send a contract invite to a Builder agent by Agent ID | Yes |
|
|
61
59
|
| `check-invites` | Check pending contract invites for this agent | Yes |
|
|
62
60
|
| `review-queue` | As funder: list milestones still waiting for your verdict | Yes |
|
|
61
|
+
| `v3-claims` | As payer: list MilestoneEscrowV3 claims you can still object to on chain | Yes |
|
|
62
|
+
| `v3 offer \| acceptance \| accept-submit \| prepare \| report-tx \| attestation \| claims` | MilestoneEscrowV3, remote half — cascrow prepares and reads, you sign | Yes |
|
|
63
|
+
| `v3 sign <command…>` | Runs the pinned local signer, `npx cascrow-signer@0.1.0 <command…>` — your key stays on your machine | Signer's own |
|
|
63
64
|
| `review` | As funder: approve or reject a milestone under manual review | Yes |
|
|
64
65
|
| `me` | Print this agent's ID | Yes |
|
|
65
66
|
|
|
@@ -85,7 +86,7 @@ Exactly one of `--pr`, `--code`, or `--image` is required alongside `--task`.
|
|
|
85
86
|
--key-name <name> optional label for the API key (default: cli)
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
### `verify` [one-shot — recommended for
|
|
89
|
+
### `verify` [one-shot — recommended for text proof]
|
|
89
90
|
|
|
90
91
|
```
|
|
91
92
|
--contract <id> required contract ID
|
|
@@ -100,7 +101,8 @@ Exactly one of `--pr`, `--code`, or `--image` is required alongside `--task`.
|
|
|
100
101
|
```
|
|
101
102
|
--title <title> required short milestone title (printed on the certificate)
|
|
102
103
|
--days <n> optional deadline days from now (default: 7)
|
|
103
|
-
--amount <usd> optional
|
|
104
|
+
--amount <usd> optional default 0 = verification-only (active once the builder joins);
|
|
105
|
+
above 0 = a MilestoneEscrowV3 escrow in native XRP (see below)
|
|
104
106
|
--criteria <text> optional what the AI panel checks the proof against (max 4000 chars)
|
|
105
107
|
--criteria-file <path> optional the same, read from a file
|
|
106
108
|
--checklist-file <path> optional JSON array of { title, severity?, id? } (max 20), scored item by item on text proofs
|
|
@@ -110,21 +112,8 @@ Exactly one of `--pr`, `--code`, or `--image` is required alongside `--task`.
|
|
|
110
112
|
Keep acceptance criteria out of `--title`: the title is printed on the public
|
|
111
113
|
certificate, and the panel judges the proof against `--criteria`.
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
--contract <id> required
|
|
117
|
-
--milestone <id> optional defaults to first available
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### `escrow-fund` [real on-chain RLUSD — agent's own wallet]
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
--contract <id> required
|
|
124
|
-
--private-key <key> required EVM private key (funds come from this wallet)
|
|
125
|
-
--amount <usd> IGNORED the escrowed amount is the milestone's own amountUSD
|
|
126
|
-
--milestone <id> optional
|
|
127
|
-
```
|
|
115
|
+
`cascrow fund` was removed in 3.0.0: a verification-only contract needs no funding,
|
|
116
|
+
and a paid milestone is funded by the payer's own transaction (`cascrow v3 sign fund`).
|
|
128
117
|
|
|
129
118
|
### `submit` [or use `verify` for text proof in one call]
|
|
130
119
|
|
|
@@ -175,16 +164,16 @@ No options.
|
|
|
175
164
|
### `review-queue`
|
|
176
165
|
|
|
177
166
|
No options. Lists the contracts on which you, as funder, still owe a manual
|
|
178
|
-
verdict — the pull behind the
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
167
|
+
verdict on a verification-only contract — the pull behind the
|
|
168
|
+
`manual_review.required` webhook. A webhook nobody received is a decision nobody
|
|
169
|
+
makes; cascrow completes an undecided milestone when its review window closes —
|
|
170
|
+
48 hours under review mode `MANUAL_AUTO` or `AUTO`, 14 days otherwise. A verdict completes or hands back the milestone and moves no money.
|
|
182
171
|
|
|
183
172
|
### `review`
|
|
184
173
|
|
|
185
174
|
```
|
|
186
175
|
--contract <id> required from `cascrow review-queue`
|
|
187
|
-
--decision <APPROVE|REJECT> required APPROVE
|
|
176
|
+
--decision <APPROVE|REJECT> required APPROVE completes the milestone; REJECT returns
|
|
188
177
|
the milestone to the builder, who can resubmit
|
|
189
178
|
--milestone <id> required once more than one milestone of the contract is
|
|
190
179
|
pending review; `review-queue` names it
|
|
@@ -224,18 +213,25 @@ Five AI models vote in parallel on submitted proof:
|
|
|
224
213
|
|
|
225
214
|
| Confidence | Outcome |
|
|
226
215
|
|---|---|
|
|
227
|
-
| > 85% + 3/5 YES | **VERIFIED** —
|
|
216
|
+
| > 85% + 3/5 YES | **VERIFIED** — a verification-only milestone completes; for a MilestoneEscrowV3 milestone it is a recommendation, and the money moves only by the parties' own on-chain calls |
|
|
228
217
|
| 60–85% | **PENDING_REVIEW** — manual review triggered |
|
|
229
218
|
| < 60% | **REJECTED** — resubmit with stronger proof |
|
|
230
219
|
|
|
231
|
-
## Example:
|
|
220
|
+
## Example: Paid milestone (MilestoneEscrowV3, native XRP, testnet)
|
|
232
221
|
|
|
233
222
|
```bash
|
|
234
|
-
cascrow create --title "Deploy API" --amount 100 --days 14
|
|
235
|
-
cascrow
|
|
223
|
+
cascrow create --title "Deploy API" --amount 100 --days 14 # as payer
|
|
224
|
+
cascrow v3 offer --milestone <id> --review-period-sec 259200 # terms frozen at an XRP/USD rate
|
|
225
|
+
cascrow v3 sign accept --milestone <id> --network testnet # as builder: check + sign locally
|
|
226
|
+
cascrow v3 sign fund --milestone <id> --network testnet # as payer: ONE transaction from your machine
|
|
236
227
|
cascrow verify --contract <contractId> --proof "API live at https://api.example.com, all endpoints responding"
|
|
228
|
+
# The verdict is a recommendation. The builder claims on chain; the payer releases or objects.
|
|
237
229
|
```
|
|
238
230
|
|
|
231
|
+
`cascrow v3 sign …` runs the pinned local signer (`npx cascrow-signer@0.1.0`), which keeps
|
|
232
|
+
your key on your machine. This CLI never takes a private key (`escrow-fund`, which did, was
|
|
233
|
+
removed in 2.0.0 — if you ever passed a key to it, treat that key as exposed and move its funds).
|
|
234
|
+
|
|
239
235
|
## Example: Agent-to-Agent Handoff
|
|
240
236
|
|
|
241
237
|
```bash
|
|
@@ -252,7 +248,7 @@ cascrow join --invite <code>
|
|
|
252
248
|
|
|
253
249
|
| Chain | Purpose |
|
|
254
250
|
|---|---|
|
|
255
|
-
| XRPL EVM Sidechain (Chain ID 1449000) |
|
|
251
|
+
| XRPL EVM Sidechain (Chain ID 1449000, testnet) | MilestoneEscrowV3 — non-custodial escrow in native XRP, no platform role |
|
|
256
252
|
| XRP Ledger Mainnet | NFT completion certificates + audit trail |
|
|
257
253
|
|
|
258
254
|
## Links
|
package/builder-agent.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Cascrow Builder Agent
|
|
4
4
|
* Polls for pending contract invites, then automatically:
|
|
5
|
-
* joins →
|
|
5
|
+
* joins → waits until the milestone is active → submits proof → triggers AI verification
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const BASE_URL = process.env.CASCROW_BASE_URL ?? "https://cascrow.com";
|
|
@@ -107,8 +107,8 @@ async function streamVerify(proofId) {
|
|
|
107
107
|
clearTimeout(timeout);
|
|
108
108
|
// SEC-073. Now that the deadline is live it can actually fire, and a raw
|
|
109
109
|
// `DOMException: This operation was aborted` names no remedy — worse, per
|
|
110
|
-
// the tail arithmetic in latency-budget.ts the abort can land AFTER
|
|
111
|
-
//
|
|
110
|
+
// the tail arithmetic in latency-budget.ts the abort can land AFTER the
|
|
111
|
+
// verdict was written, so "it failed" may be false.
|
|
112
112
|
if (e && e.name === "AbortError") {
|
|
113
113
|
throw new Error(
|
|
114
114
|
"Verification is still running on the server — this client stopped waiting, it did not fail. " +
|
|
@@ -195,32 +195,40 @@ async function handleInvite(invite) {
|
|
|
195
195
|
console.log("🔗 Joining contract...");
|
|
196
196
|
const joined = await apiPost("/api/contracts/join", { inviteCode: invite.inviteCode });
|
|
197
197
|
console.log(` ✓ Joined. Contract ID: ${joined.contractId}`);
|
|
198
|
+
// The join's own activation result: a $0 contract is active now; a money contract
|
|
199
|
+
// waits for the payer's MilestoneEscrowV3 offer, which this agent must accept with
|
|
200
|
+
// its OWN key — cascrow never signs for it.
|
|
201
|
+
if (joined.activated === true) console.log(" ✓ Verification-only contract — active now.");
|
|
202
|
+
else if (joined.activated === false)
|
|
203
|
+
console.log(" ⏳ Paid contract — waiting for the payer's MilestoneEscrowV3 offer. Accept it with your own key: npx cascrow-signer@0.1.0 accept --milestone <milestoneId>.");
|
|
198
204
|
|
|
199
|
-
// 2. Wait
|
|
200
|
-
|
|
205
|
+
// 2. Wait until the milestone is active. A verification-only ($0) contract is active
|
|
206
|
+
// as soon as the builder has joined; a paid one once the payer has funded its
|
|
207
|
+
// MilestoneEscrowV3 escrow from their own wallet. Either way the status is FUNDED.
|
|
208
|
+
console.log("⏳ Waiting for the milestone to be active...");
|
|
201
209
|
let fundedMilestoneId = null;
|
|
202
210
|
for (let i = 0; i < 40; i++) {
|
|
203
211
|
const contract = await apiGet(`/api/contracts/${joined.contractId}`);
|
|
204
212
|
const funded = (contract.milestones ?? []).find((m) => m.status === "FUNDED");
|
|
205
213
|
if (funded) {
|
|
206
214
|
fundedMilestoneId = funded.id;
|
|
207
|
-
console.log(` ✓ Milestone
|
|
215
|
+
console.log(` ✓ Milestone active. ID: ${fundedMilestoneId}`);
|
|
208
216
|
break;
|
|
209
217
|
}
|
|
210
218
|
await new Promise((r) => setTimeout(r, 3000));
|
|
211
219
|
}
|
|
212
|
-
if (!fundedMilestoneId) throw new Error("Timed out waiting for milestone to be
|
|
220
|
+
if (!fundedMilestoneId) throw new Error("Timed out waiting for the milestone to be active");
|
|
213
221
|
|
|
214
222
|
// 3. Do the work — generate proof based on the task message
|
|
215
223
|
console.log("🤖 Performing task...");
|
|
216
224
|
const task = invite.message ?? "Complete the assigned milestone";
|
|
217
|
-
const bio = `
|
|
225
|
+
const bio = `cascrow is trust infrastructure for the agent economy. It checks whether delivered work meets the bar that was agreed for it, and it keeps a verifiable record of the answer.
|
|
218
226
|
|
|
219
|
-
|
|
227
|
+
Every submission is judged in parallel by five AI models from different vendors — Claude, GPT-4o, Gemini, Mistral and Cerebras — and three approvals make a YES. Each verdict is anchored on the XRP Ledger, completed milestones earn an NFT certificate, and the results build a portable on-chain reputation for the builder.
|
|
220
228
|
|
|
221
|
-
|
|
229
|
+
For paid work, cascrow supports a non-custodial escrow in native XRP: the funds sit in a smart contract that only the two parties, or an arbiter they chose, can move. The AI verdict is a signed recommendation; cascrow never holds funds or keys.
|
|
222
230
|
|
|
223
|
-
|
|
231
|
+
Agents use a REST API, an MCP server and a CLI to create contracts, submit proof and read reputation programmatically. Live at cascrow.com.`;
|
|
224
232
|
|
|
225
233
|
const proof = `PROOF OF COMPLETION
|
|
226
234
|
═══════════════════════════════════════
|
|
@@ -230,13 +238,13 @@ Contract ID: ${invite.contractId}
|
|
|
230
238
|
Milestone ID: ${fundedMilestoneId}
|
|
231
239
|
Completed at: ${new Date().toISOString()}
|
|
232
240
|
|
|
233
|
-
DELIVERABLE (
|
|
241
|
+
DELIVERABLE (${bio.split(/\s+/).filter(Boolean).length} words):
|
|
234
242
|
${bio}
|
|
235
243
|
|
|
236
244
|
VERIFICATION CRITERIA MET:
|
|
237
245
|
✓ Professional third-person tone throughout
|
|
238
|
-
✓
|
|
239
|
-
✓ Clearly explains what
|
|
246
|
+
✓ ${bio.split(/\s+/).filter(Boolean).length} words — within the 120–160 word range
|
|
247
|
+
✓ Clearly explains what cascrow does (AI verification, on-chain reputation, non-custodial escrow)
|
|
240
248
|
✓ Highlights AI verification mechanism (5-model majority vote, named models)
|
|
241
249
|
✓ Addresses agent economy angle (autonomous agents, programmatic interfaces)
|
|
242
250
|
✓ No placeholder text
|
|
@@ -331,7 +339,20 @@ VERIFICATION CRITERIA MET:
|
|
|
331
339
|
const released = result.action === "COMPLETED";
|
|
332
340
|
const passed = released;
|
|
333
341
|
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
334
|
-
if (result.
|
|
342
|
+
if (result.action === "V3_RECOMMENDATION") {
|
|
343
|
+
// MilestoneEscrowV3: a recommendation that moves nothing — asked first, so no arm
|
|
344
|
+
// below can read it as a payout or a rejection. The claim is judged by the
|
|
345
|
+
// SUBMISSION's verdict, never by `decision` (the panel on this one file).
|
|
346
|
+
const verdict = result.submissionVerdict === "YES" || result.submissionVerdict === "NO" ? result.submissionVerdict : "PENDING";
|
|
347
|
+
console.log(
|
|
348
|
+
`🧭 AI RECOMMENDATION ${verdict} on a MilestoneEscrowV3 submission — this verdict moves NO money. ` +
|
|
349
|
+
(verdict === "YES"
|
|
350
|
+
? `Claim on chain with proofHash ${result.manifestHash ?? "(the upload's manifestHash)"} from your own key (npx cascrow-signer submit-claim).`
|
|
351
|
+
: verdict === "NO"
|
|
352
|
+
? `Not claim-ready: the payer can object to a claim made with this evidence.`
|
|
353
|
+
: `The submission's verdict is still pending; poll the proof status.`)
|
|
354
|
+
);
|
|
355
|
+
} else if (result.milestoneClosed === true && result.action === "EXPIRED") {
|
|
335
356
|
// `GET /api/contracts/{id}` does not return the milestone's `terminalReason`, so this
|
|
336
357
|
// cannot say whether the funder was refunded or the builder was paid on-chain, and
|
|
337
358
|
// does not guess.
|
|
@@ -348,15 +369,17 @@ VERIFICATION CRITERIA MET:
|
|
|
348
369
|
} else if (released) {
|
|
349
370
|
console.log(
|
|
350
371
|
result.alreadyReleased === true
|
|
351
|
-
? `✅ COMPLETED —
|
|
352
|
-
: `✅ COMPLETED — ${result.confidence}% confidence
|
|
372
|
+
? `✅ COMPLETED — another request completed this milestone first; no money was held (verification-only contract). Do not resubmit.`
|
|
373
|
+
: `✅ COMPLETED — ${result.confidence}% confidence; no money was held (verification-only contract).`
|
|
353
374
|
);
|
|
354
375
|
} else if (result.complianceBlocked === true) {
|
|
355
376
|
console.log(`⏸️ VERIFIED — payout on hold pending compliance review. No funds moved.`);
|
|
356
|
-
} else if (result.
|
|
357
|
-
|
|
377
|
+
} else if (result.action === "NOT_COMPLETABLE_WITHOUT_ESCROW") {
|
|
378
|
+
// Approved, but the milestone carries an amount without a MilestoneEscrowV3 escrow,
|
|
379
|
+
// so nothing completed it. Do not resubmit: the next verdict meets the same row.
|
|
380
|
+
console.log(`⛔ NOT COMPLETED — ${result.remedy ?? "contact support@cascrow.com; do not resubmit."} ${result.error ?? ""}`.trim());
|
|
358
381
|
} else if (result.action === "VERIFIED") {
|
|
359
|
-
console.log(`⚠️ VERIFIED — approved, but the
|
|
382
|
+
console.log(`⚠️ VERIFIED — approved, but the milestone was NOT completed. No money moved; if it stays VERIFIED, contact support@cascrow.com.`);
|
|
360
383
|
} else if (result.timedOut === true) {
|
|
361
384
|
console.log(`⏳ PENDING_REVIEW — the panel did not answer (too few models). NOT a rejection; a human decides.`);
|
|
362
385
|
} else if (result.action === "PENDING_REVIEW") {
|
package/index.js
CHANGED
|
@@ -6,10 +6,91 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
9
10
|
|
|
10
11
|
const BASE_URL = process.env.CASCROW_BASE_URL ?? "https://cascrow.com";
|
|
11
12
|
const API_KEY = process.env.CASCROW_API_KEY ?? "";
|
|
12
13
|
|
|
14
|
+
// What every cascrow client says about MilestoneEscrowV3 — a JSON copy of
|
|
15
|
+
// V3_CLIENT_TEXT in src/lib/v3-client-text.ts (this package cannot import the
|
|
16
|
+
// app). v3-client-text-parity.test.ts holds the copy equal to the owner.
|
|
17
|
+
// BEGIN v3-client-text
|
|
18
|
+
const V3_CLIENT_TEXT = {
|
|
19
|
+
"signerPin": "cascrow-signer@0.1.0",
|
|
20
|
+
"remoteTools": [
|
|
21
|
+
"cascrow_v3_offer",
|
|
22
|
+
"cascrow_v3_acceptance_get",
|
|
23
|
+
"cascrow_v3_acceptance_submit",
|
|
24
|
+
"cascrow_v3_prepare",
|
|
25
|
+
"cascrow_v3_report_tx",
|
|
26
|
+
"cascrow_v3_attestation",
|
|
27
|
+
"cascrow_v3_claims"
|
|
28
|
+
],
|
|
29
|
+
"codeHints": {
|
|
30
|
+
"V3_DISABLED": "MilestoneEscrowV3 is switched off for NEW money right now (offers, acceptances, funding). An escrow that already exists is never locked: object, release, refund, claim and withdraw keep working (cascrow_v3_prepare, or npx cascrow-signer@0.1.0 without cascrow at all).",
|
|
31
|
+
"RATE_UNAVAILABLE": "cascrow could not get a consistent XRP/USD rate from its two sources, so no offer can be priced. Nothing was created; retry in a minute.",
|
|
32
|
+
"V3_NOT_OFFERED": "The payer has not offered MilestoneEscrowV3 terms for this milestone yet — only the payer can (cascrow_v3_offer). The payer has been notified.",
|
|
33
|
+
"OFFER_EXISTS": "An open offer already exists for this milestone and stays valid until it expires. Wait for the payee's acceptance or for the offer to lapse; sending the same inputs again returns the same offer.",
|
|
34
|
+
"V3_OFFER_OPEN": "An open MilestoneEscrowV3 offer, or a signed acceptance that is not funded yet, is bound to this milestone or wallet, so it cannot change now. Wait until the payer funds the escrow from their own machine (npx cascrow-signer@0.1.0 fund --milestone <milestoneId>) or the offer or acceptance lapses.",
|
|
35
|
+
"ATTESTATION_PENDING": "The AI panel has not finished on this claim. Its result is a non-binding recommendation — as payer you can object NOW, before the objection deadline, without waiting for it.",
|
|
36
|
+
"CLAIM_PROOF_UNKNOWN": "This claim's proofHash matches no evidence submitted to cascrow, so there is no AI recommendation for it. As payer, judge the delivery yourself and object before the objection deadline if it is not what was agreed.",
|
|
37
|
+
"V3_ESCROW": "This milestone is held in a MilestoneEscrowV3 escrow: only its payer, payee or arbiter moves that money, on chain, with their own key. Get the unsigned transaction with cascrow_v3_prepare, or sign locally with npx cascrow-signer@0.1.0.",
|
|
38
|
+
"V2_ESCROW_PRESENT": "This milestone or its contract still carries data of the retired v2 (RLUSD) escrow, so it cannot become a MilestoneEscrowV3 escrow. Nothing was changed; contact support.",
|
|
39
|
+
"INVALID_SIGNATURE": "The acceptance signature does not verify for your linked wallet over these exact terms (EIP-712, low-s, from an ordinary account). Sign the terms returned by cascrow_v3_acceptance_get again — npx cascrow-signer@0.1.0 accept does it and checks them first.",
|
|
40
|
+
"V3_INDEX_STALE": "cascrow's index of the chain is behind. Read the escrow from the chain directly (npx cascrow-signer@0.1.0 claims, or getEscrow / ClaimSubmitted); every deadline, above all an objection window, runs on chain regardless.",
|
|
41
|
+
"ACCEPTANCE_LAPSED": "The acceptance expired without funding, so the milestone is closed — this is not a failure of either party. A new attempt is a new milestone.",
|
|
42
|
+
"NOT_COMPLETABLE_WITHOUT_ESCROW": "The work was approved, but this milestone carries an amount without a MilestoneEscrowV3 escrow, and the previous escrow system has been removed — nothing can complete or pay it, so it was left as it was and NO money moved. cascrow has opened a case for it; contact support@cascrow.com with the contract ID. Do not resubmit: your evidence is recorded.",
|
|
43
|
+
"RELEASE_HOLD_REMOVED": "releaseHoldHours was removed with the previous escrow system; send the contract without it. A MilestoneEscrowV3 escrow's review period is agreed in the payer's offer (cascrow_v3_offer), and a $0 verification contract has no payout to delay.",
|
|
44
|
+
"AMOUNT_VIA_MILESTONES": "This draft has milestones, and their amounts are the contract's amount — it cannot be set on the contract. Nothing was changed. To change an amount, delete the draft and create the contract again; before joining, the invited builder can propose other amounts in a counter-proposal, which the requester can accept.",
|
|
45
|
+
"DEADLINE_VIA_MILESTONES": "This draft has milestones, and their deadlines are the ones enforced — a contract-level deadline changes nothing, so it is refused. Nothing was changed. To change a deadline, delete the draft and create the contract again; before joining, the invited builder can propose other deadlines in a counter-proposal, which the requester can accept.",
|
|
46
|
+
"MIXED_AMOUNTS": "A contract is either verification-only (every milestone $0) or escrowed (every milestone above $0), never both. Nothing was created or changed. Set every milestone to $0, or give every milestone an amount and fund each one through a MilestoneEscrowV3 offer (cascrow_v3_offer) once the builder has joined."
|
|
47
|
+
},
|
|
48
|
+
"joined": {
|
|
49
|
+
"active": "Joined as Builder. This is a $0 verification contract, so it is active now — submit proof for a milestone (cascrow_submit_proof). No money is held or moved.",
|
|
50
|
+
"awaitingOffer": "Joined as Builder. The milestones carry an amount, so nothing is funded yet: wait for the payer's MilestoneEscrowV3 offer, then accept it with your own wallet key — npx cascrow-signer@0.1.0 accept --milestone <milestoneId>, or on the contract page. cascrow never signs for you. Submit proof once the payer has funded the milestone.",
|
|
51
|
+
"unknown": "Joined as Builder. The server did not say whether the contract is active — check it with cascrow_get_contract before submitting proof."
|
|
52
|
+
},
|
|
53
|
+
"payeeHints": {
|
|
54
|
+
"CLAIM_PROOF_UNKNOWN": "This manifest is not a sealed submission of this milestone, so cascrow will not confirm it and nothing was signed. As payee, claim with the manifestHash the evidence door returned (the upload's answer, or cascrow_get_proof_status): npx cascrow-signer@0.1.0 submit-claim --milestone <milestoneId> --proof-hash <manifestHash>."
|
|
55
|
+
},
|
|
56
|
+
"fundRemoved": "cascrow_fund_milestone was removed (and with it fundMilestone in cascrow-sdk and `cascrow fund`). A $0 verification contract is active by itself once the builder has joined — there is nothing to fund. A milestone with an amount is a MilestoneEscrowV3 escrow in native XRP: the payer offers terms (cascrow_v3_offer), the builder accepts with their own key, and the payer funds it with one transaction from their own machine — npx cascrow-signer@0.1.0 fund --milestone <milestoneId>. cascrow never signs or sends for you.",
|
|
57
|
+
"recommendation": "🧭 AI RECOMMENDATION {verdict} for this MilestoneEscrowV3 submission ({confidence}% confidence on this file) — this verdict moves NO money. The submission is YES only when every file in it has a definite AI yes; only then is it claim-ready. As payee: claim on chain with the submission's manifestHash (npx cascrow-signer@0.1.0 submit-claim --milestone <milestoneId> --proof-hash <manifestHash>). As payer: release, or object before the objection deadline.",
|
|
58
|
+
"reportTxOnlyHash": "cascrow_v3_report_tx takes a transaction HASH (0x + 64 hex characters) and nothing else. cascrow never accepts a signed transaction — broadcast it yourself to the chain's public RPC."
|
|
59
|
+
};
|
|
60
|
+
// END v3-client-text
|
|
61
|
+
const TX_HASH_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
62
|
+
function v3HintFor(code, body) {
|
|
63
|
+
if (!code) return null;
|
|
64
|
+
// Role-aware (post-review H9): CLAIM_PROOF_UNKNOWN without objectionDeadline comes from
|
|
65
|
+
// prepare?action=submitClaim and reaches the PAYEE; with it, a live claim — the payer's.
|
|
66
|
+
const b = body && typeof body === "object" ? body : null;
|
|
67
|
+
const liveClaim = !!b && typeof b.objectionDeadline === "string";
|
|
68
|
+
if (b && !liveClaim && V3_CLIENT_TEXT.payeeHints[code]) return V3_CLIENT_TEXT.payeeHints[code];
|
|
69
|
+
return V3_CLIENT_TEXT.codeHints[code] || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The fields of a V3 refusal body its recipient acts on, as one line (post-review G5) — the owner's rule. */
|
|
73
|
+
function v3RefusalDetail(body) {
|
|
74
|
+
if (!body || typeof body !== "object") return "";
|
|
75
|
+
const out = [];
|
|
76
|
+
if (typeof body.remedy === "string" && body.remedy) out.push(body.remedy);
|
|
77
|
+
if (Array.isArray(body.allowed)) out.push(`Allowed now: ${body.allowed.length ? body.allowed.map(String).join(", ") : "nothing"}.`);
|
|
78
|
+
if (typeof body.objectionDeadline === "string" && body.objectionDeadline) out.push(`Objection deadline: ${body.objectionDeadline}.`);
|
|
79
|
+
if (typeof body.offerExpiresAt === "string" && body.offerExpiresAt) out.push(`Open offer expires: ${body.offerExpiresAt}.`);
|
|
80
|
+
return out.join(" ");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The line for a `POST /api/contracts/join` answer — same rule as `joinedLine` in
|
|
85
|
+
* src/lib/v3-client-text.ts (v3-client-text-parity.test.ts runs this copy against it).
|
|
86
|
+
*/
|
|
87
|
+
function joinedLine(body) {
|
|
88
|
+
const activated = body && typeof body === "object" ? body.activated : undefined;
|
|
89
|
+
if (activated === true) return V3_CLIENT_TEXT.joined.active;
|
|
90
|
+
if (activated === false) return V3_CLIENT_TEXT.joined.awaitingOffer;
|
|
91
|
+
return V3_CLIENT_TEXT.joined.unknown;
|
|
92
|
+
}
|
|
93
|
+
|
|
13
94
|
// ─── HTTP helpers ─────────────────────────────────────────────────────────────
|
|
14
95
|
// Authorization header is omitted entirely when key is empty, rather than
|
|
15
96
|
// sending a garbage "Bearer " token — matters for `verify-work`, the one
|
|
@@ -81,13 +162,18 @@ async function fetchJson(method, path, { json, form, key = API_KEY, deadlineMs =
|
|
|
81
162
|
const retryAfterSeconds = retryAfterOf(res, data);
|
|
82
163
|
const base = typeof data.error === "string" && data.error ? data.error : `HTTP ${res.status}`;
|
|
83
164
|
// A platform error page on a POST (typically the 504 when a route hits its own
|
|
84
|
-
// maxDuration —
|
|
85
|
-
// may have moved money before it was killed. Say so, and
|
|
165
|
+
// maxDuration — contracts/review settles on chain inside its own) does not mean the
|
|
166
|
+
// call failed: the function may have moved money before it was killed. Say so, and
|
|
167
|
+
// how to check.
|
|
86
168
|
const mayHaveCompleted = method !== "GET" && res.status >= 500 && data.code === "UPSTREAM_NON_JSON";
|
|
87
169
|
throw Object.assign(
|
|
88
170
|
new Error(
|
|
89
171
|
`${base}${retryAfterSeconds !== null && (res.status === 429 || res.status === 503) ? ` — retry after ${retryAfterSeconds} s` : ""}${code ? ` [${code}]` : ""}` +
|
|
90
|
-
(mayHaveCompleted ? ". The request may still have completed on the server — check the contract (cascrow get --contract <id>) before repeating it." : "")
|
|
172
|
+
(mayHaveCompleted ? ". The request may still have completed on the server — check the contract (cascrow get --contract <id>) before repeating it." : "") +
|
|
173
|
+
// A MilestoneEscrowV3 refusal says who can act and how (F-72): the body's actionable
|
|
174
|
+
// fields (G5), then the owner's words, role-aware on the body (H9).
|
|
175
|
+
(v3RefusalDetail(data) ? ` — ${v3RefusalDetail(data)}` : "") +
|
|
176
|
+
(v3HintFor(code, data) ? `\n\n${v3HintFor(code, data)}` : "")
|
|
91
177
|
),
|
|
92
178
|
{ status: res.status, code, retryAfterSeconds }
|
|
93
179
|
);
|
|
@@ -273,15 +359,17 @@ async function waitForProof(proofId, maxMs = 100_000, key = API_KEY, pollMs = 5_
|
|
|
273
359
|
}
|
|
274
360
|
continue;
|
|
275
361
|
}
|
|
276
|
-
|
|
362
|
+
// `notCompletableWithoutEscrow`: no verdict can complete this milestone — final.
|
|
363
|
+
if (!last.pending || last.milestoneClosed === true || last.notCompletableWithoutEscrow === true) return { ...last, proofId };
|
|
277
364
|
// Parked for a human: no poll will change it (same rule as both MCP servers).
|
|
278
|
-
|
|
365
|
+
// Never a MilestoneEscrowV3 proof (post-review L2): no human review decides one.
|
|
366
|
+
if (last.escrowVersion !== 3 && last.running !== true && last.milestoneStatus === "PENDING_REVIEW") return { ...last, proofId, awaitingHumanReview: true };
|
|
279
367
|
if (last.claimPhase === "orphaned") return { ...last, proofId, stoppedBecause: "interrupted" };
|
|
280
368
|
}
|
|
281
369
|
return { ...(last ?? {}), pending: true, proofId, timedOut: true };
|
|
282
370
|
}
|
|
283
371
|
|
|
284
|
-
/** One line for a proof-status result, saying whether funds moved
|
|
372
|
+
/** One line for a proof-status result, saying whether funds moved. */
|
|
285
373
|
function statusSummary(s) {
|
|
286
374
|
if (s.stoppedBecause === 404) return `⛔ Proof ${s.proofId} no longer exists (deleted or erased). Nothing will verify it.`;
|
|
287
375
|
if (s.stoppedBecause === 401 || s.stoppedBecause === 403) return `⛔ Stopped: ${s.error}. Waiting does not change this — check CASCROW_API_KEY and that this account is a party to the contract.`;
|
|
@@ -293,20 +381,35 @@ function statusSummary(s) {
|
|
|
293
381
|
if (s.awaitingHumanReview) return "⏳ PENDING_REVIEW — the panel reached no verdict; a human decides. Waiting will not change it.";
|
|
294
382
|
if (s.timedOut) return `⏳ Still verifying. Check again: cascrow status --proof ${s.proofId} --wait`;
|
|
295
383
|
const conf = typeof s.confidence === "number" ? ` (${s.confidence}% confidence)` : "";
|
|
384
|
+
// MilestoneEscrowV3 (post-review G4): the verdict is a RECOMMENDATION and moves no money.
|
|
385
|
+
// Asked before every v2 arm — the claim-relevant answer is the SUBMISSION's verdict
|
|
386
|
+
// (`submissionVerdict`, `claimReady`), never this proof's `decision`, and a closed or
|
|
387
|
+
// COMPLETED V3 milestone was settled on chain by the parties.
|
|
388
|
+
if (s.escrowVersion === 3 && !(s.milestoneStatus === "COMPLETED" || s.milestoneClosed === true)) {
|
|
389
|
+
const line = V3_CLIENT_TEXT.recommendation.replace("{verdict}", s.submissionVerdict ?? "PENDING").replace("{confidence}", String(s.confidence ?? 0));
|
|
390
|
+
return s.manifestHash ? `${line} manifestHash: ${s.manifestHash}` : line;
|
|
391
|
+
}
|
|
392
|
+
if (s.escrowVersion === 3) {
|
|
393
|
+
return s.milestoneStatus === "COMPLETED"
|
|
394
|
+
? "✅ COMPLETED — the MilestoneEscrowV3 escrow paid out on chain."
|
|
395
|
+
: `⛔ CLOSED — the milestone is ${s.terminalStatus ?? "closed"}; the MilestoneEscrowV3 escrow settled on chain. Do not resubmit to it.`;
|
|
396
|
+
}
|
|
296
397
|
// COMPLETED first: proof-status reports a paid milestone as `milestoneClosed` too
|
|
297
398
|
// (terminalStatus COMPLETED), and the CLOSED arm below would read a release as
|
|
298
399
|
// "moves no funds" — SEC-059's inversion (same order as proofOutcomeLine in both
|
|
299
400
|
// MCP servers). Whether it was THIS proof's YES is `decision`.
|
|
300
401
|
if (s.milestoneStatus === "COMPLETED" || s.terminalStatus === "COMPLETED") {
|
|
301
|
-
return s.decision === "YES" ? `✅ COMPLETED${conf} —
|
|
402
|
+
return s.decision === "YES" ? `✅ COMPLETED${conf} — no money was held (verification-only contract).` : `✅ COMPLETED — no money was held (verification-only contract), but not on this proof's verdict (another proof or the funder's review approved it).`;
|
|
302
403
|
}
|
|
303
404
|
if (s.milestoneClosed === true) {
|
|
304
405
|
return s.terminalReason === "RELEASED_ON_CHAIN"
|
|
305
406
|
? "⛔ CLOSED (EXPIRED) — the chain shows the builder WAS paid, just not by this verification."
|
|
306
407
|
: `⛔ CLOSED — the milestone is ${s.terminalStatus ?? "closed"}; this verification moved no funds. Do not resubmit to it.`;
|
|
307
408
|
}
|
|
308
|
-
|
|
309
|
-
|
|
409
|
+
// An approval cannot complete this milestone (an amount without a MilestoneEscrowV3
|
|
410
|
+
// escrow); the verdict doors answered 409 NOT_COMPLETABLE_WITHOUT_ESCROW.
|
|
411
|
+
if (s.notCompletableWithoutEscrow === true) return `⛔ NOT COMPLETABLE — ${V3_CLIENT_TEXT.codeHints.NOT_COMPLETABLE_WITHOUT_ESCROW}`;
|
|
412
|
+
if (s.milestoneStatus === "VERIFIED") return `⚠️ VERIFIED${conf} — approved, not completed yet.`;
|
|
310
413
|
if (s.milestoneStatus === "PENDING_REVIEW") return `⏳ PENDING_REVIEW${conf} — manual review required.`;
|
|
311
414
|
if (s.decision === "NO") return `❌ REJECTED${conf}.`;
|
|
312
415
|
return `ℹ️ Decision: ${s.decision ?? "none"}${conf}, milestone ${s.milestoneStatus ?? "unknown"}.`;
|
|
@@ -322,6 +425,12 @@ function statusSummary(s) {
|
|
|
322
425
|
*/
|
|
323
426
|
function outcomeLine(r) {
|
|
324
427
|
const c = r.confidence ?? 0;
|
|
428
|
+
// MilestoneEscrowV3: a recommendation that moves nothing — asked first, so no arm
|
|
429
|
+
// below can read it as a payout or a rejection.
|
|
430
|
+
if (r.action === "V3_RECOMMENDATION") {
|
|
431
|
+
// The SUBMISSION's verdict, never the panel's `decision` on one file.
|
|
432
|
+
return V3_CLIENT_TEXT.recommendation.replace("{verdict}", r.submissionVerdict ?? "PENDING").replace("{confidence}", String(c));
|
|
433
|
+
}
|
|
325
434
|
if (r.action === "SUPERSEDED") {
|
|
326
435
|
// `settledAs`: the milestone was closed (e.g. EXPIRED) before this verification could
|
|
327
436
|
// settle it, not beaten by a proof.
|
|
@@ -331,17 +440,19 @@ function outcomeLine(r) {
|
|
|
331
440
|
}
|
|
332
441
|
if (r.action === "COMPLETED") {
|
|
333
442
|
return r.alreadyReleased === true
|
|
334
|
-
?
|
|
335
|
-
: `✅ COMPLETED (${c}% confidence) —
|
|
443
|
+
? `✅ COMPLETED — another request completed this milestone first; no money was held (verification-only contract). Do not resubmit.`
|
|
444
|
+
: `✅ COMPLETED (${c}% confidence) — no money was held (verification-only contract)${r.txHash ? `, tx ${r.txHash}` : ""}.`;
|
|
336
445
|
}
|
|
337
446
|
if (r.complianceBlocked === true) {
|
|
338
447
|
return `⏸️ VERIFIED (${c}%) — payout on hold pending compliance review. No funds moved.`;
|
|
339
448
|
}
|
|
340
|
-
if (r.
|
|
341
|
-
return
|
|
449
|
+
if (r.action === "NOT_COMPLETABLE_WITHOUT_ESCROW") {
|
|
450
|
+
return `⛔ NOT COMPLETED (${c}% confidence) — ${V3_CLIENT_TEXT.codeHints.NOT_COMPLETABLE_WITHOUT_ESCROW}`;
|
|
342
451
|
}
|
|
343
452
|
if (r.action === "VERIFIED") {
|
|
344
|
-
|
|
453
|
+
// Approved, but no completion followed; the producer does not reach this since
|
|
454
|
+
// Phase 6. No retry is promised — there is nothing to retry.
|
|
455
|
+
return `⚠️ VERIFIED (${c}% confidence) — the work was approved, but the milestone was NOT completed. No money moved. Check it with \`cascrow status\`; if it stays VERIFIED, contact support@cascrow.com.`;
|
|
345
456
|
}
|
|
346
457
|
if (r.timedOut === true) {
|
|
347
458
|
return "⏳ PENDING_REVIEW — the verification panel did not answer (too few models responded). This is NOT a rejection and NOT a judgement of the work; a human decides.";
|
|
@@ -423,7 +534,7 @@ function err(msg) {
|
|
|
423
534
|
|
|
424
535
|
function usage() {
|
|
425
536
|
console.log(`
|
|
426
|
-
|
|
537
|
+
cascrow CLI — AI verification, on-chain reputation and non-custodial escrow for agents
|
|
427
538
|
|
|
428
539
|
Usage:
|
|
429
540
|
cascrow <command> [options]
|
|
@@ -433,8 +544,6 @@ Commands:
|
|
|
433
544
|
verify-work Verify AI-generated work — no contract, no escrow, no account needed
|
|
434
545
|
verify Submit proof and get AI verdict in one call (recommended)
|
|
435
546
|
create Create a milestone contract
|
|
436
|
-
fund Activate a milestone (simulation — no on-chain tx)
|
|
437
|
-
escrow-fund Fund a milestone with real on-chain RLUSD escrow
|
|
438
547
|
submit Upload proof text — verification starts by itself (then: status --proof <id> --wait)
|
|
439
548
|
status Show (or --wait for) the verification result of a proof
|
|
440
549
|
get Get contract status and milestones
|
|
@@ -443,6 +552,10 @@ Commands:
|
|
|
443
552
|
check-invites Check pending contract invites for this agent
|
|
444
553
|
review-queue As funder: list milestones still waiting for your verdict
|
|
445
554
|
review As funder: approve or reject a milestone under manual review
|
|
555
|
+
v3-claims As payer: list MilestoneEscrowV3 claims you can still object to on chain
|
|
556
|
+
v3 <sub> MilestoneEscrowV3, remote half: offer | acceptance | accept-submit | prepare | report-tx | attestation | claims
|
|
557
|
+
and "v3 sign <cascrow-signer command…>" — runs the pinned local signer (npx cascrow-signer@0.1.0),
|
|
558
|
+
which holds your key on YOUR machine. cascrow never signs, sends or relays for you.
|
|
446
559
|
me Print this agent's ID
|
|
447
560
|
|
|
448
561
|
Options for each command:
|
|
@@ -469,22 +582,14 @@ Options for each command:
|
|
|
469
582
|
create
|
|
470
583
|
--title <title> required short milestone title (printed on the certificate)
|
|
471
584
|
--days <n> optional deadline days from now (default: 7)
|
|
472
|
-
--amount <usd> optional
|
|
585
|
+
--amount <usd> optional default 0 = verification-only (active once the builder joins). Above 0 the
|
|
586
|
+
milestone is a MilestoneEscrowV3 escrow in native XRP: offer it with
|
|
587
|
+
"cascrow v3 offer", the builder accepts, you fund with "cascrow v3 sign fund"
|
|
473
588
|
--criteria <text> optional what the AI panel checks the proof against (max 4000 chars)
|
|
474
589
|
--criteria-file <path> optional the same, read from a file
|
|
475
590
|
--checklist-file <path> optional JSON array of { title, severity?, id? } (max 20) scored item by item on text proofs
|
|
476
591
|
--review-mode <mode> optional AUTO | MANUAL | MANUAL_AUTO — what happens when the panel is unsure (60–85%)
|
|
477
592
|
|
|
478
|
-
fund
|
|
479
|
-
--contract <id> required
|
|
480
|
-
--milestone <id> optional defaults to first available
|
|
481
|
-
|
|
482
|
-
escrow-fund [REAL on-chain RLUSD — agent's own wallet]
|
|
483
|
-
--contract <id> required
|
|
484
|
-
--private-key <key> required EVM private key (funds come from this wallet)
|
|
485
|
-
--amount <usd> IGNORED the escrowed amount is the milestone's own amountUSD
|
|
486
|
-
--milestone <id> optional
|
|
487
|
-
|
|
488
593
|
submit [or use verify for text proof in one call]
|
|
489
594
|
--milestone <id> required
|
|
490
595
|
--proof <text> required
|
|
@@ -522,15 +627,13 @@ Examples:
|
|
|
522
627
|
# Register a new agent account (no human in the loop)
|
|
523
628
|
cascrow register --email bot@example.com --password secret123
|
|
524
629
|
|
|
525
|
-
# One-shot: create contract
|
|
630
|
+
# One-shot: create a verification-only contract (active once the builder joins), then verify a code fix
|
|
526
631
|
cascrow create --title "Fix auth bug" --criteria "All tests pass; JWT expiry honoured; no new lint errors" --days 7
|
|
527
|
-
cascrow fund --contract <contractId>
|
|
528
632
|
cascrow verify --contract <contractId> --proof "Fixed JWT expiry bug in auth.ts, 42 tests green, PR #51 merged" --commit abc1234 --links https://github.com/you/repo/pull/51
|
|
529
633
|
|
|
530
|
-
#
|
|
531
|
-
cascrow
|
|
532
|
-
cascrow
|
|
533
|
-
cascrow verify --contract <contractId> --proof "API live at https://api.example.com, all endpoints responding"
|
|
634
|
+
# Paid work is a MilestoneEscrowV3 escrow (native XRP, testnet): create with --amount,
|
|
635
|
+
# offer it (cascrow v3 offer), the builder accepts, and you fund with one transaction
|
|
636
|
+
# from your own machine (cascrow v3 sign fund). This CLI never takes a key.
|
|
534
637
|
|
|
535
638
|
# Agent-to-agent handoff
|
|
536
639
|
cascrow me # → get your Agent ID
|
|
@@ -542,10 +645,25 @@ Examples:
|
|
|
542
645
|
|
|
543
646
|
# As the funder: decide what the AI panel could not (confidence 60-85)
|
|
544
647
|
# Poll this — a manual_review.required webhook you missed is a decision nobody
|
|
545
|
-
# makes
|
|
648
|
+
# makes; an undecided milestone completes automatically after 48h under review mode
|
|
649
|
+
# MANUAL_AUTO or AUTO, 14 days otherwise.
|
|
650
|
+
# Verification-only contracts: a verdict completes or hands back, and moves no money.
|
|
546
651
|
cascrow review-queue
|
|
547
652
|
cascrow review --contract <contractId> --decision APPROVE
|
|
548
653
|
cascrow review --contract <contractId> --decision REJECT --milestone <milestoneId>
|
|
654
|
+
|
|
655
|
+
# As the payer of a MilestoneEscrowV3 escrow: claims release unless YOUR wallet
|
|
656
|
+
# calls object(escrowId) before the deadline — cascrow cannot object for you.
|
|
657
|
+
cascrow v3-claims
|
|
658
|
+
|
|
659
|
+
# MilestoneEscrowV3 (native XRP). cascrow prepares and reads; you sign locally.
|
|
660
|
+
cascrow v3 offer --milestone <id> --review-period-sec 259200 [--watcher] [--arbiter 0x… --arbitration-period-sec N]
|
|
661
|
+
cascrow v3 acceptance --milestone <id> # as builder: the frozen terms to check
|
|
662
|
+
cascrow v3 sign accept --milestone <id> --network testnet # check + sign locally (pinned cascrow-signer)
|
|
663
|
+
cascrow v3 sign fund --milestone <id> --network testnet # as payer: one transaction, value = amount
|
|
664
|
+
cascrow v3 prepare --milestone <id> --action object --reason-hash 0x… # UNSIGNED — verify before signing
|
|
665
|
+
cascrow v3 report-tx --milestone <id> --tx 0x<64 hex> # a HASH only, never a signed transaction
|
|
666
|
+
cascrow v3 attestation --escrow 0x… --claim-index 1 # non-binding AI recommendation
|
|
549
667
|
`);
|
|
550
668
|
process.exit(0);
|
|
551
669
|
}
|
|
@@ -747,7 +865,8 @@ async function cmdVerifyWork(args) {
|
|
|
747
865
|
}
|
|
748
866
|
|
|
749
867
|
// One-shot verify: submit text proof + get verdict in a single call via /api/mcp/submit.
|
|
750
|
-
//
|
|
868
|
+
// A verification-only milestone completes (NFT minted) on approval; a MilestoneEscrowV3
|
|
869
|
+
// verdict is a recommendation that moves no money. No separate `verify --proof <id>` needed.
|
|
751
870
|
async function cmdVerify(args) {
|
|
752
871
|
if (!API_KEY) err("CASCROW_API_KEY is not set");
|
|
753
872
|
if (!args.proof) err("--proof is required");
|
|
@@ -792,9 +911,12 @@ async function cmdVerify(args) {
|
|
|
792
911
|
// nothing moved, do not resubmit" from "another proof won" (round-3 pass 2).
|
|
793
912
|
settledAs: typeof result.settledAs === "string" ? result.settledAs : null,
|
|
794
913
|
complianceBlocked: result.complianceBlocked === true,
|
|
795
|
-
|
|
914
|
+
notCompletableWithoutEscrow: result.action === "NOT_COMPLETABLE_WITHOUT_ESCROW",
|
|
796
915
|
panelUnavailable: result.timedOut === true,
|
|
797
916
|
txHash: result.txHash ?? null,
|
|
917
|
+
// MilestoneEscrowV3 (post-review G4): the recommendation's own fields, so a machine
|
|
918
|
+
// caller reads the SUBMISSION's verdict and the hash to claim with.
|
|
919
|
+
...(result.action === "V3_RECOMMENDATION" || result.escrowVersion === 3 ? v3Fields(result) : {}),
|
|
798
920
|
reasoning: result.reasoning,
|
|
799
921
|
modelVotes: (result.votes ?? []).map(v => ({ model: v.model, decision: v.decision, confidence: v.confidence })),
|
|
800
922
|
});
|
|
@@ -825,14 +947,17 @@ async function cmdVerify(args) {
|
|
|
825
947
|
// `verdict` key, so `data.verdict ?? "unknown"` fell to the final arm below and told
|
|
826
948
|
// the builder "❌ REJECTED — resubmit with stronger proof" for a milestone that may
|
|
827
949
|
// well have been approved and paid (SEC-055). Classified before the verdict is read.
|
|
828
|
-
const
|
|
950
|
+
const isV3 = data.action === "V3_RECOMMENDATION";
|
|
951
|
+
const verdict = isV3
|
|
952
|
+
? "v3_recommendation"
|
|
953
|
+
: data.decided === "elsewhere"
|
|
829
954
|
? "decided_elsewhere"
|
|
830
955
|
: data.panelUnavailable
|
|
831
956
|
? "panel_unavailable"
|
|
832
957
|
: (data.verdict ?? "unknown");
|
|
833
958
|
const confidence = data.confidence ?? 0;
|
|
834
|
-
//
|
|
835
|
-
|
|
959
|
+
// An approved milestone that cannot complete is not a 200: the route answers 409
|
|
960
|
+
// NOT_COMPLETABLE_WITHOUT_ESCROW, which `apiPost` turns into an error with its hint.
|
|
836
961
|
|
|
837
962
|
// Print model votes if available
|
|
838
963
|
const votes = Array.isArray(data.model_votes) ? data.model_votes : [];
|
|
@@ -848,7 +973,12 @@ async function cmdVerify(args) {
|
|
|
848
973
|
const settledAs = verdict === "decided_elsewhere" && typeof data.settledAs === "string" ? data.settledAs : null;
|
|
849
974
|
|
|
850
975
|
let statusLine;
|
|
851
|
-
if (
|
|
976
|
+
if (isV3) {
|
|
977
|
+
// A MilestoneEscrowV3 submission: sealed, recommended on, nothing moved (G4).
|
|
978
|
+
statusLine = V3_CLIENT_TEXT.recommendation.replace("{verdict}", data.submissionVerdict ?? "PENDING").replace("{confidence}", String(confidence));
|
|
979
|
+
if (data.manifestHash) statusLine += `\n manifestHash: ${data.manifestHash}`;
|
|
980
|
+
if (typeof data.next === "string") statusLine += `\n ${data.next}`;
|
|
981
|
+
} else if (settledAs) {
|
|
852
982
|
statusLine =
|
|
853
983
|
`ℹ️ This milestone was closed (${settledAs}) before this submission could settle it, so this submission ` +
|
|
854
984
|
"changed nothing and moved no funds itself. Your evidence was recorded. Do not resubmit to this milestone";
|
|
@@ -862,10 +992,6 @@ async function cmdVerify(args) {
|
|
|
862
992
|
"⏳ PENDING_REVIEW — the verification panel could not reach a verdict (too few models " +
|
|
863
993
|
"answered). This is NOT a rejection and NOT a judgement of your work; a human decides, " +
|
|
864
994
|
"and resubmitting the same evidence will not help while the panel is down";
|
|
865
|
-
} else if (verdict === "approved" && releaseHoldUntil) {
|
|
866
|
-
statusLine =
|
|
867
|
-
`⏸️ APPROVED (${confidence}% confidence) — payout scheduled for ${releaseHoldUntil}. ` +
|
|
868
|
-
"NO FUNDS HAVE MOVED YET: the funder can contest until then, and the date can move";
|
|
869
995
|
} else if (verdict === "approved") {
|
|
870
996
|
statusLine = `✅ VERIFIED (${confidence}% confidence)`;
|
|
871
997
|
if (data.on_chain_url) statusLine += `\n Proof: ${data.on_chain_url}`;
|
|
@@ -886,18 +1012,28 @@ async function cmdVerify(args) {
|
|
|
886
1012
|
print({
|
|
887
1013
|
verdict,
|
|
888
1014
|
confidence,
|
|
889
|
-
|
|
890
|
-
passed: verdict === "approved" && !releaseHoldUntil,
|
|
891
|
-
release_hold_until: releaseHoldUntil,
|
|
1015
|
+
passed: verdict === "approved",
|
|
892
1016
|
...(settledAs ? { settled_as: settledAs } : {}),
|
|
893
1017
|
reasoning: data.reasoning ?? "",
|
|
894
1018
|
on_chain_url: data.on_chain_url ?? null,
|
|
895
1019
|
proof_id: data.proof_id ?? null,
|
|
896
1020
|
signed_at: data.signed_at ?? null,
|
|
1021
|
+
...(isV3 ? v3Fields(data) : {}),
|
|
897
1022
|
model_votes: votes,
|
|
898
1023
|
});
|
|
899
1024
|
}
|
|
900
1025
|
|
|
1026
|
+
/** The MilestoneEscrowV3 fields of an answer, for JSON output (post-review G4). Never derived: forwarded. */
|
|
1027
|
+
function v3Fields(r) {
|
|
1028
|
+
return {
|
|
1029
|
+
escrowVersion: 3,
|
|
1030
|
+
submissionId: r.submissionId ?? null,
|
|
1031
|
+
manifestHash: r.manifestHash ?? null,
|
|
1032
|
+
submissionVerdict: r.submissionVerdict ?? "PENDING",
|
|
1033
|
+
claimReady: r.claimReady === true,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
901
1037
|
async function cmdCreate(args) {
|
|
902
1038
|
if (!API_KEY) err("CASCROW_API_KEY is not set");
|
|
903
1039
|
if (!args.title) err("--title is required");
|
|
@@ -948,42 +1084,28 @@ async function cmdCreate(args) {
|
|
|
948
1084
|
process.stderr.write(`\nContract created: ${contractUrl}\n\n`);
|
|
949
1085
|
}
|
|
950
1086
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
: { contractId: args.contract };
|
|
958
|
-
|
|
959
|
-
const data = await apiPost("/api/agent/fund-milestone", body);
|
|
960
|
-
print(data);
|
|
961
|
-
process.stderr.write(`\nMilestone funded (simulation). ID: ${data.milestoneId}\n\n`);
|
|
1087
|
+
// Removed in 3.0.0 (cascrow Phase 6, owner decision 16): `fund` activated a v2
|
|
1088
|
+
// milestone. A $0 verification contract is active by itself once the builder has
|
|
1089
|
+
// joined, and a milestone with an amount is a MilestoneEscrowV3 escrow its payer
|
|
1090
|
+
// funds from their own machine. Kept as a command only to say so; it sends nothing.
|
|
1091
|
+
function cmdFundRemoved() {
|
|
1092
|
+
err(`${V3_CLIENT_TEXT.fundRemoved} [TOOL_REMOVED]`);
|
|
962
1093
|
}
|
|
963
1094
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
process.stderr.write(
|
|
979
|
-
"\n⚠️ --amount is ignored for escrow funding: the escrowed amount is the milestone's own amountUSD.\n"
|
|
980
|
-
);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
process.stderr.write("\nFunding milestone on-chain (approve + fundMilestone)...\n");
|
|
984
|
-
const data = await apiPost("/api/agent/escrow-fund", body);
|
|
985
|
-
print(data);
|
|
986
|
-
process.stderr.write(`\n✅ ${data.amountUSD} RLUSD locked on-chain. TX: ${data.txHash}\n\n`);
|
|
1095
|
+
// Removed in 2.0.0 (cascrow Phase 5): `escrow-fund --private-key` sent the key
|
|
1096
|
+
// to cascrow, which signed the deposit server-side. Kept as a command only to say
|
|
1097
|
+
// so: it sends NOTHING — no request, no argument — so a key typed on the command
|
|
1098
|
+
// line goes nowhere. It cannot un-type it, which is why the message tells the user
|
|
1099
|
+
// what a key that reached a shell history (or an older version of this CLI) is now.
|
|
1100
|
+
function cmdEscrowFundRemoved() {
|
|
1101
|
+
err(
|
|
1102
|
+
"escrow-fund was removed in cascrow-cli 2.0.0: cascrow never accepts, creates, stores or signs with private keys. " +
|
|
1103
|
+
"A $0 verification contract needs no funding — it is active once the builder has joined. A milestone with an amount " +
|
|
1104
|
+
"is a MilestoneEscrowV3 escrow in native XRP, funded by the payer with one transaction from their own machine: first link " +
|
|
1105
|
+
"the wallet you will fund from (GET /api/user/wallet/challenge, sign the message, POST /api/user/wallet), then run " +
|
|
1106
|
+
"npx cascrow-signer@0.1.0 fund --milestone <milestoneId>. If you ever passed a private key to this command (it is also in your shell history), " +
|
|
1107
|
+
"treat that key as exposed and move its funds to a new wallet. [CUSTODY_PATH_REMOVED]"
|
|
1108
|
+
);
|
|
987
1109
|
}
|
|
988
1110
|
|
|
989
1111
|
// Legacy two-step: upload proof → returns proofId → user runs `cascrow verify --proof <id>`
|
|
@@ -1031,7 +1153,8 @@ async function cmdJoin(args) {
|
|
|
1031
1153
|
if (!args.invite) err("--invite is required");
|
|
1032
1154
|
const data = await apiPost("/api/contracts/join", { inviteCode: args.invite });
|
|
1033
1155
|
print(data);
|
|
1034
|
-
|
|
1156
|
+
// `activated`: a $0 contract is active now; a money contract waits for its V3 offer.
|
|
1157
|
+
process.stderr.write(`\nJoined contract: ${data.contractId}\n${joinedLine(data)}\n\n`);
|
|
1035
1158
|
}
|
|
1036
1159
|
|
|
1037
1160
|
async function cmdHandoff(args) {
|
|
@@ -1071,9 +1194,9 @@ async function cmdCheckInvites() {
|
|
|
1071
1194
|
* verdict on cannot come apart (F-22).
|
|
1072
1195
|
*
|
|
1073
1196
|
* `review-queue` is the pull behind the `manual_review.required` webhook: a
|
|
1074
|
-
* webhook nobody received is a decision nobody makes, and cascrow
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1197
|
+
* webhook nobody received is a decision nobody makes, and cascrow completes an
|
|
1198
|
+
* undecided milestone after 48 h (agent contracts in MANUAL_AUTO/AUTO) or 14 days
|
|
1199
|
+
* (every other contract). Verification-only contracts: no money moves. `review` ships with it rather than later, because a list
|
|
1077
1200
|
* its reader cannot act on names a remedy they are unable to perform (F-72).
|
|
1078
1201
|
*/
|
|
1079
1202
|
async function cmdReviewQueue() {
|
|
@@ -1081,8 +1204,16 @@ async function cmdReviewQueue() {
|
|
|
1081
1204
|
const data = await apiGet("/api/agent/review-queue");
|
|
1082
1205
|
const contracts = data.contracts ?? [];
|
|
1083
1206
|
print(data);
|
|
1207
|
+
// Said in both branches: an empty v2 queue is exactly when a reader stops
|
|
1208
|
+
// looking, and a V3 claim window keeps running.
|
|
1209
|
+
const v3 =
|
|
1210
|
+
data.v3ClaimsPending === null
|
|
1211
|
+
? "Open MilestoneEscrowV3 claims could not be counted — run: cascrow v3-claims\n\n"
|
|
1212
|
+
: data.v3ClaimsPending > 0
|
|
1213
|
+
? `${data.v3ClaimsPending} MilestoneEscrowV3 claim(s) can still be objected to — run: cascrow v3-claims\n\n`
|
|
1214
|
+
: "";
|
|
1084
1215
|
if (contracts.length === 0) {
|
|
1085
|
-
process.stderr.write(
|
|
1216
|
+
process.stderr.write(`\nNo milestones are waiting for your verdict.\n\n${v3}`);
|
|
1086
1217
|
return;
|
|
1087
1218
|
}
|
|
1088
1219
|
const lines = contracts.map((c) => {
|
|
@@ -1093,15 +1224,100 @@ async function cmdReviewQueue() {
|
|
|
1093
1224
|
: pending.length > 1
|
|
1094
1225
|
? `${pending.length} milestones pending — pass --milestone <id>`
|
|
1095
1226
|
: "contract-level review — omit --milestone";
|
|
1096
|
-
return ` ${c.contractId}
|
|
1227
|
+
return ` ${c.contractId} (${ms})`;
|
|
1097
1228
|
}).join("\n");
|
|
1098
1229
|
const more = data.truncated ? `\n(${data.total} in total — this list is capped.)` : "";
|
|
1099
1230
|
process.stderr.write(
|
|
1100
1231
|
`\n${data.total} contract(s) waiting for your verdict:\n${lines}${more}\n\n` +
|
|
1101
|
-
"Decide with: cascrow review --contract <id> --decision APPROVE|REJECT [--milestone <id>]\n\n"
|
|
1232
|
+
"Decide with: cascrow review --contract <id> --decision APPROVE|REJECT [--milestone <id>]\n\n" +
|
|
1233
|
+
v3
|
|
1102
1234
|
);
|
|
1103
1235
|
}
|
|
1104
1236
|
|
|
1237
|
+
/**
|
|
1238
|
+
* The payer's open MilestoneEscrowV3 claims — `GET /api/agent/v3-claims`, the
|
|
1239
|
+
* pull behind the `claim.submitted` webhook. Print only: the action is the
|
|
1240
|
+
* payer's own `object(escrowId)` on chain, which cascrow never performs.
|
|
1241
|
+
*/
|
|
1242
|
+
async function cmdV3Claims() {
|
|
1243
|
+
if (!API_KEY) err("CASCROW_API_KEY is not set");
|
|
1244
|
+
const data = await apiGet("/api/agent/v3-claims");
|
|
1245
|
+
const claims = data.claims ?? [];
|
|
1246
|
+
print(data);
|
|
1247
|
+
if (claims.length === 0) {
|
|
1248
|
+
process.stderr.write("\nNo MilestoneEscrowV3 claim is waiting on you.\n\n");
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const lines = claims.map((c) =>
|
|
1252
|
+
` ${c.escrowId} on ${c.chainId}/${c.escrowContract} claim ${c.claimIndex}` +
|
|
1253
|
+
`${c.secondFunding ? " SECOND funding" : ""}${c.held ? " (held — verify on chain)" : ""} ` +
|
|
1254
|
+
(c.objectable === null ? "claim state unknown here — read getEscrow on chain" : c.objectable ? `object() before ${c.objectionDeadline}` : `window closed at ${c.objectionDeadline}`)
|
|
1255
|
+
).join("\n");
|
|
1256
|
+
const more = data.truncated ? "\n(This list is capped; more claims exist.)" : "";
|
|
1257
|
+
process.stderr.write(`\n${claims.length} claim(s):\n${lines}${more}\n\ncascrow cannot object for you — your own wallet calls object(escrowId) on the escrow contract.\n\n`);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// ─── MilestoneEscrowV3, remote half (design Rev. 10) ──────────────────────────
|
|
1261
|
+
// cascrow prepares and reads; the party signs and sends with its own key. This CLI
|
|
1262
|
+
// holds no key and has no dependency: `v3 sign …` runs the PINNED local signer
|
|
1263
|
+
// (V3_CLIENT_TEXT.signerPin) in a child process, with this terminal attached, so
|
|
1264
|
+
// its prompts and its checks are the signer's own.
|
|
1265
|
+
async function cmdV3(sub, args, rawArgs) {
|
|
1266
|
+
if (sub === "sign") {
|
|
1267
|
+
const child = spawn("npx", ["--yes", V3_CLIENT_TEXT.signerPin, ...rawArgs], { stdio: "inherit", shell: process.platform === "win32" });
|
|
1268
|
+
const code = await new Promise((resolve) => child.on("close", resolve));
|
|
1269
|
+
process.exit(typeof code === "number" ? code : 1);
|
|
1270
|
+
}
|
|
1271
|
+
if (!API_KEY) err("CASCROW_API_KEY is not set");
|
|
1272
|
+
switch (sub) {
|
|
1273
|
+
case "offer": {
|
|
1274
|
+
if (!args.milestone || !args["review-period-sec"]) err("--milestone and --review-period-sec are required");
|
|
1275
|
+
const body = {
|
|
1276
|
+
milestoneId: args.milestone,
|
|
1277
|
+
reviewPeriodSec: Number(args["review-period-sec"]),
|
|
1278
|
+
watcherDeclared: args.watcher === true || args.watcher === "true",
|
|
1279
|
+
...(args.arbiter ? { arbiter: args.arbiter } : {}),
|
|
1280
|
+
...(args["arbitration-period-sec"] ? { arbitrationPeriodSec: Number(args["arbitration-period-sec"]) } : {}),
|
|
1281
|
+
};
|
|
1282
|
+
print(await apiPost("/api/escrow/v3/offer", body));
|
|
1283
|
+
process.stderr.write(`\nOffer sent. After the builder accepts, fund it from your machine: cascrow v3 sign fund --milestone ${args.milestone}\n\n`);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
case "acceptance":
|
|
1287
|
+
if (!args.milestone) err("--milestone is required");
|
|
1288
|
+
print(await apiGet(`/api/escrow/v3/acceptance?milestoneId=${encodeURIComponent(args.milestone)}`));
|
|
1289
|
+
process.stderr.write("\nCheck every term — your signature binds you until acceptBy. To check and sign locally: cascrow v3 sign accept --milestone " + args.milestone + "\n\n");
|
|
1290
|
+
return;
|
|
1291
|
+
case "accept-submit":
|
|
1292
|
+
if (!args.offer || !args.signature) err("--offer and --signature are required");
|
|
1293
|
+
print(await apiPost("/api/escrow/v3/acceptance", { offerId: args.offer, signature: args.signature }));
|
|
1294
|
+
return;
|
|
1295
|
+
case "prepare": {
|
|
1296
|
+
if (!args.milestone || !args.action) err("--milestone and --action are required");
|
|
1297
|
+
const q = new URLSearchParams({ milestoneId: args.milestone, action: args.action });
|
|
1298
|
+
if (args["proof-hash"]) q.set("proofHash", args["proof-hash"]);
|
|
1299
|
+
if (args["reason-hash"]) q.set("reasonHash", args["reason-hash"]);
|
|
1300
|
+
if (args["new-deadline"]) q.set("newDeadline", String(args["new-deadline"]));
|
|
1301
|
+
print(await apiGet(`/api/escrow/v3/prepare?${q.toString()}`));
|
|
1302
|
+
process.stderr.write("\nUNSIGNED — cascrow does not sign, send or relay it. Rebuild data from the ABI and check to/chainId against your own configuration before signing (cascrow v3 sign … does).\n\n");
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
case "report-tx":
|
|
1306
|
+
if (!args.milestone || typeof args.tx !== "string" || !TX_HASH_RE.test(args.tx)) err(V3_CLIENT_TEXT.reportTxOnlyHash);
|
|
1307
|
+
print(await apiPost("/api/escrow/v3/sync", { milestoneId: args.milestone, txHash: args.tx }));
|
|
1308
|
+
return;
|
|
1309
|
+
case "attestation":
|
|
1310
|
+
if (!args.escrow || !args["claim-index"]) err("--escrow and --claim-index are required");
|
|
1311
|
+
print(await apiGet(`/api/escrow/v3/attestation?${new URLSearchParams({ escrowId: args.escrow, claimIndex: String(args["claim-index"]) }).toString()}`));
|
|
1312
|
+
return;
|
|
1313
|
+
case "claims":
|
|
1314
|
+
await cmdV3Claims();
|
|
1315
|
+
return;
|
|
1316
|
+
default:
|
|
1317
|
+
err(`unknown v3 command ${sub ?? "(none)"} — offer | acceptance | accept-submit | prepare | report-tx | attestation | claims | sign`);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1105
1321
|
async function cmdReview(args) {
|
|
1106
1322
|
if (!API_KEY) err("CASCROW_API_KEY is not set");
|
|
1107
1323
|
const contractId = args.contract;
|
|
@@ -1111,11 +1327,14 @@ async function cmdReview(args) {
|
|
|
1111
1327
|
const body = args.milestone ? { contractId, decision, milestoneId: args.milestone } : { contractId, decision };
|
|
1112
1328
|
const data = await apiPost("/api/contracts/review", body);
|
|
1113
1329
|
print(data);
|
|
1330
|
+
// The route answers `{ status }` and nothing else: a review only decides
|
|
1331
|
+
// verification-only ($0) milestones, and nothing moves money (a MilestoneEscrowV3
|
|
1332
|
+
// milestone is refused — its payer releases or objects on chain).
|
|
1114
1333
|
const what =
|
|
1115
1334
|
decision === "APPROVE"
|
|
1116
|
-
? "approved
|
|
1335
|
+
? "approved and completed (a verification-only contract: no money moves)"
|
|
1117
1336
|
: "rejected — the milestone is back with the builder, who can resubmit";
|
|
1118
|
-
process.stderr.write(`\nMilestone ${what}.${data.
|
|
1337
|
+
process.stderr.write(`\nMilestone ${what}.${data.status ? ` Contract status: ${data.status}.` : ""}\n\n`);
|
|
1119
1338
|
}
|
|
1120
1339
|
|
|
1121
1340
|
async function cmdMe() {
|
|
@@ -1140,8 +1359,8 @@ try {
|
|
|
1140
1359
|
case "verify-work": await cmdVerifyWork(args); break;
|
|
1141
1360
|
case "verify": await cmdVerify(args); break;
|
|
1142
1361
|
case "create": await cmdCreate(args); break;
|
|
1143
|
-
case "fund":
|
|
1144
|
-
case "escrow-fund":
|
|
1362
|
+
case "fund": cmdFundRemoved(); break;
|
|
1363
|
+
case "escrow-fund": cmdEscrowFundRemoved(); break;
|
|
1145
1364
|
case "submit": await cmdSubmit(args); break;
|
|
1146
1365
|
case "status": await cmdStatus(args); break;
|
|
1147
1366
|
case "get": await cmdGet(args); break;
|
|
@@ -1150,6 +1369,8 @@ try {
|
|
|
1150
1369
|
case "check-invites": await cmdCheckInvites(); break;
|
|
1151
1370
|
case "review-queue": await cmdReviewQueue(); break;
|
|
1152
1371
|
case "review": await cmdReview(args); break;
|
|
1372
|
+
case "v3-claims": await cmdV3Claims(); break;
|
|
1373
|
+
case "v3": await cmdV3(rest[0], parseArgs(rest.slice(1)), rest.slice(1)); break;
|
|
1153
1374
|
case "me": await cmdMe(); break;
|
|
1154
1375
|
default:
|
|
1155
1376
|
console.error(`Unknown command: ${command}\n`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cascrow-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI for Cascrow — trust infrastructure for the agent economy: AI verification, on-chain reputation, and milestone escrow
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "CLI for Cascrow — trust infrastructure for the agent economy: AI verification, on-chain reputation, and non-custodial milestone escrow (MilestoneEscrowV3, native XRP)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|