New:Socket for Asana Is Now Available.Learn more
Get Started

identityforge

Package Overview
Dependencies
Maintainers
1
Versions
17
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.4.3
to
0.4.4
+49
dist/doctor.js
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { CLI_VERSION } from "./api.js";
import { buildMcpServer } from "./mcp.js";
const CAPABILITY_GROUPS = {
designSystems: ["list_themes", "get_design_md", "get_tokens", "apply_theme"],
brandDelivery: [
"create_brand_project",
"export_brand",
"share_brand_project",
],
naming: ["create_naming_project", "generate_names", "list_name_candidates"],
domains: ["check_domains", "assess_domain_acquisition"],
trademarks: ["search_trademarks"],
};
/** Exercise the current package over a real MCP transport without calling the API. */
export async function inspectCurrentMcp() {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const client = new McpClient({
name: "identityforge-doctor",
version: CLI_VERSION,
});
const server = buildMcpServer();
await Promise.all([
client.connect(clientTransport),
server.connect(serverTransport),
]);
try {
const { tools } = await client.listTools();
const names = new Set(tools.map((tool) => tool.name));
const capabilities = Object.fromEntries(Object.entries(CAPABILITY_GROUPS).map(([group, required]) => [
group,
required.every((name) => names.has(name)),
]));
const missingRequiredTools = Object.values(CAPABILITY_GROUPS)
.flat()
.filter((name) => !names.has(name));
return {
version: CLI_VERSION,
toolCount: tools.length,
capabilities,
missingRequiredTools,
};
}
finally {
await client.close();
await server.close();
}
}
+1
-1
import { randomUUID } from "node:crypto";
import { resolveApiKey, resolveApiUrl } from "./config.js";
import { isVersionGreater } from "./updateCheck.js";
export const CLI_VERSION = "0.4.3";
export const CLI_VERSION = "0.4.4";
let apiClient = "cli";

@@ -6,0 +6,0 @@ const clientProcessReference = randomUUID();

@@ -37,3 +37,3 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";

kind: "codex",
path: () => join(homedir(), ".codex", "config.toml"),
path: (_cwd, homeDir) => join(homeDir, ".codex", "config.toml"),
},

