* feat(mcp): add list_available_fonts tool for font discovery
The existing list_fonts tool reports only fonts currently used in the
current page (it walks every TEXT node and groups by fontFamily).
Agents authoring new text have no way to discover what fonts the host
can render — they have to guess and hope set_font succeeds.
This adds list_available_fonts, which returns the set of font
families the host advertises. On desktop this is the system fonts
font-kit enumerates plus any bundled fonts the host registers; in
non-desktop runtimes that don't expose enumeration, it returns an
empty list with an explanatory note rather than throwing.
The optional `family` filter mirrors list_fonts for a substring
search, so agents can do `list_available_fonts({family: "mono"})` to
narrow before set_font.
Wired through:
- packages/core/src/figma-api/index.ts: adds optional
listAvailableFontFamilies hook on FigmaAPI (same pattern as
exportImage). Optional so non-desktop hosts aren't forced to
implement it.
- packages/core/src/tools/read.ts: defines the tool.
- packages/core/src/tools/registry.ts: registers it in CORE_TOOLS so
the MCP server advertises it.
- src/automation/figma-factory.ts: provides the desktop
implementation by delegating to engine/fonts.ts#listFamilies which
already wraps the Tauri list_system_fonts sidecar.
* refactor(mcp): reshape to listAvailableFontsAsync matching Figma API
Per review feedback on #210: FigmaAPI mirrors the Figma Plugin API, and
Figma already exposes figma.listAvailableFontsAsync(): Promise<Font[]>
where Font = { fontName: { family, style } }. Replacing the custom
listAvailableFontFamilies hook with the Figma-shaped method means eval'd
plugin code and any future plugin-compat layer get font enumeration for
free. Follows the same pattern as loadFontAsync on FigmaAPI.
- packages/core/src/figma-api/proxy.ts: export FigmaFont type
({ fontName: FigmaFontName }) alongside FigmaFontName.
- packages/core/src/figma-api/index.ts: drop optional
listAvailableFontFamilies; add listAvailableFontsAsync() as a real
method (non-optional) with a safe [] default, mirroring loadFontAsync.
- packages/core/src/tools/read.ts: list_available_fonts execute() now
consumes Font[] and dedupes families via a Set. Drops the
host-capability fallback note — the new method always returns an
array, so a non-desktop host just yields {count: 0, fonts: []}.
- src/engine/fonts.ts: add listFonts() returning TauriFontFamily[]
({family, styles[]}) so the factory can emit one Font per (family,
style) pair.
- src/automation/figma-factory.ts: override listAvailableFontsAsync,
flattening {family, styles[]}[] to Font[].
- oxlint.json: add listFonts to the engine/fonts allowlist.
Tool public shape unchanged (still returns {count, fonts: string[]})
so agents calling list_available_fonts see no change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
214 lines
5.8 KiB
JSON
214 lines
5.8 KiB
JSON
{
|
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
"plugins": [
|
|
"typescript",
|
|
"import",
|
|
"unicorn",
|
|
"vue"
|
|
],
|
|
"jsPlugins": [
|
|
"./lint/plugin.js"
|
|
],
|
|
"env": {
|
|
"browser": true,
|
|
"es2024": true
|
|
},
|
|
"rules": {
|
|
"no-unused-vars": "warn",
|
|
"no-console": ["warn", { "allow": ["warn", "error", "debug", "time", "timeEnd"] }],
|
|
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
|
"prefer-const": "error",
|
|
"radix": "error",
|
|
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
"paths": [
|
|
{
|
|
"name": "@/types",
|
|
"message": "Import types directly from @open-pencil/core."
|
|
}
|
|
],
|
|
"patterns": [
|
|
{
|
|
"group": [
|
|
"@/engine/*"
|
|
],
|
|
"message": "Import from @open-pencil/core unless the module adds platform-specific logic.",
|
|
"allowImportNames": [
|
|
"loadFont",
|
|
"listFamilies",
|
|
"listFonts",
|
|
"preloadFonts"
|
|
]
|
|
},
|
|
{
|
|
"group": [
|
|
"../../packages/*",
|
|
"../../../packages/*"
|
|
],
|
|
"message": "Use the workspace package name (e.g. @open-pencil/core) instead of relative paths to packages/."
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"no-empty-function": "error",
|
|
"max-lines": [
|
|
"warn",
|
|
{
|
|
"max": 600
|
|
}
|
|
],
|
|
"complexity": [
|
|
"error",
|
|
{
|
|
"max": 20
|
|
}
|
|
],
|
|
"typescript/no-explicit-any": "warn",
|
|
"typescript/no-non-null-assertion": "warn",
|
|
"typescript/no-floating-promises": "error",
|
|
"typescript/no-misused-promises": "error",
|
|
"typescript/consistent-type-imports": "error",
|
|
"typescript/consistent-return": "error",
|
|
"typescript/no-unnecessary-condition": "error",
|
|
"typescript/no-unnecessary-type-assertion": "error",
|
|
"typescript/no-unnecessary-type-parameters": "error",
|
|
"typescript/no-unnecessary-boolean-literal-compare": "error",
|
|
"typescript/no-unnecessary-template-expression": "error",
|
|
"typescript/no-unnecessary-type-arguments": "error",
|
|
"typescript/prefer-optional-chain": "error",
|
|
"typescript/prefer-for-of": "error",
|
|
"import/no-cycle": "error",
|
|
"import/no-self-import": "error",
|
|
"import/no-duplicates": "error",
|
|
"import/no-mutable-exports": "error",
|
|
"unicorn/no-instanceof-array": "error",
|
|
"unicorn/no-typeof-undefined": "error",
|
|
"unicorn/no-nested-ternary": "error",
|
|
"unicorn/no-useless-fallback-in-spread": "error",
|
|
"unicorn/no-length-as-slice-end": "error",
|
|
"unicorn/no-unnecessary-await": "error",
|
|
"unicorn/prefer-string-starts-ends-with": "error",
|
|
"unicorn/consistent-existence-index-check": "error",
|
|
"vue/no-arrow-functions-in-watch": "error",
|
|
"vue/no-deprecated-destroyed-lifecycle": "error",
|
|
"vue/no-export-in-script-setup": "error",
|
|
"vue/no-lifecycle-after-await": "error",
|
|
"vue/no-import-compiler-macros": "error",
|
|
"vue/prefer-import-from-vue": "error",
|
|
"vue/valid-define-emits": "error",
|
|
"vue/valid-define-props": "error",
|
|
"vue/no-required-prop-with-default": "error",
|
|
"vue/no-multiple-slot-args": "error",
|
|
"vue/define-emits-declaration": "error",
|
|
"vue/define-props-destructuring": "error",
|
|
"vue/require-typed-ref": "error",
|
|
"open-pencil/no-inline-named-types": [
|
|
"error",
|
|
{
|
|
"Vector": "x:number,y:number",
|
|
"Color": "a:number,b:number,g:number,r:number",
|
|
"GUID": "localID:number,sessionID:number",
|
|
"Rect": "height:number,width:number,x:number,y:number",
|
|
"Matrix": "m00:number,m01:number,m02:number,m10:number,m11:number,m12:number"
|
|
}
|
|
],
|
|
"open-pencil/no-structuredclone-scene-arrays": "error",
|
|
"open-pencil/no-math-random": "error",
|
|
"open-pencil/no-hand-rolled-color": "error",
|
|
"open-pencil/no-raw-console-format": "off",
|
|
"open-pencil/no-silent-catch": "error",
|
|
"open-pencil/no-typeof-window-check": "error"
|
|
},
|
|
"overrides": [
|
|
{
|
|
"files": [
|
|
"**/*.test.ts",
|
|
"**/*.test.tsx"
|
|
],
|
|
"rules": {
|
|
"typescript/no-explicit-any": "off",
|
|
"typescript/no-non-null-assertion": "off",
|
|
"open-pencil/no-silent-catch": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"**/tools/ai-adapter.ts"
|
|
],
|
|
"rules": {
|
|
"typescript/no-explicit-any": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"**/figma-api.ts"
|
|
],
|
|
"rules": {
|
|
"no-console": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"**/render/render.ts"
|
|
],
|
|
"rules": {
|
|
"typescript/no-implied-eval": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"**/kiwi/kiwi-schema/**"
|
|
],
|
|
"rules": {
|
|
"typescript/no-explicit-any": "off",
|
|
"typescript/no-non-null-assertion": "off",
|
|
"typescript/no-implied-eval": "off",
|
|
"typescript/prefer-for-of": "off",
|
|
"typescript/prefer-optional-chain": "off",
|
|
"typescript/consistent-type-imports": "off",
|
|
"typescript/no-unnecessary-condition": "off",
|
|
"import/no-mutable-exports": "off",
|
|
"complexity": "off",
|
|
"prefer-const": "off",
|
|
"eqeqeq": "off",
|
|
"radix": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"src/stores/editor.ts",
|
|
"src/composables/use-canvas-input.ts",
|
|
"src/demo.ts"
|
|
],
|
|
"rules": {
|
|
"max-lines": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"packages/cli/src/**",
|
|
"packages/mcp/src/**"
|
|
],
|
|
"rules": {
|
|
"no-console": "off"
|
|
}
|
|
},
|
|
{
|
|
"files": [
|
|
"packages/cli/src/commands/**"
|
|
],
|
|
"rules": {
|
|
"open-pencil/no-raw-console-format": "error"
|
|
}
|
|
}
|
|
],
|
|
"ignorePatterns": [
|
|
"node_modules",
|
|
"dist",
|
|
"desktop",
|
|
"*.config.*"
|
|
]
|
|
}
|