mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Add docs resource and beginnings of a prompt
wip shenanigans
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"@babel/plugin-syntax-typescript": "^7.25.9",
|
||||
"@babel/types": "^7.26.0",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"algoliasearch": "^5.23.3",
|
||||
"prettier": "^3.3.3",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import {
|
||||
McpServer,
|
||||
ResourceTemplate,
|
||||
} from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {z} from 'zod';
|
||||
import {compile} from './compiler';
|
||||
@@ -14,7 +17,11 @@ import {
|
||||
printReactiveFunctionWithOutlined,
|
||||
printFunctionWithOutlined,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {type CallToolResult} from '@modelcontextprotocol/sdk/types.js';
|
||||
import {liteClient, type SearchResponse} from 'algoliasearch/lite';
|
||||
import {DocSearchHit} from './types/algolia';
|
||||
import {printHierarchy} from './utils/algolia';
|
||||
|
||||
const client = liteClient('1FCF9AYYAT', '1b7ad4e1c89e645e351e59d40544eda1');
|
||||
|
||||
export type PrintedCompilerPipelineValue =
|
||||
| {
|
||||
@@ -27,7 +34,7 @@ export type PrintedCompilerPipelineValue =
|
||||
| {kind: 'debug'; name: string; fnName: string | null; value: string};
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'React Compiler',
|
||||
name: 'React',
|
||||
version: '0.0.0',
|
||||
capabilities: {
|
||||
resources: {},
|
||||
@@ -35,9 +42,66 @@ const server = new McpServer({
|
||||
},
|
||||
});
|
||||
|
||||
server.resource(
|
||||
'docs',
|
||||
new ResourceTemplate('docs://search', {list: undefined}),
|
||||
async (uri, {message}) => {
|
||||
const {results} = await client.search<DocSearchHit>({
|
||||
requests: [
|
||||
{
|
||||
query: Array.isArray(message) ? message.join('\n') : message,
|
||||
indexName: 'beta-react',
|
||||
attributesToRetrieve: [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
'hierarchy.lvl3',
|
||||
'hierarchy.lvl4',
|
||||
'hierarchy.lvl5',
|
||||
'hierarchy.lvl6',
|
||||
'content',
|
||||
'url',
|
||||
],
|
||||
attributesToSnippet: [
|
||||
`hierarchy.lvl1:10`,
|
||||
`hierarchy.lvl2:10`,
|
||||
`hierarchy.lvl3:10`,
|
||||
`hierarchy.lvl4:10`,
|
||||
`hierarchy.lvl5:10`,
|
||||
`hierarchy.lvl6:10`,
|
||||
`content:10`,
|
||||
],
|
||||
snippetEllipsisText: '…',
|
||||
hitsPerPage: 30,
|
||||
attributesToHighlight: [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
'hierarchy.lvl3',
|
||||
'hierarchy.lvl4',
|
||||
'hierarchy.lvl5',
|
||||
'hierarchy.lvl6',
|
||||
'content',
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const firstResult = results[0] as SearchResponse<DocSearchHit>;
|
||||
const {hits} = firstResult;
|
||||
return {
|
||||
contents: hits.map(hit => {
|
||||
return {
|
||||
uri: uri.href,
|
||||
text: `${hit.url}\n\n${hit.content ?? printHierarchy(hit)}`,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'analyze',
|
||||
'Use React Compiler to analyze React code',
|
||||
'optimize',
|
||||
'Use React Compiler to optimize React code. Optionally, provide a pass name like "HIR" to see more information.',
|
||||
{
|
||||
text: z.string(),
|
||||
passName: z.string().optional(),
|
||||
@@ -133,12 +197,51 @@ server.tool(
|
||||
} catch (err) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{type: 'text', text: `Error: ${err}`}],
|
||||
content: [{type: 'text', text: `Error: ${err.stack}`}],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
server.prompt('review-code', {code: z.string()}, ({code}) => ({
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `# React Expert Assistant
|
||||
|
||||
## Role
|
||||
You are a React assistant that helps users write better React, following the rules of React in the react.dev docs.
|
||||
|
||||
## Available Resources
|
||||
- 'docs': Look up documentation from React.dev. Returns urls that you must retrieve so you can view its content.
|
||||
|
||||
## Available Tools
|
||||
- 'optimize': Run the users code through React Compiler
|
||||
|
||||
## Process
|
||||
1. Check if the users code follows the rules of React
|
||||
- Point out issues in the users code if it does not
|
||||
|
||||
2. Run the compiler on the users code and see if it can successfully optimize the code
|
||||
- If the same code is returned by the compiler, it has bailed out or there is nothing to optimize
|
||||
|
||||
3. Iterate
|
||||
- Guide the user on making adjustments to their code so that it can be successfully optimized.
|
||||
|
||||
## Special Instructions
|
||||
Make sure to use information from react.dev as the main reference for your React knowledge. Information from unofficial sources such as blogs and articles can also be used but may sometimes be outdated or contain poor advice.
|
||||
|
||||
## Example 1: <todo>
|
||||
|
||||
## Example 2: <todo>
|
||||
`,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// https://github.com/algolia/docsearch/blob/15ebcba606b281aa0dddc4ccb8feb19d396bf79e/packages/docsearch-react/src/types/DocSearchHit.ts
|
||||
type ContentType =
|
||||
| 'content'
|
||||
| 'lvl0'
|
||||
| 'lvl1'
|
||||
| 'lvl2'
|
||||
| 'lvl3'
|
||||
| 'lvl4'
|
||||
| 'lvl5'
|
||||
| 'lvl6';
|
||||
|
||||
interface DocSearchHitAttributeHighlightResult {
|
||||
value: string;
|
||||
matchLevel: 'full' | 'none' | 'partial';
|
||||
matchedWords: string[];
|
||||
fullyHighlighted?: boolean;
|
||||
}
|
||||
|
||||
interface DocSearchHitHighlightResultHierarchy {
|
||||
lvl0: DocSearchHitAttributeHighlightResult;
|
||||
lvl1: DocSearchHitAttributeHighlightResult;
|
||||
lvl2: DocSearchHitAttributeHighlightResult;
|
||||
lvl3: DocSearchHitAttributeHighlightResult;
|
||||
lvl4: DocSearchHitAttributeHighlightResult;
|
||||
lvl5: DocSearchHitAttributeHighlightResult;
|
||||
lvl6: DocSearchHitAttributeHighlightResult;
|
||||
}
|
||||
|
||||
interface DocSearchHitHighlightResult {
|
||||
content: DocSearchHitAttributeHighlightResult;
|
||||
hierarchy: DocSearchHitHighlightResultHierarchy;
|
||||
hierarchy_camel: DocSearchHitHighlightResultHierarchy[];
|
||||
}
|
||||
|
||||
interface DocSearchHitAttributeSnippetResult {
|
||||
value: string;
|
||||
matchLevel: 'full' | 'none' | 'partial';
|
||||
}
|
||||
|
||||
interface DocSearchHitSnippetResult {
|
||||
content: DocSearchHitAttributeSnippetResult;
|
||||
hierarchy: DocSearchHitHighlightResultHierarchy;
|
||||
hierarchy_camel: DocSearchHitHighlightResultHierarchy[];
|
||||
}
|
||||
|
||||
export declare type DocSearchHit = {
|
||||
objectID: string;
|
||||
content: string | null;
|
||||
url: string;
|
||||
url_without_anchor: string;
|
||||
type: ContentType;
|
||||
anchor: string | null;
|
||||
hierarchy: {
|
||||
lvl0: string;
|
||||
lvl1: string;
|
||||
lvl2: string | null;
|
||||
lvl3: string | null;
|
||||
lvl4: string | null;
|
||||
lvl5: string | null;
|
||||
lvl6: string | null;
|
||||
};
|
||||
_highlightResult: DocSearchHitHighlightResult;
|
||||
_snippetResult: DocSearchHitSnippetResult;
|
||||
_rankingInfo?: {
|
||||
promoted: boolean;
|
||||
nbTypos: number;
|
||||
firstMatchedWord: number;
|
||||
proximityDistance?: number;
|
||||
geoDistance: number;
|
||||
geoPrecision?: number;
|
||||
nbExactWords: number;
|
||||
words: number;
|
||||
filters: number;
|
||||
userScore: number;
|
||||
matchedGeoLocation?: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
distance: number;
|
||||
};
|
||||
};
|
||||
_distinctSeqID?: number;
|
||||
__autocomplete_indexName?: string;
|
||||
__autocomplete_queryID?: string;
|
||||
__autocomplete_algoliaCredentials?: {
|
||||
appId: string;
|
||||
apiKey: string;
|
||||
};
|
||||
__autocomplete_id?: number;
|
||||
};
|
||||
|
||||
export type InternalDocSearchHit = DocSearchHit & {
|
||||
__docsearch_parent: InternalDocSearchHit | null;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {DocSearchHit, InternalDocSearchHit} from '../types/algolia';
|
||||
|
||||
export function printHierarchy(
|
||||
hit: DocSearchHit | InternalDocSearchHit,
|
||||
): string {
|
||||
let val = `${hit.hierarchy.lvl0} > ${hit.hierarchy.lvl1}`;
|
||||
if (hit.hierarchy.lvl2 != null) {
|
||||
val = val.concat(` > ${hit.hierarchy.lvl2}`);
|
||||
}
|
||||
if (hit.hierarchy.lvl3 != null) {
|
||||
val = val.concat(` > ${hit.hierarchy.lvl3}`);
|
||||
}
|
||||
if (hit.hierarchy.lvl4 != null) {
|
||||
val = val.concat(` > ${hit.hierarchy.lvl4}`);
|
||||
}
|
||||
if (hit.hierarchy.lvl5 != null) {
|
||||
val = val.concat(` > ${hit.hierarchy.lvl5}`);
|
||||
}
|
||||
if (hit.hierarchy.lvl6 != null) {
|
||||
val = val.concat(` > ${hit.hierarchy.lvl6}`);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -12,7 +12,9 @@ export default defineConfig({
|
||||
platform: 'node',
|
||||
target: 'es2022',
|
||||
banner: {
|
||||
js: `/**
|
||||
js: `#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
|
||||
@@ -7,6 +7,122 @@
|
||||
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
|
||||
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
|
||||
|
||||
"@algolia/client-abtesting@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.23.3.tgz#efc2ad31792675a26cfac12cc0ef3adbd4766a11"
|
||||
integrity sha512-yHI0hBwYcNPc+nJoHPTmmlP8pG6nstCEhpHaZQCDwLZhdMtNhd1hliZMCtLgNnvd1yKEgTt/ZDnTSdZLehfKdA==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/client-analytics@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.23.3.tgz#ebc613413f7ebad5b0a2631d7a72ca436109b239"
|
||||
integrity sha512-/70Ey+nZm4bRr2DcNrGU251YIn9lDu0g8xeP4jTCyunGRNFZ/d8hQAw9El34pcTpO1QDojJWAi6ywKIrUaks9w==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/client-common@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.23.3.tgz#c5eb2256d6fe1390cb2bf545b52ea78ecae472e7"
|
||||
integrity sha512-fkpbPclIvaiyw3ADKRBCxMZhrNx/8//6DClfWGxeEiTJ0HEEYtHlqE6GjAkEJubz4v1ioCQkhZwMoFfFct2/vQ==
|
||||
|
||||
"@algolia/client-insights@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.23.3.tgz#312add9292887d3e41c0161028b27ee54adef9c3"
|
||||
integrity sha512-TXc5Ve6QOCihWCTWY9N56CZxF1iovzpBWBUhQhy6JSiUfX3MXceV3saV+sXHQ1NVt2NKkyUfEspYHBsTrYzIDg==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/client-personalization@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.23.3.tgz#d5be045bd93b9896f9e65d17af8ece5d89507e95"
|
||||
integrity sha512-JlReruxxiw9LB53jF/BmvVV+c0thiWQUHRdgtbVIEusvRaiX1IdpWJSPQExEtBQ7VFg89nP8niCzWtA34ktKSA==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/client-query-suggestions@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.23.3.tgz#d47a6288dc8ea64083f30a2aa71c3044d2887bb0"
|
||||
integrity sha512-GDEExFMXwx0ScE0AZUA4F6ssztdJvGcXUkdWmWyt2hbYz43ukqmlVJqPaYgGmWdjJjvTx+dNF/hcinwWuXbCug==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/client-search@5.23.3", "@algolia/client-search@^5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.23.3.tgz#e8df14c9aa257c81b8aeaa3cb80cb2af484b9c61"
|
||||
integrity sha512-mwofV6tGo0oHt4BPi+S5eLC3wnhOa4A1OVgPxetTxZuetod+2W4cxKavUW2v/Ma5CABXPLooXX+g9E67umELZw==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/ingestion@1.23.3":
|
||||
version "1.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.23.3.tgz#5ed0a38bfae72222b12579255cdca42bba3f62ce"
|
||||
integrity sha512-Zxgmi7Hk4lI52YFphzzJekUqWxYxVjY2GrCpOxV+QiojvUi8Ru+knq6REcwLHFSwpwaDh2Th5pOefMpn4EkQCw==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/monitoring@1.23.3":
|
||||
version "1.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.23.3.tgz#f4748e7ccdf4d84e5044f34e231f9b93fff526b1"
|
||||
integrity sha512-zi/IqvsmFW4E5gMaovAE4KRbXQ+LDYpPGG1nHtfuD5u3SSuQ31fT1vX2zqb6PbPTlgJMEmMk91Mbb7fIKmbQUw==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/recommend@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.23.3.tgz#76b0d0df2e13a722512b75844e5dd954a370f182"
|
||||
integrity sha512-C9TwbT1zGwULLXGSUSB+G7o/30djacPmQcsTHepvT47PVfPr2ISK/5QVtUnjMU84LEP8uNjuPUeM4ZeWVJ2iuQ==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
"@algolia/requester-browser-xhr@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.23.3.tgz#a66b17495be4a4d3fff85efc9d2ec3589481b7d8"
|
||||
integrity sha512-/7oYeUhYzY0lls7WtkAURM6wy21/Wwmq9GdujW1MpoYVC0ATXXxwCiAfOpYL9xdWxLV0R3wjyD+yZEni+nboKg==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
|
||||
"@algolia/requester-fetch@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.23.3.tgz#85bb4a0894d4956122699cc541935a31d9de4be0"
|
||||
integrity sha512-r/4fKz4t+bSU1KdjRq+swdNvuGfJ0spV8aFTHPtcsF+1ZaN/VqmdXrTe5NkaZLSztFeMqKwZlJIVvE7VuGlFtw==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
|
||||
"@algolia/requester-node-http@5.23.3":
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.23.3.tgz#67f9034a62a571f3fa9e840ed00f3e2cf9dd679b"
|
||||
integrity sha512-UZiTNmUBQFPl3tUKuXaDd8BxEC0t0ny86wwW6XgwfM9IQf4PrzuMpvuOGIJMcCGlrNolZDEI0mcbz/tqRdKW7A==
|
||||
dependencies:
|
||||
"@algolia/client-common" "5.23.3"
|
||||
|
||||
"@ampproject/remapping@^2.2.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
|
||||
@@ -3665,6 +3781,25 @@ ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
algoliasearch@^5.23.3:
|
||||
version "5.23.3"
|
||||
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.23.3.tgz#ac2a0541efac4dcd63be1ed98bfbd0583095dec2"
|
||||
integrity sha512-0JlUaY/hl3LrKvbidI5FysEi2ggAlcTHM8AHV2UsrJUXnNo8/lWBfhzc1b7o8bK3YZNiU26JtLyT9exoj5VBgA==
|
||||
dependencies:
|
||||
"@algolia/client-abtesting" "5.23.3"
|
||||
"@algolia/client-analytics" "5.23.3"
|
||||
"@algolia/client-common" "5.23.3"
|
||||
"@algolia/client-insights" "5.23.3"
|
||||
"@algolia/client-personalization" "5.23.3"
|
||||
"@algolia/client-query-suggestions" "5.23.3"
|
||||
"@algolia/client-search" "5.23.3"
|
||||
"@algolia/ingestion" "1.23.3"
|
||||
"@algolia/monitoring" "1.23.3"
|
||||
"@algolia/recommend" "5.23.3"
|
||||
"@algolia/requester-browser-xhr" "5.23.3"
|
||||
"@algolia/requester-fetch" "5.23.3"
|
||||
"@algolia/requester-node-http" "5.23.3"
|
||||
|
||||
ansi-colors@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
|
||||
|
||||
Reference in New Issue
Block a user