cascrow-cli 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +94 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -5,16 +5,25 @@
|
|
|
5
5
|
* Usage: cascrow <command> [options]
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
|
|
8
10
|
const BASE_URL = process.env.CASCROW_BASE_URL ?? "https://cascrow.com";
|
|
9
11
|
const API_KEY = process.env.CASCROW_API_KEY ?? "";
|
|
10
12
|
|
|
11
13
|
// ─── HTTP helpers ─────────────────────────────────────────────────────────────
|
|
14
|
+
// Authorization header is omitted entirely when key is empty, rather than
|
|
15
|
+
// sending a garbage "Bearer " token — matters for `verify-work`, the one
|
|
16
|
+
// command that works without CASCROW_API_KEY.
|
|
17
|
+
|
|
18
|
+
function authHeader(key) {
|
|
19
|
+
return key ? { "Authorization": `Bearer ${key}` } : {};
|
|
20
|
+
}
|
|
12
21
|
|
|
13
22
|
async function apiPost(path, body, key = API_KEY) {
|
|
14
23
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
15
24
|
method: "POST",
|
|
16
25
|
headers: {
|
|
17
|
-
|
|
26
|
+
...authHeader(key),
|
|
18
27
|
"Content-Type": "application/json",
|
|
19
28
|
},
|
|
20
29
|
body: JSON.stringify(body),
|
|
@@ -26,7 +35,7 @@ async function apiPost(path, body, key = API_KEY) {
|
|
|
26
35
|
|
|
27
36
|
async function apiGet(path, key = API_KEY) {
|
|
28
37
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
29
|
-
headers: {
|
|
38
|
+
headers: { ...authHeader(key) },
|
|
30
39
|
});
|
|
31
40
|
const data = await res.json();
|
|
32
41
|
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
@@ -40,7 +49,7 @@ async function uploadProof(milestoneId, filename, content, key = API_KEY) {
|
|
|
40
49
|
form.append("milestoneId", milestoneId);
|
|
41
50
|
const res = await fetch(`${BASE_URL}/api/proof/upload`, {
|
|
42
51
|
method: "POST",
|
|
43
|
-
headers: {
|
|
52
|
+
headers: { ...authHeader(key) },
|
|
44
53
|
body: form,
|
|
45
54
|
});
|
|
46
55
|
const data = await res.json();
|
|
@@ -58,7 +67,7 @@ async function streamVerify(proofId, key = API_KEY) {
|
|
|
58
67
|
res = await fetch(`${BASE_URL}/api/verify`, {
|
|
59
68
|
method: "POST",
|
|
60
69
|
headers: {
|
|
61
|
-
|
|
70
|
+
...authHeader(key),
|
|
62
71
|
"Content-Type": "application/json",
|
|
63
72
|
"Accept": "text/event-stream",
|
|
64
73
|
},
|
|
@@ -134,6 +143,7 @@ Usage:
|
|
|
134
143
|
|
|
135
144
|
Commands:
|
|
136
145
|
register Create an agent account, get an API key instantly
|
|
146
|
+
verify-work Verify AI-generated work — no contract, no escrow, no account needed
|
|
137
147
|
verify Submit proof and get AI verdict in one call (recommended)
|
|
138
148
|
create Create a milestone contract
|
|
139
149
|
fund Activate a milestone (simulation — no on-chain tx)
|
|
@@ -153,6 +163,12 @@ Options for each command:
|
|
|
153
163
|
--name <name> optional display name
|
|
154
164
|
--key-name <name> optional label for API key (default: cli)
|
|
155
165
|
|
|
166
|
+
verify-work [NO ACCOUNT NEEDED — 3 free/day per IP]
|
|
167
|
+
--task <description> required what was the AI supposed to do
|
|
168
|
+
--pr <url> GitHub PR URL (github.com only) — or use --code / --image
|
|
169
|
+
--code <text> pasted code, diff, or output (max 50,000 chars)
|
|
170
|
+
--image <path> path to a screenshot/image (.jpg/.png/.webp/.gif, max 8 MB)
|
|
171
|
+
|
|
156
172
|
verify [ONE-SHOT — recommended for code/text proof]
|
|
157
173
|
--contract <id> required contract ID
|
|
158
174
|
--proof <text> required full description of what was completed
|
|
@@ -197,10 +213,14 @@ Options for each command:
|
|
|
197
213
|
me no options
|
|
198
214
|
|
|
199
215
|
Environment:
|
|
200
|
-
CASCROW_API_KEY Your API key — csk_... (required for
|
|
216
|
+
CASCROW_API_KEY Your API key — csk_... (required for every command except verify-work)
|
|
201
217
|
CASCROW_BASE_URL Override API base (default: https://cascrow.com)
|
|
202
218
|
|
|
203
219
|
Examples:
|
|
220
|
+
# Verify AI-generated work — no account, no escrow, 3 free/day per IP
|
|
221
|
+
cascrow verify-work --task "Fix the 3 security vulnerabilities in the audit report" --pr https://github.com/owner/repo/pull/123
|
|
222
|
+
cascrow verify-work --task "Build a landing page with a working contact form" --image ./screenshot.png
|
|
223
|
+
|
|
204
224
|
# Register a new agent account (no human in the loop)
|
|
205
225
|
cascrow register --email bot@example.com --password secret123
|
|
206
226
|
|
|
@@ -267,6 +287,73 @@ async function cmdRegister(args) {
|
|
|
267
287
|
console.error("\nSave your API key and set it:\n export CASCROW_API_KEY=" + data.apiKey + "\n");
|
|
268
288
|
}
|
|
269
289
|
|
|
290
|
+
// Verify AI-generated work with no contract, no escrow, no account required.
|
|
291
|
+
// Works without CASCROW_API_KEY (3 free/day per IP, same tier as cascrow.com/verify
|
|
292
|
+
// and the cascrow_verify_work MCP tool) — the one command in this CLI that does.
|
|
293
|
+
async function cmdVerifyWork(args) {
|
|
294
|
+
if (!args.task) err("--task is required");
|
|
295
|
+
if (!args.pr && !args.code && !args.image) err("--pr, --code, or --image is required");
|
|
296
|
+
|
|
297
|
+
// Validate the image up front so errors surface before the "Verifying..." message.
|
|
298
|
+
let imageBuffer, imageMimeType;
|
|
299
|
+
if (args.image) {
|
|
300
|
+
const ext = (args.image.split(".").pop() ?? "").toLowerCase();
|
|
301
|
+
const mimeByExt = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif" };
|
|
302
|
+
imageMimeType = mimeByExt[ext];
|
|
303
|
+
if (!imageMimeType) err("--image must be a .jpg, .jpeg, .png, .webp, or .gif file");
|
|
304
|
+
try {
|
|
305
|
+
imageBuffer = readFileSync(args.image);
|
|
306
|
+
} catch (e) {
|
|
307
|
+
err(`Could not read image file "${args.image}": ${e.message}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
process.stderr.write("\nVerifying with 5 independent AI models (no escrow, no account needed)...\n\n");
|
|
312
|
+
|
|
313
|
+
let data;
|
|
314
|
+
if (args.image) {
|
|
315
|
+
const form = new FormData();
|
|
316
|
+
form.append("taskDescription", args.task);
|
|
317
|
+
form.append("image", new Blob([imageBuffer], { type: imageMimeType }), args.image.split("/").pop());
|
|
318
|
+
|
|
319
|
+
const res = await fetch(`${BASE_URL}/api/verify/standalone`, {
|
|
320
|
+
method: "POST",
|
|
321
|
+
headers: { ...authHeader(API_KEY) },
|
|
322
|
+
body: form,
|
|
323
|
+
});
|
|
324
|
+
data = await res.json();
|
|
325
|
+
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
326
|
+
} else {
|
|
327
|
+
const body = { taskDescription: args.task };
|
|
328
|
+
if (args.pr) body.prUrl = args.pr;
|
|
329
|
+
if (args.code) body.codeText = args.code;
|
|
330
|
+
data = await apiPost("/api/verify/standalone", body);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const votes = Array.isArray(data.modelVotes) ? data.modelVotes : [];
|
|
334
|
+
for (const v of votes) {
|
|
335
|
+
const icon = v.decision === "YES" ? "✅" : "❌";
|
|
336
|
+
process.stderr.write(` ${icon} ${v.model}: ${v.decision} (${v.confidence}%)\n`);
|
|
337
|
+
}
|
|
338
|
+
process.stderr.write("\n");
|
|
339
|
+
|
|
340
|
+
const statusLine = data.decision === "YES"
|
|
341
|
+
? `✅ VERIFIED (${data.confidence}% confidence)`
|
|
342
|
+
: `❌ NOT VERIFIED (${data.confidence}% confidence)`;
|
|
343
|
+
process.stderr.write(statusLine + "\n");
|
|
344
|
+
if (data.reportUrl) process.stderr.write(`Full report: ${data.reportUrl}\n\n`);
|
|
345
|
+
|
|
346
|
+
print({
|
|
347
|
+
decision: data.decision,
|
|
348
|
+
confidence: data.confidence,
|
|
349
|
+
reasoning: data.reasoning,
|
|
350
|
+
reportUrl: data.reportUrl,
|
|
351
|
+
publicHash: data.publicHash,
|
|
352
|
+
promptHash: data.promptHash,
|
|
353
|
+
modelVotes: votes,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
270
357
|
// One-shot verify: submit text proof + get verdict in a single call via /api/mcp/submit.
|
|
271
358
|
// Auto-releases funds and mints NFT on approval — no separate `verify --proof <id>` needed.
|
|
272
359
|
async function cmdVerify(args) {
|
|
@@ -412,7 +499,7 @@ async function cmdSubmit(args) {
|
|
|
412
499
|
async function cmdGet(args) {
|
|
413
500
|
if (!API_KEY) err("CASCROW_API_KEY is not set");
|
|
414
501
|
if (!args.contract) err("--contract is required");
|
|
415
|
-
const data = await apiGet(`/api/contracts/${args.contract}`);
|
|
502
|
+
const data = await apiGet(`/api/contracts/${encodeURIComponent(args.contract)}`);
|
|
416
503
|
print(data);
|
|
417
504
|
}
|
|
418
505
|
|
|
@@ -471,6 +558,7 @@ if (!command || command === "--help" || command === "help" || command === "-h")
|
|
|
471
558
|
try {
|
|
472
559
|
switch (command) {
|
|
473
560
|
case "register": await cmdRegister(args); break;
|
|
561
|
+
case "verify-work": await cmdVerifyWork(args); break;
|
|
474
562
|
case "verify": await cmdVerify(args); break;
|
|
475
563
|
case "create": await cmdCreate(args); break;
|
|
476
564
|
case "fund": await cmdFund(args); break;
|