🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

identityforge

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

identityforge - npm Package Compare versions

Comparing version
0.3.3
to
0.3.5
+140
dist/updateCheck.js
import { get } from "node:https";
import { readConfig, updateConfig } from "./config.js";
const REGISTRY_URL = "https://registry.npmjs.org/identityforge/latest";
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
function parseVersion(value) {
const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
if (!match)
return undefined;
return {
major: Number(match[1]),
minor: Number(match[2]),
patch: Number(match[3]),
prerelease: match[4]?.split(".") ?? [],
};
}
function compareVersions(left, right) {
for (const [a, b] of [
[left.major, right.major],
[left.minor, right.minor],
[left.patch, right.patch],
]) {
if (a !== b)
return a > b ? 1 : -1;
}
if (!left.prerelease.length || !right.prerelease.length) {
return left.prerelease.length === right.prerelease.length
? 0
: left.prerelease.length
? -1
: 1;
}
for (let i = 0; i < Math.max(left.prerelease.length, right.prerelease.length); i++) {
const a = left.prerelease[i];
const b = right.prerelease[i];
if (a === undefined)
return -1;
if (b === undefined)
return 1;
if (a === b)
continue;
const aNumber = /^\d+$/.test(a);
const bNumber = /^\d+$/.test(b);
if (aNumber && bNumber)
return Number(a) > Number(b) ? 1 : -1;
if (aNumber !== bNumber)
return aNumber ? -1 : 1;
return a > b ? 1 : -1;
}
return 0;
}
export function isVersionGreater(candidate, current) {
const left = parseVersion(candidate);
const right = parseVersion(current);
return left !== undefined && right !== undefined && compareVersions(left, right) > 0;
}
let updateNoticePrinted = false;
function printUpdateNotice(currentVersion, latestVersion) {
if (updateNoticePrinted)
return;
updateNoticePrinted = true;
process.stderr.write(`Identity Forge update available: ${currentVersion} -> ${latestVersion}. Update with npm i -g identityforge@latest.\n`);
}
function fetchLatestVersion() {
return new Promise((resolve) => {
let settled = false;
const finish = (version) => {
if (settled)
return;
settled = true;
resolve(version);
};
const request = get(REGISTRY_URL, { headers: { Accept: "application/json" } }, (response) => {
let body = "";
response.setEncoding("utf8");
response.on("data", (chunk) => {
body += chunk;
});
response.on("end", () => {
if (response.statusCode !== 200)
return finish();
try {
const version = JSON.parse(body).version;
finish(typeof version === "string" && parseVersion(version)
? version
: undefined);
}
catch {
finish();
}
});
response.on("error", () => finish());
});
request.on("socket", (socket) => socket.unref());
request.on("error", () => finish());
const timeout = setTimeout(() => {
request.destroy();
finish();
}, 3000);
timeout.unref();
});
}
export async function checkForUpdate(currentVersion) {
const config = readConfig();
const cached = config.updateCheck;
if (cached?.latestVersion && isVersionGreater(cached.latestVersion, currentVersion)) {
printUpdateNotice(currentVersion, cached.latestVersion);
}
const checkedAt = cached ? Date.parse(cached.checkedAt) : Number.NaN;
if (Number.isFinite(checkedAt) && Date.now() - checkedAt < CHECK_INTERVAL_MS)
return;
let latestVersion;
try {
latestVersion = await fetchLatestVersion();
if (latestVersion) {
if (isVersionGreater(latestVersion, currentVersion)) {
printUpdateNotice(currentVersion, latestVersion);
}
}
}
catch {
return;
}
finally {
try {
updateConfig({
updateCheck: {
checkedAt: new Date().toISOString(),
...(latestVersion ? { latestVersion } : {}),
},
});
}
catch {
// An unwritable config must never affect the command being run.
}
}
}
/** Start the check without giving the network request a chance to hold up a command. */
export function startUpdateCheck(currentVersion) {
void checkForUpdate(currentVersion).catch(() => { });
}
+49
-1
import { resolveApiKey, resolveApiUrl } from "./config.js";
export const CLI_VERSION = "0.3.3";
import { isVersionGreater } from "./updateCheck.js";
export const CLI_VERSION = "0.3.5";
let apiClient = "cli";

