Openpm (Independent Publisher) (#2884)
* Revert "Merge branch 'microsoft:dev' into dev" This reverts commit096a7c3199
, reversing changes made tocb0948670f
. * Revert "Merge branch 'microsoft:dev' into dev" This reverts commitcb0948670f
, reversing changes made todc26e8a026
. * Revert "Revert "Merge branch 'microsoft:dev' into dev"" This reverts commit01764d0a6d
. * Revert "Revert "Merge branch 'microsoft:dev' into dev"" This reverts commita3f086ef0f
. * Add files via upload * Revert "Add files via upload" This reverts commiteeff7b7a02
. * Add files via upload * Delete independent-publisher-connectors/FileIO directory * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Revert "Add files via upload" This reverts commit1d85506958
. * Add files via upload
This commit is contained in:
Родитель
01eb43bd9b
Коммит
7181e27a62
|
@ -0,0 +1,720 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "openpm",
|
||||
"description": "Openpm is an open source package-manager for OpenAPI files. AIs can use consume packages from openpm in a similar fashion to how ChatGPT plugins work.",
|
||||
"contact": {
|
||||
"name": "Troy Taylor",
|
||||
"url": "https://www.hitachisolutions.com",
|
||||
"email": "ttaylor@hitachisolutions.com"
|
||||
}
|
||||
},
|
||||
"host": "openpm.ai",
|
||||
"basePath": "/api",
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/packages": {
|
||||
"get": {
|
||||
"description": "Returns all packages.",
|
||||
"summary": "Get packages",
|
||||
"operationId": "PackagesGET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"x-ms-summary": "Limit",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"maximum": 500,
|
||||
"exclusiveMaximum": false,
|
||||
"description": "How many package to return per page (default 500)."
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"x-ms-summary": "Page",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Page number (default 1)."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A paginated list of packages.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PackagesResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-ms-visibility": "important"
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a package.",
|
||||
"summary": "Create package",
|
||||
"operationId": "PackagesPOST",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A package.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/packages/search": {},
|
||||
"/packages/lookup": {
|
||||
"get": {
|
||||
"description": "Retrieve a package by identifier.",
|
||||
"summary": "Get package",
|
||||
"operationId": "PackagesLookupGET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ids",
|
||||
"in": "query",
|
||||
"x-ms-summary": "IDs",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"description": "CSV list of package identifiers."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A paginated list of packages.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/packages/connected": {},
|
||||
"/packages/{packageId}": {
|
||||
"get": {
|
||||
"description": "Returns a package by identifier.",
|
||||
"summary": "Get package by identifier",
|
||||
"operationId": "PackagesByPackageIdGET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "packageId",
|
||||
"in": "path",
|
||||
"x-ms-summary": "Package ID",
|
||||
"x-ms-url-encoding": "single",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "The package identifier."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A package.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Updates a package.",
|
||||
"summary": "Update package",
|
||||
"operationId": "PackagesByPackageIdPOST",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "packageId",
|
||||
"in": "path",
|
||||
"x-ms-summary": "Package ID",
|
||||
"x-ms-url-encoding": "single",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "The package identifier."
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A package.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/packages/{packageId}/openapi": {
|
||||
"get": {
|
||||
"description": "Returns an OpenAPI specification for the API.",
|
||||
"summary": "Get definition",
|
||||
"operationId": "PackagesOpenapiByPackageId_GET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "packageId",
|
||||
"in": "path",
|
||||
"x-ms-summary": "Package ID",
|
||||
"x-ms-url-encoding": "single",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "The package identifier."
|
||||
},
|
||||
{
|
||||
"name": "format",
|
||||
"in": "query",
|
||||
"x-ms-summary": "Format",
|
||||
"required": false,
|
||||
"enum": [
|
||||
"json",
|
||||
"yaml"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Format of the OpenAPI spec (default json)."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An OpenAPI spec.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PackagesOpenapiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/packages/{packageId}/ai-plugin": {
|
||||
"get": {
|
||||
"description": "Returns an OpenAI plugin manifest.",
|
||||
"summary": "Get plugin manifest",
|
||||
"operationId": "PackagesAiPluginByPackageId_GET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "packageId",
|
||||
"in": "path",
|
||||
"x-ms-summary": "Package ID",
|
||||
"x-ms-url-encoding": "single",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "The package identifier."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An OpenAPI spec.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AiPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ai-plugins/search": {
|
||||
"get": {
|
||||
"description": "Searches packages and responds with the AI plugin manifest.",
|
||||
"summary": "Search package manifests",
|
||||
"operationId": "AiPluginsSearchGET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"x-ms-summary": "Limit",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"maximum": 100,
|
||||
"exclusiveMaximum": false,
|
||||
"description": "How many package to return per page (default 10)."
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"x-ms-summary": "Query",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "Search query."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A list of AI plugins (not paginated).",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AiPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ai-plugins/lookup": {
|
||||
"get": {
|
||||
"description": "Looks up AI plugins by package identifiers.",
|
||||
"summary": "Get plugin by package ID",
|
||||
"operationId": "AiPluginsLookupGET",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ids",
|
||||
"in": "query",
|
||||
"x-ms-summary": "IDs",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"description": "CSV list of package identifiers."
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A list of AI plugins.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AiPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"PaginatedPackage": {
|
||||
"title": "PaginatedPackage",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Package"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"description": "Total number of items.",
|
||||
"title": "Total",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"page": {
|
||||
"description": "Current page.",
|
||||
"title": "Page",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Items per page.",
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items",
|
||||
"total",
|
||||
"page",
|
||||
"limit"
|
||||
]
|
||||
},
|
||||
"Package": {
|
||||
"title": "Package",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The package identifier.",
|
||||
"title": "ID",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Package name.",
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"machine_name": {
|
||||
"description": "Package name (for machines).",
|
||||
"title": "Machine Name",
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"description": "Package domain.",
|
||||
"title": "Domain",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "Package version.",
|
||||
"title": "Version",
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "Package creation date.",
|
||||
"title": "Created At",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "Package last update date.",
|
||||
"title": "Updated At",
|
||||
"type": "string"
|
||||
},
|
||||
"published_at": {
|
||||
"description": "Package publication date.",
|
||||
"title": "Published At",
|
||||
"type": "string"
|
||||
},
|
||||
"logo_url": {
|
||||
"description": "Package logo url.",
|
||||
"title": "Logo URL",
|
||||
"type": "string"
|
||||
},
|
||||
"contact_email": {
|
||||
"description": "Package contact email.",
|
||||
"title": "Contact Email",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Package description.",
|
||||
"title": "Description",
|
||||
"type": "string"
|
||||
},
|
||||
"machine_description": {
|
||||
"description": "Package description (for machines).",
|
||||
"title": "Machine Description",
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "Package owner user id.",
|
||||
"title": "User ID",
|
||||
"type": "string"
|
||||
},
|
||||
"openapi": {
|
||||
"description": "Package OpenAPI specification.",
|
||||
"title": "OpenAPI",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"PackageLite": {
|
||||
"title": "PackageLite",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The package identifier.",
|
||||
"title": "ID",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Package name.",
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"machine_name": {
|
||||
"description": "Package name (for machines).",
|
||||
"title": "Machine Name",
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"description": "Package domain.",
|
||||
"title": "Domain",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "Package version.",
|
||||
"title": "Version",
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "Package creation date.",
|
||||
"title": "Created At",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "Package last update date.",
|
||||
"title": "Updated At",
|
||||
"type": "string"
|
||||
},
|
||||
"published_at": {
|
||||
"description": "Package publication date.",
|
||||
"title": "Published At",
|
||||
"type": "string"
|
||||
},
|
||||
"logo_url": {
|
||||
"description": "Package logo url.",
|
||||
"title": "Logo URL",
|
||||
"type": "string"
|
||||
},
|
||||
"contact_email": {
|
||||
"description": "Package contact email.",
|
||||
"title": "Contact Email",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Package description.",
|
||||
"title": "Description",
|
||||
"type": "string"
|
||||
},
|
||||
"machine_description": {
|
||||
"description": "Package description (for machines).",
|
||||
"title": "Machine Description",
|
||||
"type": "string"
|
||||
},
|
||||
"user_id": {
|
||||
"description": "Package owner user id.",
|
||||
"title": "User ID",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"AiPlugin": {
|
||||
"title": "AiPlugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schema_version": {
|
||||
"description": "The schema version.",
|
||||
"title": "Schema Version",
|
||||
"type": "string"
|
||||
},
|
||||
"name_for_human": {
|
||||
"description": "The name for human.",
|
||||
"title": "Human Name",
|
||||
"type": "string"
|
||||
},
|
||||
"name_for_model": {
|
||||
"description": "The name for model.",
|
||||
"title": "Model Name",
|
||||
"type": "string"
|
||||
},
|
||||
"description_for_human": {
|
||||
"description": "The description for human.",
|
||||
"title": "Human Description",
|
||||
"type": "string"
|
||||
},
|
||||
"description_for_model": {
|
||||
"description": "The description for model.",
|
||||
"title": "Description Model",
|
||||
"type": "string"
|
||||
},
|
||||
"auth": {
|
||||
"$ref": "#/definitions/Auth"
|
||||
},
|
||||
"api": {
|
||||
"$ref": "#/definitions/Api"
|
||||
},
|
||||
"logo_url": {
|
||||
"description": "The logo URL address.",
|
||||
"title": "Logo URL",
|
||||
"type": "string"
|
||||
},
|
||||
"contact_email": {
|
||||
"description": "The contact email address.",
|
||||
"title": "Contact Email",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"schema_version",
|
||||
"name_for_human",
|
||||
"name_for_model",
|
||||
"description_for_human",
|
||||
"description_for_model",
|
||||
"auth",
|
||||
"api",
|
||||
"logo_url",
|
||||
"contact_email"
|
||||
]
|
||||
},
|
||||
"Api": {
|
||||
"title": "API",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "The type.",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"description": "The URL address.",
|
||||
"title": "URL",
|
||||
"type": "string"
|
||||
},
|
||||
"is_user_authenticated": {
|
||||
"description": "Whether user is authenticated.",
|
||||
"title": "User Authenticated",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Auth": {
|
||||
"title": "Auth",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "The type.",
|
||||
"title": "Type",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"format": {
|
||||
"description": "The format.",
|
||||
"title": "Format",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"json",
|
||||
"yaml"
|
||||
]
|
||||
},
|
||||
"Info": {
|
||||
"title": "Info",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"description": "The title.",
|
||||
"title": "Title",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "The version.",
|
||||
"title": "Version",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "The description.",
|
||||
"title": "Description",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PackagesOpenapiResponse": {
|
||||
"title": "PackagesOpenapiResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"openapi": {
|
||||
"description": "The OpenAPI.",
|
||||
"title": "OpenAPI",
|
||||
"type": "string"
|
||||
},
|
||||
"info": {
|
||||
"$ref": "#/definitions/Info"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PackagesResponse": {
|
||||
"title": "PackagesResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PackageLite"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"description": "The total.",
|
||||
"title": "Total",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"page": {
|
||||
"description": "The page.",
|
||||
"title": "Page",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"limit": {
|
||||
"description": "The limit.",
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PackagesSearchResponse": {
|
||||
"title": "PackagesSearchResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PackageLite"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"description": "The total.",
|
||||
"title": "Total",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"page": {
|
||||
"description": "The page.",
|
||||
"title": "Page",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"limit": {
|
||||
"description": "The limit.",
|
||||
"title": "Limit",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {},
|
||||
"responses": {},
|
||||
"securityDefinitions": {
|
||||
"API Key": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "Authorization"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API Key": []
|
||||
}
|
||||
],
|
||||
"tags": [],
|
||||
"x-ms-connector-metadata": [
|
||||
{
|
||||
"propertyName": "Website",
|
||||
"propertyValue": "https://openpm.ai/"
|
||||
},
|
||||
{
|
||||
"propertyName": "Privacy policy",
|
||||
"propertyValue": "https://openpm.ai/privacy"
|
||||
},
|
||||
{
|
||||
"propertyName": "Categories",
|
||||
"propertyValue": "AI;Website"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"properties": {
|
||||
"connectionParameters": {
|
||||
"api_key": {
|
||||
"type": "securestring",
|
||||
"uiDefinition": {
|
||||
"displayName": "API Key (in the form 'Bearer API_Key')",
|
||||
"description": "The API Key (in the form 'Bearer API_Key') for this api",
|
||||
"tooltip": "Provide your API Key (in the form 'Bearer API_Key')",
|
||||
"constraints": {
|
||||
"tabIndex": 2,
|
||||
"clearText": false,
|
||||
"required": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"iconBrandColor": "#da3b01",
|
||||
"capabilities": [],
|
||||
"publisher": "Troy Taylor",
|
||||
"stackOwner": "openpm"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
# openpm
|
||||
Openpm is an open source package-manager for OpenAPI files. AIs can use consume packages from openpm in a similar fashion to how ChatGPT plugins work.
|
||||
|
||||
## Publisher: Troy Taylor, Hitachi Solutions
|
||||
|
||||
## Prerequisites
|
||||
You need to sign up for an account with [openpm](https://openpm.ai/auth).
|
||||
|
||||
## Obtaining Credentials
|
||||
Once you are logged in to your account, you can generate an API key on your account page.
|
||||
|
||||
## Supported Operations
|
||||
### Get packages
|
||||
Returns all packages.
|
||||
### Create package
|
||||
Creates a package.
|
||||
### Get package
|
||||
Retrieve a package by identifier.
|
||||
### Get package by identifier
|
||||
Returns a package by identifier.
|
||||
### Update package
|
||||
Updates a package.
|
||||
### Get definition
|
||||
Returns an OpenAPI specification for the API.
|
||||
### Get plugin manifest
|
||||
Returns an OpenAI plugin manifest.
|
||||
### Search package manifests
|
||||
Searches packages and responds with the AI plugin manifest.
|
||||
### Get plugin by package ID
|
||||
Looks up AI plugins by package identifiers.
|
||||
|
||||
## Known Issues and Limitations
|
||||
There are no known issues at this time.
|
Загрузка…
Ссылка в новой задаче