@demarker/client 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,26 @@
1
- # Temporary Holding Version
1
+ # @demarker/client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed client for the [Demarker](https://demarker.io/developers) API: identify what an AI image carries, then clean it. No runtime dependencies, Node 20+.
4
+
5
+ ```ts
6
+ import { DemarkerClient } from "@demarker/client";
7
+ import { readFile } from "node:fs/promises";
8
+
9
+ const client = new DemarkerClient({ apiKey: process.env.DEMARKER_API_KEY });
10
+ const bytes = new Uint8Array(await readFile("image.png"));
11
+
12
+ // Free. Without an apiKey, use client.scanBytes() (keyless, rate-limited).
13
+ const job = await client.identifyBytes(bytes, "image.png");
14
+ console.log(job.offers);
15
+
16
+ // Spends credits. maxCredits is required; nothing is charged above it.
17
+ // rightsAttested must come from a real confirmation by the person who owns
18
+ // or may edit the image. There is no default.
19
+ const { image } = await client.clean({
20
+ bytes, filename: "image.png", maxCredits: 1, rightsAttested: true,
21
+ });
22
+ ```
23
+
24
+ Base URL defaults to `https://demarker.io/api/v1`; override with `baseUrl` or `DEMARKER_API_URL`. Errors are `DemarkerApiError` (API envelope: `code`, `status`, `details`) or `DemarkerClientError` (a rule enforced before any request, such as `OVER_CREDIT_CAP`).
25
+
26
+ Create an API key at https://demarker.io/app/settings/api. See the [OpenAPI spec](https://demarker.io/api/v1/openapi).
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The rights statement a person must confirm before a clean. Same wording as the
3
+ * web's AttestationCheckbox; the server stores the version with the job.
4
+ * Callers (CLI, MCP) must get a human's yes. It is never a default.
5
+ */
6
+ export declare const ATTESTATION_VERSION = "v1";
7
+ export declare const ATTESTATION_TEXT = "This is my image or I have the right to edit it, and I have permission from anyone shown. I will follow the terms of the tool that made it, and I am responsible for any labelling the law or the place I publish requires. I am 18 or older, and I agree to the Terms (https://demarker.io/legal/terms).";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The rights statement a person must confirm before a clean. Same wording as the
3
+ * web's AttestationCheckbox; the server stores the version with the job.
4
+ * Callers (CLI, MCP) must get a human's yes. It is never a default.
5
+ */
6
+ export const ATTESTATION_VERSION = "v1";
7
+ export const ATTESTATION_TEXT = "This is my image or I have the right to edit it, and I have permission from anyone shown. I will follow the terms of the tool that made it, and I am responsible for any labelling the law or the place I publish requires. I am 18 or older, and I agree to the Terms (https://demarker.io/legal/terms).";
8
+ //# sourceMappingURL=attestation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestation.js","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACxC,MAAM,CAAC,MAAM,gBAAgB,GAC5B,2SAA2S,CAAC"}
@@ -0,0 +1,97 @@
1
+ import type { Balance, Job, PendingIdentify, Pricing, ProcessOptions, ScanTicket, UploadTicket } from "./types.ts";
2
+ export declare const DEFAULT_BASE_URL = "https://demarker.io/api/v1";
3
+ export interface ClientOptions {
4
+ /**
5
+ * `sk_live_...` key from /app/settings/api. Optional: without it only the
6
+ * keyless `scan` methods and `getPricing` work.
7
+ */
8
+ apiKey?: string;
9
+ /** Defaults to DEMARKER_API_URL, then https://demarker.io/api/v1. */
10
+ baseUrl?: string;
11
+ fetch?: typeof fetch;
12
+ userAgent?: string;
13
+ }
14
+ export interface WaitOptions {
15
+ intervalMs?: number;
16
+ timeoutMs?: number;
17
+ signal?: AbortSignal;
18
+ onProgress?: (job: Job) => void;
19
+ }
20
+ export interface CleanOptions extends WaitOptions {
21
+ bytes: Uint8Array;
22
+ filename: string;
23
+ tier?: "GPU" | "QUICK";
24
+ /**
25
+ * Mandatory ceiling. The API has no one-shot job, so the cap is checked
26
+ * against the identify response's offer before any credit is spent.
27
+ */
28
+ maxCredits: number;
29
+ rightsAttested: boolean;
30
+ keepResolution?: boolean;
31
+ vendorHint?: string;
32
+ force?: boolean;
33
+ marks?: string[];
34
+ secondAttestation?: string;
35
+ idempotencyKey?: string;
36
+ onIdentified?: (job: Job) => void;
37
+ }
38
+ export interface CleanResult {
39
+ job: Job;
40
+ image: Uint8Array;
41
+ creditsCharged: number;
42
+ }
43
+ export declare class DemarkerClient {
44
+ readonly baseUrl: string;
45
+ private readonly apiKey;
46
+ /** False for a keyless client: only `scan*` and `getPricing` work. */
47
+ get hasApiKey(): boolean;
48
+ private readonly doFetch;
49
+ private readonly userAgent;
50
+ constructor(options: ClientOptions);
51
+ createUpload(filename: string): Promise<UploadTicket>;
52
+ /** PUT the bytes to the presigned URL. Not an API call, so no bearer token. */
53
+ putUpload(ticket: Pick<UploadTicket, "uploadUrl" | "contentType">, bytes: Uint8Array): Promise<void>;
54
+ /** Returns the settled job, or a pending stub when identify is still running (202). */
55
+ identify(uploadKey: string, idempotencyKey?: string): Promise<Job | PendingIdentify>;
56
+ getJob(id: string): Promise<Job>;
57
+ listJobs(params?: {
58
+ status?: string;
59
+ limit?: number;
60
+ offset?: number;
61
+ }): Promise<{
62
+ jobs: Job[];
63
+ total: number;
64
+ }>;
65
+ deleteJob(id: string): Promise<{
66
+ deleted: boolean;
67
+ creditsRefunded: number;
68
+ }>;
69
+ /**
70
+ * Debits the offer. `rightsAttested` must be an explicit true. A retry after a
71
+ * lost response gets 409 JOB_NOT_IDENTIFIED, so a debit is never doubled.
72
+ */
73
+ processJob(id: string, options: ProcessOptions): Promise<Job>;
74
+ getBalance(): Promise<Balance>;
75
+ getPricing(): Promise<Pricing>;
76
+ /** Upload an image for the free identify step. Returns the job id and a secret claim token. */
77
+ scan(bytes: Uint8Array, filename: string): Promise<ScanTicket>;
78
+ getScan(id: string, claimToken: string): Promise<Job>;
79
+ /** Keyless scan and wait for the identify result. */
80
+ scanBytes(bytes: Uint8Array, filename: string, options?: WaitOptions): Promise<Job>;
81
+ /** Poll until `until` returns true or the job is terminal. */
82
+ waitForJob(id: string, until: (job: Job) => boolean, options?: WaitOptions): Promise<Job>;
83
+ /** Upload, PUT and identify (free). Polls if the API answers 202. */
84
+ identifyBytes(bytes: Uint8Array, filename: string, options?: WaitOptions & {
85
+ idempotencyKey?: string;
86
+ }): Promise<Job>;
87
+ /** Download a result URL (valid for one hour). */
88
+ download(url: string): Promise<Uint8Array>;
89
+ /**
90
+ * Identify, check the price against `maxCredits`, process, wait, download.
91
+ * Spends nothing unless the offer is at or under the cap.
92
+ */
93
+ clean(options: CleanOptions): Promise<CleanResult>;
94
+ private request;
95
+ }
96
+ /** True once identify has produced a result (not UPLOADED / IDENTIFYING). */
97
+ export declare function isSettledIdentify(j: Job | PendingIdentify): j is Job;
package/dist/client.js ADDED
@@ -0,0 +1,266 @@
1
+ import { DemarkerApiError, DemarkerClientError } from "./errors.js";
2
+ export const DEFAULT_BASE_URL = "https://demarker.io/api/v1";
3
+ const TERMINAL = ["COMPLETED", "FAILED", "REFUSED", "EXPIRED"];
4
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
5
+ if (signal?.aborted) {
6
+ return reject(signal.reason);
7
+ }
8
+ const t = setTimeout(resolve, ms);
9
+ signal?.addEventListener("abort", () => {
10
+ clearTimeout(t);
11
+ reject(signal.reason);
12
+ }, { once: true });
13
+ });
14
+ export class DemarkerClient {
15
+ baseUrl;
16
+ apiKey;
17
+ /** False for a keyless client: only `scan*` and `getPricing` work. */
18
+ get hasApiKey() {
19
+ return Boolean(this.apiKey);
20
+ }
21
+ doFetch;
22
+ userAgent;
23
+ constructor(options) {
24
+ this.apiKey = options.apiKey;
25
+ this.baseUrl = (options.baseUrl ??
26
+ process.env.DEMARKER_API_URL ??
27
+ DEFAULT_BASE_URL).replace(/\/+$/, "");
28
+ this.doFetch = options.fetch ?? fetch;
29
+ this.userAgent = options.userAgent ?? "@demarker/client";
30
+ }
31
+ // ----- HTTP methods: one per endpoint, no file I/O -----
32
+ createUpload(filename) {
33
+ return this.request("POST", "/upload", { filename });
34
+ }
35
+ /** PUT the bytes to the presigned URL. Not an API call, so no bearer token. */
36
+ async putUpload(ticket, bytes) {
37
+ const res = await this.doFetch(ticket.uploadUrl, {
38
+ method: "PUT",
39
+ headers: { "Content-Type": ticket.contentType },
40
+ body: bytes,
41
+ });
42
+ if (!res.ok) {
43
+ throw new DemarkerClientError("UPLOAD_FAILED", `Upload failed with HTTP ${res.status}.`, { status: res.status });
44
+ }
45
+ }
46
+ /** Returns the settled job, or a pending stub when identify is still running (202). */
47
+ identify(uploadKey, idempotencyKey) {
48
+ return this.request("POST", "/identify", { uploadKey }, idempotencyKey ? { "Idempotency-Key": idempotencyKey } : undefined);
49
+ }
50
+ getJob(id) {
51
+ return this.request("GET", `/jobs/${encodeURIComponent(id)}`);
52
+ }
53
+ listJobs(params = {}) {
54
+ const q = new URLSearchParams();
55
+ for (const [k, v] of Object.entries(params)) {
56
+ if (v !== undefined) {
57
+ q.set(k, String(v));
58
+ }
59
+ }
60
+ const qs = q.size ? `?${q}` : "";
61
+ return this.request("GET", `/jobs${qs}`);
62
+ }
63
+ deleteJob(id) {
64
+ return this.request("DELETE", `/jobs/${encodeURIComponent(id)}`);
65
+ }
66
+ /**
67
+ * Debits the offer. `rightsAttested` must be an explicit true. A retry after a
68
+ * lost response gets 409 JOB_NOT_IDENTIFIED, so a debit is never doubled.
69
+ */
70
+ processJob(id, options) {
71
+ if (options.rightsAttested !== true) {
72
+ throw new DemarkerClientError("ATTESTATION_REQUIRED", "rightsAttested must be explicitly true.");
73
+ }
74
+ return this.request("POST", `/jobs/${encodeURIComponent(id)}/process`, options);
75
+ }
76
+ getBalance() {
77
+ return this.request("GET", "/balance");
78
+ }
79
+ getPricing() {
80
+ return this.request("GET", "/pricing", undefined, undefined, false);
81
+ }
82
+ // ----- Keyless scan: identify only, no API key -----
83
+ /** Upload an image for the free identify step. Returns the job id and a secret claim token. */
84
+ async scan(bytes, filename) {
85
+ const form = new FormData();
86
+ form.set("file", new Blob([bytes]), filename);
87
+ return this.request("POST", "/scan", form, undefined, false);
88
+ }
89
+ getScan(id, claimToken) {
90
+ return this.request("GET", `/scan/${encodeURIComponent(id)}`, undefined, { "X-Claim-Token": claimToken }, false);
91
+ }
92
+ /** Keyless scan and wait for the identify result. */
93
+ async scanBytes(bytes, filename, options = {}) {
94
+ const ticket = await this.scan(bytes, filename);
95
+ const interval = options.intervalMs ?? 1500;
96
+ const deadline = Date.now() + (options.timeoutMs ?? 120_000);
97
+ for (;;) {
98
+ const job = await this.getScan(ticket.id, ticket.claimToken);
99
+ options.onProgress?.(job);
100
+ if (job.status !== "UPLOADED" && job.status !== "IDENTIFYING") {
101
+ return job;
102
+ }
103
+ if (Date.now() >= deadline) {
104
+ throw new DemarkerClientError("TIMEOUT", `Scan ${ticket.id} did not finish in time (status ${job.status}).`, { jobId: ticket.id, status: job.status });
105
+ }
106
+ await sleep(interval, options.signal);
107
+ }
108
+ }
109
+ // ----- Helpers that combine calls -----
110
+ /** Poll until `until` returns true or the job is terminal. */
111
+ async waitForJob(id, until, options = {}) {
112
+ const interval = options.intervalMs ?? 2000;
113
+ const deadline = Date.now() + (options.timeoutMs ?? 10 * 60_000);
114
+ for (;;) {
115
+ const job = await this.getJob(id);
116
+ options.onProgress?.(job);
117
+ if (until(job) || TERMINAL.includes(job.status)) {
118
+ return job;
119
+ }
120
+ if (Date.now() >= deadline) {
121
+ throw new DemarkerClientError("TIMEOUT", `Job ${id} did not finish in time (status ${job.status}).`, { jobId: id, status: job.status });
122
+ }
123
+ await sleep(interval, options.signal);
124
+ }
125
+ }
126
+ /** Upload, PUT and identify (free). Polls if the API answers 202. */
127
+ async identifyBytes(bytes, filename, options = {}) {
128
+ const ticket = await this.createUpload(filename);
129
+ await this.putUpload(ticket, bytes);
130
+ const first = await this.identify(ticket.uploadKey, options.idempotencyKey);
131
+ if (isSettledIdentify(first)) {
132
+ return first;
133
+ }
134
+ return this.waitForJob(first.id, isSettledIdentify, options);
135
+ }
136
+ /** Download a result URL (valid for one hour). */
137
+ async download(url) {
138
+ const res = await this.doFetch(url);
139
+ if (!res.ok) {
140
+ throw new DemarkerClientError("UPLOAD_FAILED", `Download failed with HTTP ${res.status}.`, { status: res.status });
141
+ }
142
+ return new Uint8Array(await res.arrayBuffer());
143
+ }
144
+ /**
145
+ * Identify, check the price against `maxCredits`, process, wait, download.
146
+ * Spends nothing unless the offer is at or under the cap.
147
+ */
148
+ async clean(options) {
149
+ if (!(Number.isFinite(options.maxCredits) && options.maxCredits >= 0)) {
150
+ throw new DemarkerClientError("CREDIT_CAP_REQUIRED", "maxCredits is required (a number of credits, 0 or more).");
151
+ }
152
+ if (options.rightsAttested !== true) {
153
+ throw new DemarkerClientError("ATTESTATION_REQUIRED", "rightsAttested must be explicitly true.");
154
+ }
155
+ const tier = options.tier ?? "GPU";
156
+ const keepResolution = options.keepResolution === true;
157
+ const identified = await this.identifyBytes(options.bytes, options.filename, options);
158
+ options.onIdentified?.(identified);
159
+ if (identified.status === "REFUSED") {
160
+ throw new DemarkerClientError("REFUSED", `Identify refused this image: ${identified.refusalReason ?? "unspecified"}.`, {
161
+ jobId: identified.id,
162
+ refusalReason: identified.refusalReason,
163
+ });
164
+ }
165
+ const offer = identified.offers?.find((o) => o.tier === tier && o.keepResolution === keepResolution);
166
+ if (!offer) {
167
+ throw new DemarkerClientError("NO_OFFER", `No ${tier} offer${keepResolution ? " at original size" : ""} for this image.`, { jobId: identified.id, offers: identified.offers });
168
+ }
169
+ if (offer.credits > options.maxCredits) {
170
+ throw new DemarkerClientError("OVER_CREDIT_CAP", `This run costs ${offer.credits} credit(s); maxCredits is ${options.maxCredits}. Nothing was charged.`, {
171
+ jobId: identified.id,
172
+ requiredCredits: offer.credits,
173
+ maxCredits: options.maxCredits,
174
+ });
175
+ }
176
+ let started;
177
+ try {
178
+ started = await this.processJob(identified.id, {
179
+ tier,
180
+ keepResolution,
181
+ rightsAttested: true,
182
+ vendorHint: options.vendorHint,
183
+ force: options.force,
184
+ marks: options.marks,
185
+ secondAttestation: options.secondAttestation,
186
+ });
187
+ }
188
+ catch (err) {
189
+ // The debit may have landed before the response was lost. A second
190
+ // pick is refused with 409, so look at the job instead.
191
+ if (!(err instanceof DemarkerApiError &&
192
+ err.code === "JOB_NOT_IDENTIFIED")) {
193
+ throw err;
194
+ }
195
+ started = await this.getJob(identified.id);
196
+ if (!["PENDING", "PROCESSING", "COMPLETED"].includes(started.status)) {
197
+ throw err;
198
+ }
199
+ }
200
+ const done = started.status === "COMPLETED"
201
+ ? started
202
+ : await this.waitForJob(started.id, (j) => j.status === "COMPLETED", options);
203
+ if (done.status !== "COMPLETED" || !done.outputs.image) {
204
+ throw new DemarkerClientError("JOB_FAILED", done.errorMessage ??
205
+ `Job ended as ${done.status}. Credits are refunded automatically on failure.`, {
206
+ jobId: done.id,
207
+ status: done.status,
208
+ errorCode: done.errorCode,
209
+ });
210
+ }
211
+ const image = await this.download(done.outputs.image);
212
+ return {
213
+ job: done,
214
+ image,
215
+ creditsCharged: done.creditsCharged ?? offer.credits,
216
+ };
217
+ }
218
+ // ----- internals -----
219
+ async request(method, path, body, extraHeaders, auth = true) {
220
+ const headers = {
221
+ Accept: "application/json",
222
+ "User-Agent": this.userAgent,
223
+ ...extraHeaders,
224
+ };
225
+ if (auth) {
226
+ if (!this.apiKey) {
227
+ throw new DemarkerClientError("NO_API_KEY", "This call needs an API key. Create one at https://demarker.io/app/settings/api");
228
+ }
229
+ headers.Authorization = `Bearer ${this.apiKey}`;
230
+ }
231
+ const isForm = typeof FormData !== "undefined" && body instanceof FormData;
232
+ if (body !== undefined && !isForm) {
233
+ headers["Content-Type"] = "application/json";
234
+ }
235
+ const res = await this.doFetch(`${this.baseUrl}${path}`, {
236
+ method,
237
+ headers,
238
+ body: body === undefined
239
+ ? undefined
240
+ : isForm
241
+ ? body
242
+ : JSON.stringify(body),
243
+ });
244
+ const text = await res.text();
245
+ let data;
246
+ try {
247
+ data = text ? JSON.parse(text) : undefined;
248
+ }
249
+ catch {
250
+ data = undefined;
251
+ }
252
+ if (!res.ok) {
253
+ const env = data?.error;
254
+ const { code, message, ...rest } = env ?? {};
255
+ throw new DemarkerApiError(res.status, typeof code === "string" ? code : `HTTP_${res.status}`, typeof message === "string"
256
+ ? message
257
+ : `Request failed with HTTP ${res.status}.`, rest);
258
+ }
259
+ return data;
260
+ }
261
+ }
262
+ /** True once identify has produced a result (not UPLOADED / IDENTIFYING). */
263
+ export function isSettledIdentify(j) {
264
+ return j.status !== "UPLOADED" && j.status !== "IDENTIFYING";
265
+ }
266
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /** An error envelope from the API: `{ error: { code, message, resolution, docs, ...extra } }`. */
2
+ export declare class DemarkerApiError extends Error {
3
+ readonly status: number;
4
+ readonly code: string;
5
+ readonly resolution?: string;
6
+ /** Extra fields the API sent with the error (requiredCredits, balance, purchaseUrl, ...). */
7
+ readonly details: Record<string, unknown>;
8
+ constructor(status: number, code: string, message: string, details?: Record<string, unknown>);
9
+ }
10
+ /** The caller broke a rule the client enforces before any request is sent. */
11
+ export declare class DemarkerClientError extends Error {
12
+ readonly code: string;
13
+ readonly details: Record<string, unknown>;
14
+ constructor(code: "CREDIT_CAP_REQUIRED" | "OVER_CREDIT_CAP" | "NO_OFFER" | "TIMEOUT" | "JOB_FAILED" | "REFUSED" | "ATTESTATION_REQUIRED" | "UPLOAD_FAILED" | "NO_API_KEY", message: string, details?: Record<string, unknown>);
15
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,31 @@
1
+ /** An error envelope from the API: `{ error: { code, message, resolution, docs, ...extra } }`. */
2
+ export class DemarkerApiError extends Error {
3
+ status;
4
+ code;
5
+ resolution;
6
+ /** Extra fields the API sent with the error (requiredCredits, balance, purchaseUrl, ...). */
7
+ details;
8
+ constructor(status, code, message, details = {}) {
9
+ super(message);
10
+ this.name = "DemarkerApiError";
11
+ this.status = status;
12
+ this.code = code;
13
+ this.details = details;
14
+ const resolution = details.resolution;
15
+ if (typeof resolution === "string") {
16
+ this.resolution = resolution;
17
+ }
18
+ }
19
+ }
20
+ /** The caller broke a rule the client enforces before any request is sent. */
21
+ export class DemarkerClientError extends Error {
22
+ code;
23
+ details;
24
+ constructor(code, message, details = {}) {
25
+ super(message);
26
+ this.name = "DemarkerClientError";
27
+ this.code = code;
28
+ this.details = details;
29
+ }
30
+ }
31
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACjC,MAAM,CAAS;IACf,IAAI,CAAS;IACb,UAAU,CAAU;IAC7B,6FAA6F;IACpF,OAAO,CAA0B;IAE1C,YACC,MAAc,EACd,IAAY,EACZ,OAAe,EACf,UAAmC,EAAE;QAErC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACtC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACpC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC9B,CAAC;IACF,CAAC;CACD;AAED,8EAA8E;AAC9E,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACpC,IAAI,CAAS;IACb,OAAO,CAA0B;IAE1C,YACC,IASe,EACf,OAAe,EACf,UAAmC,EAAE;QAErC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD"}
@@ -0,0 +1,4 @@
1
+ /** Pick the extension from the bytes: the API returns the result as stored, not by name. */
2
+ export declare function detectExt(bytes: Uint8Array): string;
3
+ /** `photo.png` -> `photo.clean.png` next to the input, or inside/at `out`. */
4
+ export declare function outputPathFor(input: string, ext: string, out?: string, outIsDir?: boolean): string;
package/dist/files.js ADDED
@@ -0,0 +1,28 @@
1
+ import { basename, dirname, extname, join } from "node:path";
2
+ /** Pick the extension from the bytes: the API returns the result as stored, not by name. */
3
+ export function detectExt(bytes) {
4
+ const b = bytes;
5
+ if (b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) {
6
+ return ".png";
7
+ }
8
+ if (b[0] === 0xff && b[1] === 0xd8) {
9
+ return ".jpg";
10
+ }
11
+ if (b[0] === 0x52 &&
12
+ b[1] === 0x49 &&
13
+ b[2] === 0x46 &&
14
+ b[3] === 0x46 &&
15
+ b[8] === 0x57) {
16
+ return ".webp";
17
+ }
18
+ return ".png";
19
+ }
20
+ /** `photo.png` -> `photo.clean.png` next to the input, or inside/at `out`. */
21
+ export function outputPathFor(input, ext, out, outIsDir = false) {
22
+ const name = `${basename(input, extname(input))}.clean${ext}`;
23
+ if (!out) {
24
+ return join(dirname(input), name);
25
+ }
26
+ return outIsDir ? join(out, name) : out;
27
+ }
28
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE7D,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,KAAiB;IAC1C,MAAM,CAAC,GAAG,KAAK,CAAC;IAChB,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACtE,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IACC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EACZ,CAAC;QACF,OAAO,OAAO,CAAC;IAChB,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAC5B,KAAa,EACb,GAAW,EACX,GAAY,EACZ,QAAQ,GAAG,KAAK;IAEhB,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC;IAC9D,IAAI,CAAC,GAAG,EAAE,CAAC;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACzC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./attestation.ts";
2
+ export * from "./client.ts";
3
+ export * from "./errors.ts";
4
+ export * from "./files.ts";
5
+ export * from "./types.ts";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./attestation.js";
2
+ export * from "./client.js";
3
+ export * from "./errors.js";
4
+ export * from "./files.js";
5
+ export * from "./types.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,99 @@
1
+ /** Shapes of `/api/v1` responses. Mirror `apps/web/app/api/v1/lib/serialize.ts`. */
2
+ export type JobStatus = "UPLOADED" | "IDENTIFYING" | "IDENTIFIED" | "HELD" | "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED" | "REFUSED" | "EXPIRED";
3
+ export type Tier = "QUICK" | "GPU";
4
+ export interface Offer {
5
+ tier: Tier;
6
+ keepResolution: boolean;
7
+ credits: number;
8
+ [key: string]: unknown;
9
+ }
10
+ export interface Job {
11
+ id: string;
12
+ status: JobStatus;
13
+ progress: number | null;
14
+ width: number;
15
+ height: number;
16
+ megapixels: number;
17
+ band: string | null;
18
+ hasInvisibleTarget: boolean | null;
19
+ vendor: string | null;
20
+ /** The identify report: visible marks, AI metadata, inferred hidden watermarks. */
21
+ report: unknown;
22
+ cmiFields: unknown;
23
+ offers: Offer[] | null;
24
+ /** Present on the identify response only. */
25
+ balance?: number;
26
+ attestationVersion: string | null;
27
+ tier: Tier | null;
28
+ keepResolution: boolean | null;
29
+ creditsCharged: number | null;
30
+ refusalReason: string | null;
31
+ processBlocked: string | null;
32
+ errorCode: string | null;
33
+ errorMessage: string | null;
34
+ cleanReport: unknown;
35
+ outputs: {
36
+ image: string | null;
37
+ };
38
+ createdAt: string;
39
+ completedAt: string | null;
40
+ expiresAt: string | null;
41
+ }
42
+ /** 202 from /identify while the worker is still busy. */
43
+ export interface PendingIdentify {
44
+ id: string;
45
+ status: JobStatus;
46
+ }
47
+ export interface UploadTicket {
48
+ uploadUrl: string;
49
+ uploadKey: string;
50
+ expiresAt: string;
51
+ maxFileSizeBytes: number;
52
+ maxFileSizeMb: number;
53
+ contentType: string;
54
+ }
55
+ export interface Balance {
56
+ balanceCredits: number;
57
+ updatedAt: string;
58
+ }
59
+ export interface Pricing {
60
+ unit: string;
61
+ bandMegapixels: number;
62
+ maxMegapixels: number;
63
+ tiers: {
64
+ QUICK: {
65
+ purchasable: boolean;
66
+ freePerDayWeb: number;
67
+ };
68
+ GPU: {
69
+ standardOutput: number;
70
+ originalOverBand: number;
71
+ };
72
+ };
73
+ packs: {
74
+ id: string;
75
+ label: string;
76
+ credits: number;
77
+ priceCents: number;
78
+ }[];
79
+ }
80
+ export interface ProcessOptions {
81
+ tier: Tier;
82
+ /** Must be an explicit `true`. There is no default. */
83
+ rightsAttested: boolean;
84
+ keepResolution?: boolean;
85
+ vendorHint?: string;
86
+ force?: boolean;
87
+ marks?: string[];
88
+ secondAttestation?: string;
89
+ }
90
+ /** 202 from POST /scan. `claimToken` is a secret: send it as X-Claim-Token, never in a URL. */
91
+ export interface ScanTicket {
92
+ id: string;
93
+ claimToken: string;
94
+ status: JobStatus;
95
+ width: number;
96
+ height: number;
97
+ megapixels: number;
98
+ identifyOnly: boolean;
99
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Shapes of `/api/v1` responses. Mirror `apps/web/app/api/v1/lib/serialize.ts`. */
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oFAAoF"}
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
- "name": "@demarker/client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@demarker/client",
3
+ "version": "0.1.0",
4
+ "description": "Typed client for the Demarker API: identify what an AI image carries, then clean it.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "homepage": "https://demarker.io/developers",
23
+ "keywords": [
24
+ "demarker",
25
+ "ai-watermark",
26
+ "synthid",
27
+ "image",
28
+ "api-client"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.build.json",
32
+ "type-check": "tsc --noEmit",
33
+ "test": "node --test \"src/**/*.test.ts\"",
34
+ "clean": "rm -rf dist"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^24.10.1",
38
+ "typescript": "5.9.3"
39
+ }
40
+ }