@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.
Files changed (3) hide show
  1. package/README.md +9 -6
  2. package/dist/index.js +78 -6
  3. 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
- Hash, sign, and submit a provenance record for AI-generated content. Requires an API key, generator ID, and private key.
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
- | `--private-key <key>` | Override ML-DSA private key (base64) |
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`, `CERTIVU_PRIVATE_KEY`, `CERTIVU_BASE_URL`) override the config file.
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]), "image");
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 watermarkedContent = new Uint8Array(await res.arrayBuffer());
153
- return { token, record_id, watermarkedContent };
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.watermarkedContent);
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": "1.2.1",
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
- "type": "git",
9
- "url": "https://github.com/art-emini/certivu"
10
- },
11
- "bin": {
12
- "certivu": "./bin/certivu"
13
- },
14
- "files": ["bin", "dist", "README.md"],
15
- "scripts": {
16
- "dev": "bun run src/index.ts",
17
- "build": "tsup",
18
- "typecheck": "tsc --noEmit",
19
- "lint": "biome check src"
20
- },
21
- "devDependencies": {
22
- "@biomejs/biome": "^1.9.4",
23
- "@certivu/crypto": "0.0.1",
24
- "@certivu/sdk": "1.0.0",
25
- "@types/node": "^20.0.0",
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
  }