@@ -190,2 +191,12 @@ /** Select the client identity used by subsequent API requests. */

}
let minimumCliWarningPrinted = false;
function noteMinimumCliVersion(res) {
if (minimumCliWarningPrinted)
return;
const minimum = res.headers.get("x-identityforge-min-cli");
if (!minimum || !isVersionGreater(minimum, CLI_VERSION))
return;
minimumCliWarningPrinted = true;
process.stderr.write(`Identity Forge server requires CLI ${minimum} or newer; installed CLI is ${CLI_VERSION}. Update with npm i -g identityforge@latest.\n`);
}
async function requestJson(path, init) {

@@ -196,2 +207,3 @@ const res = await fetch(`${resolveApiUrl()}${path}`, {

});
noteMinimumCliVersion(res);
if (!res.ok)

@@ -326,2 +338,3 @@ throw await readError(res, path);

});
noteMinimumCliVersion(res);
if (!res.ok)

@@ -355,2 +368,3 @@ throw await readError(res, path);

});
noteMinimumCliVersion(res);
if (!res.ok)

@@ -382,2 +396,3 @@ throw await readError(res, path);

});
noteMinimumCliVersion(res);
if (!res.ok)

@@ -411,2 +426,3 @@ throw await readError(res, path);

});
noteMinimumCliVersion(res);
if (!res.ok)

@@ -512,2 +528,9 @@ throw await readError(res, path);

}
export async function searchTrademarks(input) {
return requestJson("/api/v1/naming/trademarks/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
});
}
/** Create a private design kit from scratch or by forking a catalog kit. */

