@certivu/cli 1.2.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/index.js +78 -6
- package/package.json +20 -23
package/README.md
CHANGED
|
@@ -18,10 +18,11 @@ certivu --version
|
|
|
18
18
|
# Save your API key
|
|
19
19
|
certivu config set api-key ctv_key_...
|
|
20
20
|
certivu config set generator-id your-generator-id
|
|
21
|
-
certivu config set private-key BASE64_PRIVATE_KEY
|
|
22
21
|
|
|
23
|
-
# Sign a file
|
|
22
|
+
# Sign a file — images, audio, or text; format auto-detected
|
|
24
23
|
certivu sign ./output.jpg --model stable-diffusion-xl
|
|
24
|
+
certivu sign ./podcast.wav --model whisper-v3
|
|
25
|
+
certivu sign ./report.pdf --model gpt-4o --format text
|
|
25
26
|
|
|
26
27
|
# Verify a file
|
|
27
28
|
certivu verify ./output.jpg
|
|
@@ -34,20 +35,23 @@ certivu status ctv_7f3kx9mq2...
|
|
|
34
35
|
|
|
35
36
|
### `certivu sign <file>`
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
Sign AI-generated content. Signing, watermarking, and ML-DSA are all server-side — no private key required locally. Accepts images (JPEG/PNG/WebP), audio (MP3/FLAC/WAV), and text (PDF/HTML/plain text). Format is auto-detected from magic bytes; use `--format` to override.
|
|
38
39
|
|
|
39
40
|
```
|
|
40
41
|
certivu sign ./output.jpg --model stable-diffusion-xl
|
|
41
42
|
✓ Signed
|
|
42
43
|
Token ctv_7f3kx9mq2...
|
|
43
44
|
Record ID rec-00000000-...
|
|
45
|
+
Format image
|
|
46
|
+
Output ./output.signed.jpg
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
| Flag | Description |
|
|
47
50
|
|------|-------------|
|
|
48
51
|
| `--model <name>` | AI model name — required |
|
|
52
|
+
| `--format <type>` | `image`, `audio`, or `text` — auto-detected if omitted |
|
|
49
53
|
| `--generator-id <id>` | Override generator ID |
|
|
50
|
-
| `--
|
|
54
|
+
| `--output <path>` | Output file path (default: `<input>.signed.<ext>`) |
|
|
51
55
|
| `--api-key <key>` | Override API key |
|
|
52
56
|
|
|
53
57
|
### `certivu verify <file>`
|
|
@@ -78,11 +82,10 @@ Look up a token without re-uploading the file.
|
|
|
78
82
|
```bash
|
|
79
83
|
certivu config set api-key ctv_key_abc123
|
|
80
84
|
certivu config set generator-id <uuid>
|
|
81
|
-
certivu config set private-key <base64>
|
|
82
85
|
certivu config get
|
|
83
86
|
```
|
|
84
87
|
|
|
85
|
-
Config is stored at `~/.config/certivu/config.json`. Environment variables (`CERTIVU_API_KEY`, `CERTIVU_GENERATOR_ID`, `
|
|
88
|
+
Config is stored at `~/.config/certivu/config.json`. Environment variables (`CERTIVU_API_KEY`, `CERTIVU_GENERATOR_ID`, `CERTIVU_BASE_URL`) override the config file.
|
|
86
89
|
|
|
87
90
|
## Requirements
|
|
88
91
|
|
package/dist/index.js
CHANGED
|
@@ -135,9 +135,11 @@ function toUint8Array(content) {
|
|
|
135
135
|
async function signContent(baseUrl, apiKey, input) {
|
|
136
136
|
const bytes = toUint8Array(input.content);
|
|
137
137
|
const form = new FormData();
|
|
138
|
-
form.append("image", new Blob([bytes]), "
|
|
138
|
+
form.append("image", new Blob([bytes]), "content");
|
|
139
139
|
form.append("model", input.model);
|
|
140
140
|
form.append("generator_id", input.generatorId);
|
|
141
|
+
if (input.format)
|
|
142
|
+
form.append("format", input.format);
|
|
141
143
|
const res = await fetch(`${baseUrl}/v1/sign`, {
|
|
142
144
|
method: "POST",
|
|
143
145
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
@@ -149,8 +151,15 @@ async function signContent(baseUrl, apiKey, input) {
|
|
|
149
151
|
}
|
|
150
152
|
const token = res.headers.get("X-Certivu-Token") ?? "";
|
|
151
153
|
const record_id = res.headers.get("X-Certivu-Record-Id") ?? "";
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
+
const formatHeader = res.headers.get("X-Certivu-Format");
|
|
155
|
+
const signedContent = new Uint8Array(await res.arrayBuffer());
|
|
156
|
+
return {
|
|
157
|
+
token,
|
|
158
|
+
record_id,
|
|
159
|
+
signedContent,
|
|
160
|
+
watermarkedContent: signedContent,
|
|
161
|
+
...formatHeader ? { format: formatHeader } : {}
|
|
162
|
+
};
|
|
154
163
|
}
|
|
155
164
|
function toUint8Array2(content) {
|
|
156
165
|
if (typeof content === "string")
|
|
@@ -234,11 +243,71 @@ var CertivuClient = class {
|
|
|
234
243
|
}
|
|
235
244
|
return res.json();
|
|
236
245
|
}
|
|
246
|
+
async getAnalyticsOverview(days) {
|
|
247
|
+
const url = new URL(`${this.baseUrl}/v1/analytics/overview`);
|
|
248
|
+
if (days !== void 0)
|
|
249
|
+
url.searchParams.set("days", String(days));
|
|
250
|
+
const res = await fetch(url.toString(), { headers: { Authorization: `Bearer ${this.apiKey}` } });
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
const err2 = await res.json().catch(() => ({}));
|
|
253
|
+
throw new Error(err2.error ?? `HTTP ${res.status}`);
|
|
254
|
+
}
|
|
255
|
+
return res.json();
|
|
256
|
+
}
|
|
257
|
+
async getRecordAnalytics(recordId) {
|
|
258
|
+
const res = await fetch(`${this.baseUrl}/v1/analytics/records/${encodeURIComponent(recordId)}`, {
|
|
259
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok) {
|
|
262
|
+
const err2 = await res.json().catch(() => ({}));
|
|
263
|
+
throw new Error(err2.error ?? `HTTP ${res.status}`);
|
|
264
|
+
}
|
|
265
|
+
return res.json();
|
|
266
|
+
}
|
|
267
|
+
async listWebhooks() {
|
|
268
|
+
const res = await fetch(`${this.baseUrl}/v1/webhooks`, {
|
|
269
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
270
|
+
});
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
const err2 = await res.json().catch(() => ({}));
|
|
273
|
+
throw new Error(err2.error ?? `HTTP ${res.status}`);
|
|
274
|
+
}
|
|
275
|
+
return res.json();
|
|
276
|
+
}
|
|
277
|
+
async createWebhook(input) {
|
|
278
|
+
const res = await fetch(`${this.baseUrl}/v1/webhooks`, {
|
|
279
|
+
method: "POST",
|
|
280
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` },
|
|
281
|
+
body: JSON.stringify(input)
|
|
282
|
+
});
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
const err2 = await res.json().catch(() => ({}));
|
|
285
|
+
throw new Error(err2.error ?? `HTTP ${res.status}`);
|
|
286
|
+
}
|
|
287
|
+
return res.json();
|
|
288
|
+
}
|
|
289
|
+
async deleteWebhook(webhookId) {
|
|
290
|
+
const res = await fetch(`${this.baseUrl}/v1/webhooks/${encodeURIComponent(webhookId)}`, {
|
|
291
|
+
method: "DELETE",
|
|
292
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
293
|
+
});
|
|
294
|
+
if (!res.ok) {
|
|
295
|
+
const err2 = await res.json().catch(() => ({}));
|
|
296
|
+
throw new Error(err2.error ?? `HTTP ${res.status}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
237
299
|
};
|
|
238
300
|
|
|
239
301
|
// src/commands/sign.ts
|
|
302
|
+
function inferFormat(filePath) {
|
|
303
|
+
const ext = (0, import_node_path2.extname)(filePath).toLowerCase();
|
|
304
|
+
if ([".jpg", ".jpeg", ".png", ".webp"].includes(ext)) return "image";
|
|
305
|
+
if ([".mp3", ".flac", ".wav", ".ogg", ".aac", ".m4a", ".aiff"].includes(ext)) return "audio";
|
|
306
|
+
if ([".pdf", ".docx", ".html", ".htm", ".txt", ".md"].includes(ext)) return "text";
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
240
309
|
async function signCommand(filePath, flags) {
|
|
241
|
-
if (!filePath) die("Usage: certivu sign <file> --model <model>");
|
|
310
|
+
if (!filePath) die("Usage: certivu sign <file> --model <model> [--format image|audio|text]");
|
|
242
311
|
const config = await loadConfig();
|
|
243
312
|
const apiKey = flags.apiKey ?? config.apiKey;
|
|
244
313
|
const generatorId = flags.generatorId ?? config.generatorId;
|
|
@@ -250,6 +319,7 @@ async function signCommand(filePath, flags) {
|
|
|
250
319
|
} catch {
|
|
251
320
|
die(`Cannot read file: ${filePath}`);
|
|
252
321
|
}
|
|
322
|
+
const format = flags.format ?? inferFormat(filePath);
|
|
253
323
|
const clientConfig = {
|
|
254
324
|
apiKey,
|
|
255
325
|
generatorId,
|
|
@@ -258,20 +328,21 @@ async function signCommand(filePath, flags) {
|
|
|
258
328
|
const client = new CertivuClient(clientConfig);
|
|
259
329
|
let result;
|
|
260
330
|
try {
|
|
261
|
-
result = await client.sign({ content, model: flags.model, generatorId });
|
|
331
|
+
result = await client.sign({ content, model: flags.model, generatorId, ...format ? { format } : {} });
|
|
262
332
|
} catch (e) {
|
|
263
333
|
const msg = e instanceof Error ? e.message : String(e);
|
|
264
334
|
die(`Sign failed: ${msg}`);
|
|
265
335
|
}
|
|
266
336
|
const outPath = flags.output ?? (filePath.replace(/(\.[^.]+)$/, ".signed$1") || `${filePath}.signed${(0, import_node_path2.extname)(filePath)}`);
|
|
267
337
|
try {
|
|
268
|
-
(0, import_node_fs2.writeFileSync)(outPath, result.
|
|
338
|
+
(0, import_node_fs2.writeFileSync)(outPath, result.signedContent);
|
|
269
339
|
} catch {
|
|
270
340
|
die(`Could not write output file: ${outPath}`);
|
|
271
341
|
}
|
|
272
342
|
console.log(ok("Signed"));
|
|
273
343
|
row("Token", result.token);
|
|
274
344
|
row("Record ID", result.record_id);
|
|
345
|
+
row("Format", result.format ?? format ?? "auto-detected");
|
|
275
346
|
row("Output", outPath);
|
|
276
347
|
if (result.deduplicated) {
|
|
277
348
|
console.log(" (content already signed \u2014 existing token returned, no quota consumed)");
|
|
@@ -374,6 +445,7 @@ ${bold("Commands:")}
|
|
|
374
445
|
|
|
375
446
|
${bold("Sign flags:")}
|
|
376
447
|
--model <name> AI model name, e.g. stable-diffusion-xl ${dim("(required)")}
|
|
448
|
+
--format <fmt> Content format: image | audio | text ${dim("(auto-detected if omitted)")}
|
|
377
449
|
--generator-id <id> Override generator ID
|
|
378
450
|
--private-key <key> Override ML-DSA private key (base64)
|
|
379
451
|
--api-key <key> Override API key
|
package/package.json
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certivu/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Certivu CLI — sign and verify AI-generated content",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://certivu.ai",
|
|
7
|
-
"repository": {
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"tsup": "^8.0.0",
|
|
27
|
-
"typescript": "^5.7.3"
|
|
28
|
-
}
|
|
7
|
+
"repository": { "type": "git", "url": "https://github.com/art-emini/certivu" },
|
|
8
|
+
"bin": {
|
|
9
|
+
"certivu": "./bin/certivu"
|
|
10
|
+
},
|
|
11
|
+
"files": ["bin", "dist", "README.md"],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "bun run src/index.ts",
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "biome check src"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@biomejs/biome": "^1.9.4",
|
|
20
|
+
"@certivu/crypto": "workspace:*",
|
|
21
|
+
"@certivu/sdk": "workspace:*",
|
|
22
|
+
"@types/node": "^20.0.0",
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.7.3"
|
|
25
|
+
}
|
|
29
26
|
}
|