forked from ultraworkers/claw-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelpers.ts
More file actions
88 lines (76 loc) · 2.54 KB
/
Copy pathhelpers.ts
File metadata and controls
88 lines (76 loc) · 2.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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}`
}