@@ -544,2 +567,6 @@ export async function createTheme(input) {

}
export async function deleteTheme(identifier) {
const json = await requestJson(`/api/v1/kits/${encodeURIComponent(identifier)}`, { method: "DELETE" });
return { ...json.data, ...json.meta };
}
export async function createBrandProject(input) {

@@ -601,2 +628,22 @@ const json = await requestJson("/api/v1/brand-projects", {

}
export async function generateMockups(input) {
const { projectId, idempotencyKey, ...body } = input;
const json = await requestJson(`/api/v1/brand-projects/${encodeURIComponent(projectId)}/mockups`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
},
body: JSON.stringify(body),
});
return json.data;
}
export async function listMockupJobs(projectId) {
const json = await requestJson(`/api/v1/brand-projects/${encodeURIComponent(projectId)}/mockups`);
return json.data;
}
export async function getMockupJob(projectId, jobId) {
const json = await requestJson(`/api/v1/brand-projects/${encodeURIComponent(projectId)}/mockups/${encodeURIComponent(jobId)}`);
return json.data;
}
/** Change an existing share WITHOUT reissuing its URL. `password: null` clears

@@ -777,2 +824,3 @@ * the protection; omitting it leaves whatever is set alone. 404 when the

});
noteMinimumCliVersion(res);
if (!res.ok)

@@ -779,0 +827,0 @@ throw await readError(res, path);

+3
-3

@@ -7,3 +7,3 @@ import { spawn } from "node:child_process";

import { resolveApiUrl } from "./config.js";
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
const LOGIN_TIMEOUT_MS = 10 * 60 * 1000;
const base64url = (buf) => buf.toString("base64url");

@@ -93,3 +93,3 @@ function openBrowser(url) {

const timer = setTimeout(() => {
done(() => reject(new Error("Timed out waiting for browser sign-in (5 minutes).")));
done(() => reject(new Error("Timed out waiting for browser sign-in (10 minutes).")));
}, LOGIN_TIMEOUT_MS);

@@ -106,3 +106,3 @@ server.on("error", (e) => done(() => reject(e)));

const href = authorizeUrl.toString();
process.stdout.write(`Opening your browser to sign in…\nIf it doesn't open, paste this URL:\n ${href}\n`);
process.stdout.write(`Opening your browser to sign in…\nNo account yet? Create one there and confirm the email; this login resumes automatically.\nIf it doesn't open, paste this URL:\n ${href}\n`);
openBrowser(href);

@@ -109,0 +109,0 @@ });

{
"name": "identityforge",
"version": "0.3.3",
"version": "0.3.5",
"description": "Agent-native brand naming, domain research, and design systems through one CLI and MCP server.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -29,2 +29,6 @@ # identityforge

No account yet? The browser offers signup and waits for the confirmation email.
Follow the email link and confirm on the page; the pending CLI authorization then
resumes automatically.
Then tell your agent:

@@ -38,6 +42,10 @@

- `list_themes({ use: "data-dashboard" })` returns only kits genuinely fit for dashboards, ranked by fitness, each with a score from 0 to 100 computed from that kit's own tokens against the lane's criteria.
- `list_themes({ use: "data-dashboard" })` re-orders the catalog by fitness for dashboards rather than narrowing it, each kit carrying a score from 0 to 100 computed from its own tokens against the lane's criteria.
- `list_themes({ q: "calm fintech dashboard" })` runs a synonym-aware ranked search across moods, industries, and use cases.
- `search_themes` returns the whole catalog unranked so the agent can weigh a subtle brief itself.
Read the order, not the number. The score measures how well a kit is built against a lane's criteria, and every catalog kit is well built, so the scores cluster high and the documented cut excludes almost nothing. A lane changes which kits come first, not which kits come back.
For the data lanes there is a better answer than the score. Every kit summary carries a `charts` block measured on the mode the kit ships in: `minDeltaE` and `cvdMinDeltaE` (the closest pair of series colors, plain and under colorblind simulation), `distinct`, `hueFamilies`, `severityHeadroom` (how close any series comes to the destructive, warning and success roles — 0 means a category color IS a status color), `sequentialReady`, and `designed`, which is `false` when the kit defines no chart slots and the five were cycled from its brand roles. Those are measurements, so unlike the fitness score they can be stated to a user as the reason for a recommendation.
Use-case lanes: `data-dashboard`, `admin-internal-tool`, `saas-marketing`, `landing-page`, `ecommerce-store`, `portfolio`, `editorial-blog`, `docs-knowledge-base`, `mobile-app`, `business-services`, `community-social`, `ai-agent-chat`.

@@ -65,8 +73,8 @@

Once connected, your agent gets 56 tools. Browsing free kits needs no key; scopes are noted where they apply.
Once connected, your agent gets 61 tools. Browsing free kits needs no key; scopes are noted where they apply.
### Find a design kit
- `list_themes`: browse the catalog as compact summaries. Filter to a judged use-case lane with `use`, run ranked search with `q`, page with `offset`, sort by `featured`, `popular`, `recent`, `name`, or `fit`.
- `search_themes`: return the whole catalog unranked, for briefs too subtle to filter on.
- `list_themes`: browse the catalog as compact summaries. Rank by use-case lane with `use`, run ranked search with `q`, page with `offset`, sort by `featured`, `popular`, `recent`, `name`, or `fit`.
- `search_themes`: return the whole catalog unranked, for briefs too subtle to rank against a lane.
- `similar_themes`: given a kit slug, find neighbours by palette, tags, and audience.

@@ -169,6 +177,8 @@ - `match_palette`: given existing brand colors, rank kits by perceptual color distance.

- `update_theme`: edit one of your saved kits in place, keeping its slug and publication state so existing consumers follow the change. Overwrites the stored kit, but every save mints a version, so the replaced state stays readable through the version tools below. The slug itself cannot be renamed here, and `expectedUpdatedAt` turns a concurrent edit into a 409 instead of a silent overwrite.
- `delete_theme`: permanently delete one of your saved kits. Pass `confirm: true`; a kit still referenced by a brand project is refused with `409 kit_in_use`, so retire or repoint those references first.
- `create_brand_project` and `list_brand_projects`: the container for brand variations and a client share.
- `add_brand_variation`: attach a proposal to a project, with a kit plus optional name, domain, label, and notes.
- `update_brand_variation`: revise one proposal in place, including repointing it at a different kit. The client sees it on their next view.
- `remove_brand_variation`: permanently delete one proposal and its comments. Not undoable.
- `remove_brand_variation`: permanently delete one proposal and its comments. Pass `confirm: true`; it is not undoable.
- `revoke_brand_share`: permanently withdraw a client link. Pass `confirm: true`; sharing again mints a new token.
- `reorder_brand_variations`: set the order the client meets the directions in. Must list every variation exactly once.

@@ -184,3 +194,3 @@ - `share_brand_project`: create or rotate a read-only `/p/<token>` client share link, optionally password protected.

- `add_brand_layer`: compose one record onto the brand, recording the revision it is at now so a later read can report a change rather than apply it silently. One tool for all three axes via `axis`. Image direction and interface style hold one each; a second is refused with 409 unless you pass `replace: true`, which is also how you accept a drifted revision.
- `remove_brand_layer`: take one off. Names the record rather than the axis, so a stale view cannot clear a layer it never saw, and repeating it is a no-op.
- `remove_brand_layer`: take one off. Pass `confirm: true`; it names the record rather than the axis, so a stale view cannot clear a layer it never saw, and repeating it is a no-op.
- `export_brand` (`kits:read`): the brand as ONE document, ready to build from — the kit's `DESIGN.md` with every pinned layer written into it, under the precedence rule that decides which wins when they disagree (the kit owns identity, a layer owns application). Use it instead of merging the kit and each layer yourself. A layer the key cannot open is named with its page and an upgrade path rather than dropped; a brand with no chosen kit answers 409 instead of returning a placeholder nobody picked.

@@ -240,3 +250,3 @@

Everything an agent creates it can also revise. The write tools overwrite live, client-visible state and none of them ask first, so read the feedback before acting on it.
Everything an agent creates it can also revise. The write tools change live, client-visible state; destructive tools require `confirm: true`, so read the feedback before acting on it.

@@ -287,2 +297,3 @@ New keys carry `kits:write` by default. A key minted before that scope existed will 403 until you re-run `identityforge login` or create a new scoped key.

identityforge themes remix <id|slug> --overrides o.json # copies; original untouched
identityforge themes delete <id|slug> --yes # permanent; refuses kits still in use
identityforge themes similar <id|slug> # nearby published kits

@@ -310,3 +321,3 @@ identityforge themes match "#1d4ed8" "#f97316" # kits closest to colors you hold

identityforge brand add-layer --project <uuid> --axis imageDirection --record <id>
identityforge brand remove-layer --project <uuid> --axis imageDirection --record <id>
identityforge brand remove-layer --project <uuid> --axis imageDirection --record <id> --yes
# The kit's DESIGN.md with every pinned layer composed into it: the one document to build from

@@ -320,2 +331,6 @@ identityforge brand export --project <uuid> > DESIGN.md

identityforge brand recommend --project <uuid> # candidates (3 units, needs a key)
# Queue mockups: one AI credit per variation and scene combination
identityforge brand mockups generate --project <uuid> --variation <uuid> --item tshirt:front
identityforge brand mockups list --project <uuid>
identityforge brand mockups get --project <uuid> --job <uuid>

@@ -356,2 +371,3 @@ identityforge brand versions --project <uuid> # project history

identityforge naming search --file research-tasks.json
identityforge naming trademarks "Candidate name" --project <uuid> --candidate <uuid> --nice-classes 9,42
identityforge naming move <candidate-uuid> --project <uuid> --status finalist --notes "Strong market fit"

@@ -410,3 +426,3 @@ identityforge naming rank <candidate-uuid>=1 <candidate-uuid>=2 --project <uuid>

Free kits and naming-recipe discovery work without a key. Owned naming projects and domain research use `naming:read`, generation and board edits use `naming:write`, reading design systems uses `kits:read`, and creating or remixing kits plus building shareable brand projects uses `kits:write`. API calls count against the plan's API quota, while generation separately spends AI credits for successfully persisted unique candidates. Manage keys at <https://identityforge.io/account/api-keys>.
Free kits and naming-recipe discovery work without a key. Sign in to keep persistent projects and saved work under an authenticated quota. Owned naming projects and domain research use `naming:read`, generation and board edits use `naming:write`, reading design systems uses `kits:read`, and creating or remixing kits plus building shareable brand projects uses `kits:write`. API calls count against the plan's API quota, while generation separately spends AI credits for successfully persisted unique candidates. Manage keys at <https://identityforge.io/account/api-keys>.

@@ -413,0 +429,0 @@ Existing design-only keys are not silently upgraded. If a key reports that it is missing `naming:read` or `naming:write`, create a scoped key or run browser login again.

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display