@@ -123,4 +123,8 @@ vscode: {

const existing = existsSync(file) ? readFileSync(file, "utf8") : "";
if (existing.includes(header))
return;
if (existing.includes(header)) {
const entry = codexServerEntry(existing);
if (entry && codexEntryIsCurrent(entry))
return;
throw new Error(`${file} already contains ${header}, but it does not run npx -y ${CLI_PACKAGE_SPEC} mcp. Repair or remove that table, then retry; Identity Forge did not overwrite it.`);
}
const envLine = apiUrl

@@ -133,5 +137,76 @@ ? `\nenv = { IDENTITYFORGE_API_URL = "${apiUrl}" }`

}
export function configPathFor(client, cwd = process.cwd()) {
return CLIENTS[client].path(cwd);
function codexServerEntry(source) {
const header = `[mcp_servers.${SERVER_KEY}]`;
const start = source.indexOf(header);
if (start < 0)
return undefined;
const rest = source.slice(start + header.length);
const nextTable = rest.search(/\n\s*\[/);
return nextTable < 0 ? rest : rest.slice(0, nextTable);
}
function codexEntryIsCurrent(entry) {
return (/^\s*command\s*=\s*["']npx["']\s*$/m.test(entry) &&
/^\s*args\s*=\s*\[\s*["']-y["']\s*,\s*["']identityforge@latest["']\s*,\s*["']mcp["']\s*\]\s*$/m.test(entry));
}
function jsonServerIsCurrent(kind, json) {
if (kind === "opencode") {
const entry = json.mcp?.[SERVER_KEY];
return (entry?.type === "local" &&
entry.enabled === true &&
Array.isArray(entry.command) &&
entry.command.join("\0") === ["npx", ...NPX_ARGS].join("\0"));
}
const parent = kind === "vscode" ? "servers" : "mcpServers";
const entry = json[parent]?.[SERVER_KEY];
return ((kind !== "vscode" || entry?.type === "stdio") &&
entry?.command === "npx" &&
Array.isArray(entry.args) &&
entry.args.join("\0") === NPX_ARGS.join("\0"));
}
export function configPathFor(client, cwd = process.cwd(), homeDir = homedir()) {
return CLIENTS[client].path(cwd, homeDir);
}
/** Read-only check that the client points at the rolling public MCP package. */
export function inspectClientConfig(client, opts = {}) {
const spec = CLIENTS[client];
const file = spec.path(opts.cwd ?? process.cwd(), opts.homeDir ?? homedir());
if (!existsSync(file)) {
return {
client,
file,
configured: false,
current: false,
issue: "Configuration file does not exist.",
};
}
try {
const source = readFileSync(file, "utf8");
const current = spec.kind === "codex"
? (() => {
const entry = codexServerEntry(source);
return entry !== undefined && codexEntryIsCurrent(entry);
})()
: jsonServerIsCurrent(spec.kind, JSON.parse(source));
return {
client,
file,
configured: true,
current,
...(current
? {}
: {
issue: `Identity Forge is not configured as npx -y ${CLI_PACKAGE_SPEC} mcp.`,
}),
};
}
catch (error) {
return {
client,
file,
configured: true,
current: false,
issue: error instanceof Error ? error.message : String(error),
};
}
}
/** Write the Identity Forge MCP server config for a coding agent. Returns the file written. */

@@ -143,3 +218,3 @@ export function installClient(client, opts = {}) {

}
const file = spec.path(opts.cwd ?? process.cwd());
const file = spec.path(opts.cwd ?? process.cwd(), opts.homeDir ?? homedir());
switch (spec.kind) {

@@ -159,3 +234,7 @@ case "mcpServers":

}
const inspection = inspectClientConfig(client, opts);
if (!inspection.current) {
throw new Error(`Identity Forge wrote ${file}, but verification failed: ${inspection.issue ?? "unknown configuration error"}`);
}
return file;
}

@@ -6,3 +6,5 @@ import { get } from "node:https";

function parseVersion(value) {
const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
const match = value
.trim()
.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
if (!match)

@@ -55,3 +57,5 @@ return undefined;

const right = parseVersion(current);
return left !== undefined && right !== undefined && compareVersions(left, right) > 0;
return (left !== undefined &&
right !== undefined &&
compareVersions(left, right) > 0);
}

@@ -107,3 +111,4 @@ let updateNoticePrinted = false;

const cached = config.updateCheck;
if (cached?.latestVersion && isVersionGreater(cached.latestVersion, currentVersion)) {
if (cached?.latestVersion &&
isVersionGreater(cached.latestVersion, currentVersion)) {
printUpdateNotice(currentVersion, cached.latestVersion);

@@ -140,2 +145,38 @@ }

}
/** Perform an explicit registry check for scripts and `identityforge update-check`. */
export async function getUpdateStatus(currentVersion) {
const cached = readConfig().updateCheck;
let latestVersion;
try {
latestVersion = await fetchLatestVersion();
}
catch {
latestVersion = undefined;
}
if (latestVersion) {
const checkedAt = new Date().toISOString();
try {
updateConfig({ updateCheck: { checkedAt, latestVersion } });
}
catch {
// A read-only home still gets the answer; it merely cannot cache it.
}
return {
currentVersion,
latestVersion,
updateAvailable: isVersionGreater(latestVersion, currentVersion),
checkedAt,
source: "registry",
};
}
return {
currentVersion,
latestVersion: cached?.latestVersion,
updateAvailable: cached?.latestVersion
? isVersionGreater(cached.latestVersion, currentVersion)
: false,
checkedAt: cached?.checkedAt,
source: cached?.latestVersion ? "cache" : "unavailable",
};
}
/** Start the check without giving the network request a chance to hold up a command. */

@@ -142,0 +183,0 @@ export function startUpdateCheck(currentVersion) {

{
"name": "identityforge",
"version": "0.4.3",
"version": "0.4.4",
"mcpName": "io.identityforge/mcp",

@@ -32,3 +32,4 @@ "description": "Agent-native brand naming, domain research, and design systems through one CLI and MCP server.",

"typecheck": "tsc --noEmit -p tsconfig.json",
"prepublishOnly": "npm run build"
"smoke:mcp": "node scripts/smoke-mcp.mjs",
"prepublishOnly": "npm test && npm run typecheck && npm run build && npm run smoke:mcp"
},

@@ -35,0 +36,0 @@ "keywords": [

@@ -15,3 +15,4 @@ <p align="center">

<a href="#mcp-tools">MCP tools</a> ·
<a href="#cli-commands">CLI reference</a>
<a href="#cli-commands">CLI reference</a> ·
<a href="https://identityforge.io/docs">Docs</a>
</p>

@@ -25,2 +26,4 @@

**Free to use.** 69 of 79 design kits are free, with all 6 export formats, the CLI, the MCP server, and the API. No account required. Pro unlocks 10 additional kits, 1,000 AI generations/month, and advanced features.
## For people

@@ -116,2 +119,5 @@

All of them run the same local stdio server via `npx -y identityforge@latest mcp`.
After installation, `identityforge doctor --client <name>` checks that the client
points at the rolling package and initializes the package's MCP server without
calling the Identity Forge API or spending quota.

@@ -149,3 +155,3 @@ ### Install the agent plugin and skill

Once connected, your agent gets 63 tools. Browsing free kits needs no key; scopes are noted where they apply.
Once connected, your agent gets 64 tools. Browsing free kits needs no key; scopes are noted where they apply.

@@ -178,5 +184,5 @@ ### Find a design kit

current CLI or MCP process. A local apply reports one bounded result: files
written, artifacts already current, a safe conflict refusal, or the stage where
it failed. The report contains counts and classifications, never local paths or
error prose. Running as an MCP server, requests also carry the client name
written, artifacts already current, a safe conflict refusal, or the stage where it failed. The report
contains counts and classifications, never local paths or error prose. Running
as an MCP server, requests also carry the client name
your editor or agent already sends in the MCP handshake (`claude-code`,

@@ -342,3 +348,4 @@ `cursor-vscode`, `codex`), so usage can be attributed to a product rather than to

- `check_domains`: DNS plus distinct RDAP, registrar, and optional self-hosted SERP evidence. Basic research costs one unit per unique domain; SERP adds one. Absent DNS records only mean a domain might be available.
- `search_trademarks`: EUIPO automation is coming soon and returns 503 without a provider call until production access is enabled.
- `assess_domain_acquisition`: state whether the goal is a new registration, an aftermarket purchase, or either. It reports registrar registration evidence and bounded public landing-page evidence separately, including literal sale, marketplace, reserved-page, and visible-price signals. It never validates the seller or guarantees purchase. Aftermarket evidence adds one unit per unique domain; an exact successful repeat within ten minutes costs zero.
- `search_trademarks`: the EUIPO official-API adapter runs only when the deployment reports live provider access. Otherwise it returns structured upstream/implementation/runtime status plus an official manual handoff and makes no provider request. The research context lists EUIPO, DPMA, WIPO, and USPTO coverage; screening is never legal clearance.

@@ -390,2 +397,5 @@ ### Build a brand and share it with a client

identityforge whoami # plan, scopes, quota, credits, saved-kit slots (free)
identityforge usage # alias for whoami
identityforge update-check # current/latest package version as JSON
identityforge doctor --client codex # verify config and MCP capabilities without API usage
identityforge logout # remove stored credentials

@@ -478,3 +488,4 @@ identityforge themes # list kits

identityforge naming search --file research-tasks.json
# Coming soon: returns 503 without a provider call until EUIPO production access is enabled
identityforge naming acquisition candidate.com --intent either
# Returns live EUIPO evidence when enabled, otherwise a structured official manual handoff
identityforge naming trademarks "Candidate name" --project <uuid> --candidate <uuid> --nice-classes 9,42

@@ -481,0 +492,0 @@ identityforge naming move <candidate-uuid> --project <uuid> --status finalist --notes "Strong market fit"

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

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