init claude-code
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import type { McpbManifest } from '@anthropic-ai/mcpb'
|
||||
import { errorMessage } from '../errors.js'
|
||||
import { jsonParse } from '../slowOperations.js'
|
||||
|
||||
/**
|
||||
* Parses and validates a DXT manifest from a JSON object.
|
||||
*
|
||||
* Lazy-imports @anthropic-ai/mcpb: that package uses zod v3 which eagerly
|
||||
* creates 24 .bind(this) closures per schema instance (~300 instances between
|
||||
* schemas.js and schemas-loose.js). Deferring the import keeps ~700KB of bound
|
||||
* closures out of the startup heap for sessions that never touch .dxt/.mcpb.
|
||||
*/
|
||||
export async function validateManifest(
|
||||
manifestJson: unknown,
|
||||
): Promise<McpbManifest> {
|
||||
const { McpbManifestSchema } = await import('@anthropic-ai/mcpb')
|
||||
const parseResult = McpbManifestSchema.safeParse(manifestJson)
|
||||
|
||||
if (!parseResult.success) {
|
||||
const errors = parseResult.error.flatten()
|
||||
const errorMessages = [
|
||||
...Object.entries(errors.fieldErrors).map(
|
||||
([field, errs]) => `${field}: ${errs?.join(', ')}`,
|
||||
),
|
||||
...(errors.formErrors || []),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('; ')
|
||||
|
||||
throw new Error(`Invalid manifest: ${errorMessages}`)
|
||||
}
|
||||
|
||||
return parseResult.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and validates a DXT manifest from raw text data.
|
||||
*/
|
||||
export async function parseAndValidateManifestFromText(
|
||||
manifestText: string,
|
||||
): Promise<McpbManifest> {
|
||||
let manifestJson: unknown
|
||||
|
||||
try {
|
||||
manifestJson = jsonParse(manifestText)
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid JSON in manifest.json: ${errorMessage(error)}`)
|
||||
}
|
||||
|
||||
return validateManifest(manifestJson)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and validates a DXT manifest from raw binary data.
|
||||
*/
|
||||
export async function parseAndValidateManifestFromBytes(
|
||||
manifestData: Uint8Array,
|
||||
): Promise<McpbManifest> {
|
||||
const manifestText = new TextDecoder().decode(manifestData)
|
||||
return parseAndValidateManifestFromText(manifestText)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an extension ID from author name and extension name.
|
||||
* Uses the same algorithm as the directory backend for consistency.
|
||||
*/
|
||||
export function generateExtensionId(
|
||||
manifest: McpbManifest,
|
||||
prefix?: 'local.unpacked' | 'local.dxt',
|
||||
): string {
|
||||
const sanitize = (str: string) =>
|
||||
str
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-_.]/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|
||||
const authorName = manifest.author.name
|
||||
const extensionName = manifest.name
|
||||
|
||||
const sanitizedAuthor = sanitize(authorName)
|
||||
const sanitizedName = sanitize(extensionName)
|
||||
|
||||
return prefix
|
||||
? `${prefix}.${sanitizedAuthor}.${sanitizedName}`
|
||||
: `${sanitizedAuthor}.${sanitizedName}`
|
||||
}
|
||||
Reference in New Issue
Block a user