@colorfast-design/client
v0.1.3
Published
Server-side TypeScript client for the Colorfast brand lifecycle.
Readme
@colorfast-design/client
Server-side TypeScript client for requesting, polling, retrying, and reading complete Colorfast brand publications.
Install
bun add @colorfast-design/clientThe package also supports npm, pnpm, and Node.js 20 or newer.
Configure
Use the project-scoped server API key provided by the Colorfast pilot owner, then store it in the consumer's secret manager:
COLORFAST_API_URL=https://api.colorfast.dev/v1
COLORFAST_API_KEY=...Never expose the API key through browser code or a public environment variable.
Pilot access is operator-provisioned. Contact the Colorfast pilot owner for an endpoint and project key, keep the key in a server-side secret manager, and inject consumer authorization separately.
Durable consumer handoff
The packaged
pinned-publication example provides
a loopback handler, restart-safe state, immutable mirroring, atomic adoption,
and local agent context as compiled JavaScript for Node and Bun.
Use one stable idempotency key for the initial mutation. Poll the saved job without creating work, replay uncertain requests with the same key, and retry only a confirmed retryable terminal error with a new key. Refresh explicitly from the active publication and keep the prior snapshot when replacement fails. Rotate or revoke keys through the secret manager and restart the handler. Adopted context remains local and makes zero Colorfast API or asset-origin calls, including during rollback.
Request a brand
import "server-only";
import { Colorfast } from "@colorfast-design/client";
const colorfast = new Colorfast({
baseUrl: process.env.COLORFAST_API_URL!,
apiKey: process.env.COLORFAST_API_KEY!,
});
const result = await colorfast.requestBrand({
url: "https://example.com/about",
idempotencyKey: `workspace:${workspaceId}:brand:${brandId}`,
});
if (result.status === "processing") {
await saveSetupProgress({
jobId: result.job.id,
pollAfterMs: result.job.pollAfterMs,
});
} else if (result.status === "failed") {
await saveSetupFailure(result.error);
} else {
await savePinnedPublication({
brandId: result.brandId,
publicationId: result.publicationId,
verifiedAt: result.verifiedAt,
contract: result.contract,
assets: result.assetManifest,
artifacts: result.artifactManifest,
guidance: result.guidance,
canonicalContentHash: result.canonicalContentHash,
extractionProfileHash: result.extractionProfileHash,
});
}The client converts a URL to its canonical domain before sending the request. The idempotency key belongs to the consumer and must remain stable when the same setup operation is resumed.
This native acquisition is the primary integration path. After it becomes ready, pin and consume the exact publication and generation kit:
const intelligence = await colorfast.getBrandIntelligence(
result.publicationId,
);
const kit = await colorfast.getGenerationKit(result.publicationId);Request focused results
Screenshot, page images, styling, description, and organization info are thin optional operations over one project-and-scope acquisition. Use them when early visual context or a smaller agent payload is independently useful, and give each operation its own stable idempotency key:
const [screenshot, images, styling, description, info] = await Promise.all([
colorfast.requestScreenshot({
domain: "example.com",
idempotencyKey: `${operationId}:screenshot`,
}),
colorfast.requestImages({
domain: "example.com",
idempotencyKey: `${operationId}:images`,
}),
colorfast.requestStyling({
domain: "example.com",
idempotencyKey: `${operationId}:styling`,
}),
colorfast.requestDescription({
domain: "example.com",
idempotencyKey: `${operationId}:description`,
}),
colorfast.requestInfo({
domain: "example.com",
idempotencyKey: `${operationId}:info`,
}),
]);Persist each returned jobId, captureScopeId, and acquisitionId. Poll a
focused operation without creating work:
const styling = await colorfast.getBrandIntelligenceJob(
savedStylingJobId,
"styling",
);A terminal retryable focused failure can be resumed explicitly with
retryBrandIntelligenceJob. Use a new stable retry idempotency key and preserve
the original projection.
A ready focused result binds its publication id, screenshot receipt, screenshot content hash, capture scope, and acquisition id. Reject results from different identities instead of combining them.
Read exact data after pinning:
const intelligence = await colorfast.getBrandIntelligence(publicationId);
const typography = await colorfast.getBrandIntelligenceSection(
publicationId,
"fonts",
);
const darkLogos = await colorfast.getBrandAssets(publicationId, {
kind: "logo",
theme: "dark",
});
const availableLogo = darkLogos.items.find(
(item) => item.availability === "available",
);
const logoBytes = availableLogo
? await colorfast.getAsset(availableLogo.delivery.handleId)
: null;
const images = await colorfast.getBrandImages(publicationId);
const styling = await colorfast.getBrandStyling(publicationId);
const description = await colorfast.getBrandDescription(publicationId);
const info = await colorfast.getBrandInfo(publicationId);
const kit = await colorfast.getGenerationKit(publicationId);
const design = await colorfast.getGenerationArtifact(
publicationId,
"design_markdown",
);
const structured = await colorfast.getGenerationArtifact(
publicationId,
"structured_json",
);Exact intelligence exposes fixed verifiedAt and recommendedRefreshAt
timestamps, never a clock-dependent stale boolean. A latest or maximum-age read
does not create work. Refresh requires refreshBrandIntelligence with the prior
publication id and a new stable idempotency key.
The focused views are progressive-disclosure reads, not separate storage models. Prefer the coherent evidence-bound publication and compact generation kit when the consumer can use the native composed result directly.
Use the generation kit
The exact kit contains a ready-made editable DESIGN.md, compact
generation.json, CSS variables, a Tailwind v4 @theme, and
tokens.stylex.ts. All
five projections contain the same canonical token ids and have independent
exact descriptors and content hashes. generation.json is a sparse, bounded,
consumer-output-neutral render model for DESIGN.md, not a second source of
truth or something consumers must store. It includes the exact publication
reference and an authorized exact screenshot as the guide's visual-validation
companion when available. CSS
variables are the interoperability fallback, not full StyleX integration.
The generic design guide includes the publication's supported essence signals,
signature moves, semantic colors and typography, component and layout guidance,
ranked imagery, image-style guidance, positive guidance, prohibitions, evidence,
coverage, and explicit gaps. Supported color-role explanations name observed
background, surface, text, border, primary or secondary action, accent, or
decorative use and bind it to tokens, components, and evidence. Detected palette
values do not receive invented
roles. It never fills missing values with inferred defaults. Any unresolved
same-publication semantic conflict is listed with all candidates and evidence
instead of being selected silently.
Consumers may keep only their own downstream markdown plus the exact
publication id for traceability; the complete publication and focused reads
remain available without persisting raw API responses.
The exact screenshot is a validation companion, not calibrated screenshot
interpretation. This kit does not compare screenshot pixels or invoke a
VLM, and it keeps visual_reconciliation_unavailable explicit. Screenshot-
informed cohesion validation and the complex multicolor evaluation remain in
the U14 pre-pilot gate.
For StyleX, install @stylexjs/[email protected] and
@stylexjs/[email protected], then configure:
{
"plugins": [
[
"@stylexjs/babel-plugin",
{
"runtimeInjection": false,
"treeshakeCompensation": true,
"unstable_moduleResolution": {
"type": "commonJS"
}
}
]
]
}Import the named tokens export and only the light or dark theme exports listed
by the kit descriptor. Generated component recipes and variants remain
unavailable until their states have evidence.
Provider-compatibility facade
The server-only @colorfast-design/client/provider-compatibility entrypoint projects
exact canonical publications into sparse brand, fonts, styleguide, images, and
screenshot views:
import {
ProviderCompatibilityFacade,
} from "@colorfast-design/client/provider-compatibility";
const compatibility = new ProviderCompatibilityFacade(colorfast);
const brand = await compatibility.acquireAndWait({
domain: "example.com",
idempotencyKey: `${operationId}:compatibility`,
});Timeout errors include the durable job, domain, and capture-scope identity for
resumeAcquire. The facade owns no extraction, cache, fallback facts, or
generation-kit model.
Resume and retry
const job = await colorfast.getBrandJob(savedJobId);
if (job.status === "failed" && job.result?.error?.retryable) {
await colorfast.retryBrandJob({
jobId: job.id,
idempotencyKey: `${savedOperationKey}:retry:${job.attemptCount + 1}`,
});
}
if (
(job.status === "succeeded" || job.status === "superseded")
&& job.publicationId
) {
const publication = await colorfast.getPublication(job.publicationId);
await savePinnedPublication(publication);
}The SDK does not automatically retry mutations or keep process-local setup state. Consumers persist request, job, and publication identity so work can resume safely after a restart. Polling never creates work. Replay an uncertain request with its original idempotency key. Use a new stable retry key only after a terminal job reports a retryable failure. A superseded job without a publication id must not silently read latest.
Pin before agent use
A ready response contains one exact contract, asset manifest, artifact manifest, and guidance package. Persist the publication id, verified timestamp, content hashes, and required bytes before marking the consumer's setup ready. Verify downloaded assets, artifacts, and focused guidance against their descriptors. Agent generation should read that stored snapshot rather than call Colorfast or select the latest publication.
First-party browser-v2 publications also declare
provenance/design-source-map.json. Fetch it with getArtifact only when an
audit or extraction investigation needs region-grounded provenance. Normal
setup and agent context do not require it, and supported historical
publications may not contain it.
The initial consumer extractor migration guide maps the SDK lifecycle to an external consumer integration without assuming access to the consumer's repository.
Attach a source asset
Upload source assets only from server code. The SDK computes the content hash, creates a five-minute upload intent, sends the bytes to the presigned target, and completes verification:
const logo = await colorfast.uploadSourceAsset({
idempotencyKey: `workspace:${workspaceId}:logo:${logoVersion}`,
role: "logo",
label: "Primary logo",
mediaType: "image/png",
bytes: await readFile("./primary-logo.png"),
});
if (logo.state !== "verified") {
throw new Error(`logo rejected: ${logo.reason}`);
}
const result = await colorfast.requestBrand({
domain: "example.com",
idempotencyKey: `workspace:${workspaceId}:brand:${brandId}`,
sourceAssetIds: [logo.sourceAssetId],
});The Colorfast API key is sent only to Colorfast API endpoints. The presigned upload request receives only the headers declared by its upload intent. Do not copy either credential or upload URL into browser code.
Uploaded logos can replace the captured logo in a publication after immutable
readback and re-verification. Brand-guideline uploads are verified and retained
with an explicit ignored/not_applicable publication outcome until guideline
interpretation is supported.
Errors
Transport and protocol failures throw ColorfastClientError. Its kind is one
of authentication, validation, retryable, permanent, timeout,
malformed_response, or transport. Brand extraction failures remain typed
failed responses with their safe Colorfast error code.
The initial consumer pilot does not send generated CSS or HTML, prompts, private attachments, workspace overrides, or customer data to Colorfast. A hosted output-conformance oracle is deferred.
