feat: implement skills marketplace API with CRUD operations and gateway integration

This commit is contained in:
Abhimanyu Saharan
2026-02-13 23:11:54 +05:30
committed by Abhimanyu Saharan
parent db510a8612
commit e7b5df0bce
22 changed files with 2246 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ export interface ApprovalCreate {
* @maximum 100
*/
confidence: number;
lead_reasoning?: string | null;
payload?: ApprovalCreatePayload;
rubric_scores?: ApprovalCreateRubricScores;
status?: ApprovalCreateStatus;

View File

@@ -114,6 +114,7 @@ export * from "./getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams
export * from "./healthHealthGet200";
export * from "./healthzHealthzGet200";
export * from "./hTTPValidationError";
export * from "./installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams";
export * from "./limitOffsetPageTypeVarCustomizedActivityEventRead";
export * from "./limitOffsetPageTypeVarCustomizedActivityTaskCommentFeedItemRead";
export * from "./limitOffsetPageTypeVarCustomizedAgentRead";
@@ -146,6 +147,7 @@ export * from "./listBoardWebhookPayloadsApiV1BoardsBoardIdWebhooksWebhookIdPayl
export * from "./listBoardWebhooksApiV1BoardsBoardIdWebhooksGetParams";
export * from "./listGatewaysApiV1GatewaysGetParams";
export * from "./listGatewaySessionsApiV1GatewaysSessionsGetParams";
export * from "./listMarketplaceSkillsApiV1SkillsMarketplaceGetParams";
export * from "./listOrgInvitesApiV1OrganizationsMeInvitesGetParams";
export * from "./listOrgMembersApiV1OrganizationsMeMembersGetParams";
export * from "./listTagsApiV1TagsGetParams";
@@ -154,6 +156,10 @@ export * from "./listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetPa
export * from "./listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams";
export * from "./listTasksApiV1AgentBoardsBoardIdTasksGetParams";
export * from "./listTasksApiV1BoardsBoardIdTasksGetParams";
export * from "./marketplaceSkillActionResponse";
export * from "./marketplaceSkillCardRead";
export * from "./marketplaceSkillCreate";
export * from "./marketplaceSkillRead";
export * from "./okResponse";
export * from "./organizationActiveUpdate";
export * from "./organizationBoardAccessRead";
@@ -207,6 +213,7 @@ export * from "./taskReadCustomFieldValues";
export * from "./taskReadStatus";
export * from "./taskUpdate";
export * from "./taskUpdateCustomFieldValues";
export * from "./uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams";
export * from "./updateAgentApiV1AgentsAgentIdPatchParams";
export * from "./userRead";
export * from "./userUpdate";

View File

@@ -0,0 +1,11 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams =
{
gateway_id: string;
};

View File

@@ -0,0 +1,10 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams = {
gateway_id: string;
};

View File

@@ -0,0 +1,16 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
/**
* Install/uninstall action response payload.
*/
export interface MarketplaceSkillActionResponse {
gateway_id: string;
installed: boolean;
ok?: boolean;
skill_id: string;
}

View File

@@ -0,0 +1,21 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
/**
* Marketplace card payload with gateway-specific install state.
*/
export interface MarketplaceSkillCardRead {
created_at: string;
description?: string | null;
id: string;
installed: boolean;
installed_at?: string | null;
name: string;
organization_id: string;
source_url: string;
updated_at: string;
}

View File

@@ -0,0 +1,16 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
/**
* Payload used to register a skill URL in the organization marketplace.
*/
export interface MarketplaceSkillCreate {
description?: string | null;
name?: string | null;
/** @minLength 1 */
source_url: string;
}

View File

@@ -0,0 +1,19 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
/**
* Serialized marketplace skill catalog record.
*/
export interface MarketplaceSkillRead {
created_at: string;
description?: string | null;
id: string;
name: string;
organization_id: string;
source_url: string;
updated_at: string;
}

View File

@@ -0,0 +1,11 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams =
{
gateway_id: string;
};

View File

@@ -0,0 +1,939 @@
/**
* Generated by orval v8.3.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import { useMutation, useQuery } from "@tanstack/react-query";
import type {
DataTag,
DefinedInitialDataOptions,
DefinedUseQueryResult,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UndefinedInitialDataOptions,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from "@tanstack/react-query";
import type {
HTTPValidationError,
InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams,
ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
MarketplaceSkillActionResponse,
MarketplaceSkillCardRead,
MarketplaceSkillCreate,
MarketplaceSkillRead,
OkResponse,
UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams,
} from ".././model";
import { customFetch } from "../../mutator";
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
/**
* List marketplace cards for an org and annotate install state for a gateway.
* @summary List Marketplace Skills
*/
export type listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse200 = {
data: MarketplaceSkillCardRead[];
status: 200;
};
export type listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse422 = {
data: HTTPValidationError;
status: 422;
};
export type listMarketplaceSkillsApiV1SkillsMarketplaceGetResponseSuccess =
listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse200 & {
headers: Headers;
};
export type listMarketplaceSkillsApiV1SkillsMarketplaceGetResponseError =
listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse422 & {
headers: Headers;
};
export type listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse =
| listMarketplaceSkillsApiV1SkillsMarketplaceGetResponseSuccess
| listMarketplaceSkillsApiV1SkillsMarketplaceGetResponseError;
export const getListMarketplaceSkillsApiV1SkillsMarketplaceGetUrl = (
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? "null" : value.toString());
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/skills/marketplace?${stringifiedParams}`
: `/api/v1/skills/marketplace`;
};
export const listMarketplaceSkillsApiV1SkillsMarketplaceGet = async (
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
options?: RequestInit,
): Promise<listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse> => {
return customFetch<listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse>(
getListMarketplaceSkillsApiV1SkillsMarketplaceGetUrl(params),
{
...options,
method: "GET",
},
);
};
export const getListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryKey = (
params?: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
) => {
return [`/api/v1/skills/marketplace`, ...(params ? [params] : [])] as const;
};
export const getListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryOptions = <
TData = Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError = HTTPValidationError,
>(
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
TData
>
>;
request?: SecondParameter<typeof customFetch>;
},
) => {
const { query: queryOptions, request: requestOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ??
getListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>>
> = ({ signal }) =>
listMarketplaceSkillsApiV1SkillsMarketplaceGet(params, {
signal,
...requestOptions,
});
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>>,
TError,
TData
> & { queryKey: DataTag<QueryKey, TData, TError> };
};
export type ListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryResult =
NonNullable<
Awaited<ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>>
>;
export type ListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryError =
HTTPValidationError;
export function useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
TData = Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError = HTTPValidationError,
>(
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
options: {
query: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
TData
>
> &
Pick<
DefinedInitialDataOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>
>,
"initialData"
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): DefinedUseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
};
export function useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
TData = Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError = HTTPValidationError,
>(
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
TData
>
> &
Pick<
UndefinedInitialDataOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>
>,
"initialData"
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
};
export function useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
TData = Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError = HTTPValidationError,
>(
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
TData
>
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
};
/**
* @summary List Marketplace Skills
*/
export function useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
TData = Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError = HTTPValidationError,
>(
params: ListMarketplaceSkillsApiV1SkillsMarketplaceGetParams,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof listMarketplaceSkillsApiV1SkillsMarketplaceGet>
>,
TError,
TData
>
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
} {
const queryOptions =
getListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryOptions(
params,
options,
);
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
TData,
TError
> & { queryKey: DataTag<QueryKey, TData, TError> };
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* Register a skill source URL in the organization's marketplace catalog.
* @summary Create Marketplace Skill
*/
export type createMarketplaceSkillApiV1SkillsMarketplacePostResponse200 = {
data: MarketplaceSkillRead;
status: 200;
};
export type createMarketplaceSkillApiV1SkillsMarketplacePostResponse422 = {
data: HTTPValidationError;
status: 422;
};
export type createMarketplaceSkillApiV1SkillsMarketplacePostResponseSuccess =
createMarketplaceSkillApiV1SkillsMarketplacePostResponse200 & {
headers: Headers;
};
export type createMarketplaceSkillApiV1SkillsMarketplacePostResponseError =
createMarketplaceSkillApiV1SkillsMarketplacePostResponse422 & {
headers: Headers;
};
export type createMarketplaceSkillApiV1SkillsMarketplacePostResponse =
| createMarketplaceSkillApiV1SkillsMarketplacePostResponseSuccess
| createMarketplaceSkillApiV1SkillsMarketplacePostResponseError;
export const getCreateMarketplaceSkillApiV1SkillsMarketplacePostUrl = () => {
return `/api/v1/skills/marketplace`;
};
export const createMarketplaceSkillApiV1SkillsMarketplacePost = async (
marketplaceSkillCreate: MarketplaceSkillCreate,
options?: RequestInit,
): Promise<createMarketplaceSkillApiV1SkillsMarketplacePostResponse> => {
return customFetch<createMarketplaceSkillApiV1SkillsMarketplacePostResponse>(
getCreateMarketplaceSkillApiV1SkillsMarketplacePostUrl(),
{
...options,
method: "POST",
headers: { "Content-Type": "application/json", ...options?.headers },
body: JSON.stringify(marketplaceSkillCreate),
},
);
};
export const getCreateMarketplaceSkillApiV1SkillsMarketplacePostMutationOptions =
<TError = HTTPValidationError, TContext = unknown>(options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<typeof createMarketplaceSkillApiV1SkillsMarketplacePost>
>,
TError,
{ data: MarketplaceSkillCreate },
TContext
>;
request?: SecondParameter<typeof customFetch>;
}): UseMutationOptions<
Awaited<
ReturnType<typeof createMarketplaceSkillApiV1SkillsMarketplacePost>
>,
TError,
{ data: MarketplaceSkillCreate },
TContext
> => {
const mutationKey = ["createMarketplaceSkillApiV1SkillsMarketplacePost"];
const { mutation: mutationOptions, request: requestOptions } = options
? options.mutation &&
"mutationKey" in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey }, request: undefined };
const mutationFn: MutationFunction<
Awaited<
ReturnType<typeof createMarketplaceSkillApiV1SkillsMarketplacePost>
>,
{ data: MarketplaceSkillCreate }
> = (props) => {
const { data } = props ?? {};
return createMarketplaceSkillApiV1SkillsMarketplacePost(
data,
requestOptions,
);
};
return { mutationFn, ...mutationOptions };
};
export type CreateMarketplaceSkillApiV1SkillsMarketplacePostMutationResult =
NonNullable<
Awaited<ReturnType<typeof createMarketplaceSkillApiV1SkillsMarketplacePost>>
>;
export type CreateMarketplaceSkillApiV1SkillsMarketplacePostMutationBody =
MarketplaceSkillCreate;
export type CreateMarketplaceSkillApiV1SkillsMarketplacePostMutationError =
HTTPValidationError;
/**
* @summary Create Marketplace Skill
*/
export const useCreateMarketplaceSkillApiV1SkillsMarketplacePost = <
TError = HTTPValidationError,
TContext = unknown,
>(
options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<typeof createMarketplaceSkillApiV1SkillsMarketplacePost>
>,
TError,
{ data: MarketplaceSkillCreate },
TContext
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseMutationResult<
Awaited<ReturnType<typeof createMarketplaceSkillApiV1SkillsMarketplacePost>>,
TError,
{ data: MarketplaceSkillCreate },
TContext
> => {
return useMutation(
getCreateMarketplaceSkillApiV1SkillsMarketplacePostMutationOptions(options),
queryClient,
);
};
/**
* Delete a marketplace catalog entry and any install records that reference it.
* @summary Delete Marketplace Skill
*/
export type deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse200 =
{
data: OkResponse;
status: 200;
};
export type deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse422 =
{
data: HTTPValidationError;
status: 422;
};
export type deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponseSuccess =
deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse200 & {
headers: Headers;
};
export type deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponseError =
deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse422 & {
headers: Headers;
};
export type deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse =
| deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponseSuccess
| deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponseError;
export const getDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteUrl = (
skillId: string,
) => {
return `/api/v1/skills/marketplace/${skillId}`;
};
export const deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete = async (
skillId: string,
options?: RequestInit,
): Promise<deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse> => {
return customFetch<deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteResponse>(
getDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteUrl(skillId),
{
...options,
method: "DELETE",
},
);
};
export const getDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteMutationOptions =
<TError = HTTPValidationError, TContext = unknown>(options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<
typeof deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete
>
>,
TError,
{ skillId: string },
TContext
>;
request?: SecondParameter<typeof customFetch>;
}): UseMutationOptions<
Awaited<
ReturnType<
typeof deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete
>
>,
TError,
{ skillId: string },
TContext
> => {
const mutationKey = [
"deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete",
];
const { mutation: mutationOptions, request: requestOptions } = options
? options.mutation &&
"mutationKey" in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey }, request: undefined };
const mutationFn: MutationFunction<
Awaited<
ReturnType<
typeof deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete
>
>,
{ skillId: string }
> = (props) => {
const { skillId } = props ?? {};
return deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete(
skillId,
requestOptions,
);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteMutationResult =
NonNullable<
Awaited<
ReturnType<
typeof deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete
>
>
>;
export type DeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteMutationError =
HTTPValidationError;
/**
* @summary Delete Marketplace Skill
*/
export const useDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete = <
TError = HTTPValidationError,
TContext = unknown,
>(
options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<
typeof deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete
>
>,
TError,
{ skillId: string },
TContext
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseMutationResult<
Awaited<
ReturnType<typeof deleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete>
>,
TError,
{ skillId: string },
TContext
> => {
return useMutation(
getDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDeleteMutationOptions(
options,
),
queryClient,
);
};
/**
* Install a marketplace skill by dispatching instructions to the gateway agent.
* @summary Install Marketplace Skill
*/
export type installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse200 =
{
data: MarketplaceSkillActionResponse;
status: 200;
};
export type installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse422 =
{
data: HTTPValidationError;
status: 422;
};
export type installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponseSuccess =
installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse200 & {
headers: Headers;
};
export type installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponseError =
installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse422 & {
headers: Headers;
};
export type installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse =
| installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponseSuccess
| installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponseError;
export const getInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostUrl =
(
skillId: string,
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(
key,
value === null ? "null" : value.toString(),
);
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/skills/marketplace/${skillId}/install?${stringifiedParams}`
: `/api/v1/skills/marketplace/${skillId}/install`;
};
export const installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost =
async (
skillId: string,
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams,
options?: RequestInit,
): Promise<installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse> => {
return customFetch<installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostResponse>(
getInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostUrl(
skillId,
params,
),
{
...options,
method: "POST",
},
);
};
export const getInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostMutationOptions =
<TError = HTTPValidationError, TContext = unknown>(options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<
typeof installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost
>
>,
TError,
{
skillId: string;
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams;
},
TContext
>;
request?: SecondParameter<typeof customFetch>;
}): UseMutationOptions<
Awaited<
ReturnType<
typeof installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost
>
>,
TError,
{
skillId: string;
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams;
},
TContext
> => {
const mutationKey = [
"installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost",
];
const { mutation: mutationOptions, request: requestOptions } = options
? options.mutation &&
"mutationKey" in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey }, request: undefined };
const mutationFn: MutationFunction<
Awaited<
ReturnType<
typeof installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost
>
>,
{
skillId: string;
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams;
}
> = (props) => {
const { skillId, params } = props ?? {};
return installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost(
skillId,
params,
requestOptions,
);
};
return { mutationFn, ...mutationOptions };
};
export type InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostMutationResult =
NonNullable<
Awaited<
ReturnType<
typeof installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost
>
>
>;
export type InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostMutationError =
HTTPValidationError;
/**
* @summary Install Marketplace Skill
*/
export const useInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost =
<TError = HTTPValidationError, TContext = unknown>(
options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<
typeof installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost
>
>,
TError,
{
skillId: string;
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams;
},
TContext
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseMutationResult<
Awaited<
ReturnType<
typeof installMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost
>
>,
TError,
{
skillId: string;
params: InstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostParams;
},
TContext
> => {
return useMutation(
getInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPostMutationOptions(
options,
),
queryClient,
);
};
/**
* Uninstall a marketplace skill by dispatching instructions to the gateway agent.
* @summary Uninstall Marketplace Skill
*/
export type uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse200 =
{
data: MarketplaceSkillActionResponse;
status: 200;
};
export type uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse422 =
{
data: HTTPValidationError;
status: 422;
};
export type uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponseSuccess =
uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse200 & {
headers: Headers;
};
export type uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponseError =
uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse422 & {
headers: Headers;
};
export type uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse =
| uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponseSuccess
| uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponseError;
export const getUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostUrl =
(
skillId: string,
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(
key,
value === null ? "null" : value.toString(),
);
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/skills/marketplace/${skillId}/uninstall?${stringifiedParams}`
: `/api/v1/skills/marketplace/${skillId}/uninstall`;
};
export const uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost =
async (
skillId: string,
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams,
options?: RequestInit,
): Promise<uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse> => {
return customFetch<uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostResponse>(
getUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostUrl(
skillId,
params,
),
{
...options,
method: "POST",
},
);
};
export const getUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostMutationOptions =
<TError = HTTPValidationError, TContext = unknown>(options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<
typeof uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost
>
>,
TError,
{
skillId: string;
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams;
},
TContext
>;
request?: SecondParameter<typeof customFetch>;
}): UseMutationOptions<
Awaited<
ReturnType<
typeof uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost
>
>,
TError,
{
skillId: string;
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams;
},
TContext
> => {
const mutationKey = [
"uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost",
];
const { mutation: mutationOptions, request: requestOptions } = options
? options.mutation &&
"mutationKey" in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey }, request: undefined };
const mutationFn: MutationFunction<
Awaited<
ReturnType<
typeof uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost
>
>,
{
skillId: string;
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams;
}
> = (props) => {
const { skillId, params } = props ?? {};
return uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost(
skillId,
params,
requestOptions,
);
};
return { mutationFn, ...mutationOptions };
};
export type UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostMutationResult =
NonNullable<
Awaited<
ReturnType<
typeof uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost
>
>
>;
export type UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostMutationError =
HTTPValidationError;
/**
* @summary Uninstall Marketplace Skill
*/
export const useUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost =
<TError = HTTPValidationError, TContext = unknown>(
options?: {
mutation?: UseMutationOptions<
Awaited<
ReturnType<
typeof uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost
>
>,
TError,
{
skillId: string;
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams;
},
TContext
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseMutationResult<
Awaited<
ReturnType<
typeof uninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost
>
>,
TError,
{
skillId: string;
params: UninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostParams;
},
TContext
> => {
return useMutation(
getUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPostMutationOptions(
options,
),
queryClient,
);
};

View File

@@ -0,0 +1,412 @@
"use client";
export const dynamic = "force-dynamic";
import Link from "next/link";
import { FormEvent, useMemo, useState } from "react";
import { useAuth } from "@/auth/clerk";
import { useQueryClient } from "@tanstack/react-query";
import { ExternalLink, Package, PlusCircle, Trash2 } from "lucide-react";
import { ApiError } from "@/api/mutator";
import {
type listGatewaysApiV1GatewaysGetResponse,
useListGatewaysApiV1GatewaysGet,
} from "@/api/generated/gateways/gateways";
import type { MarketplaceSkillCardRead } from "@/api/generated/model";
import {
getListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryKey,
type listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse,
useCreateMarketplaceSkillApiV1SkillsMarketplacePost,
useDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete,
useInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost,
useListMarketplaceSkillsApiV1SkillsMarketplaceGet,
useUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost,
} from "@/api/generated/skills-marketplace/skills-marketplace";
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
import { Badge } from "@/components/ui/badge";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { formatRelativeTimestamp } from "@/lib/formatters";
import { useOrganizationMembership } from "@/lib/use-organization-membership";
export default function SkillsMarketplacePage() {
const queryClient = useQueryClient();
const { isSignedIn } = useAuth();
const { isAdmin } = useOrganizationMembership(isSignedIn);
const [selectedGatewayId, setSelectedGatewayId] = useState("");
const [sourceUrl, setSourceUrl] = useState("");
const [skillName, setSkillName] = useState("");
const [description, setDescription] = useState("");
const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
listGatewaysApiV1GatewaysGetResponse,
ApiError
>(undefined, {
query: {
enabled: Boolean(isSignedIn && isAdmin),
refetchOnMount: "always",
refetchInterval: 30_000,
},
});
const gateways = useMemo(
() =>
gatewaysQuery.data?.status === 200
? (gatewaysQuery.data.data.items ?? [])
: [],
[gatewaysQuery.data],
);
const resolvedGatewayId = useMemo(() => {
if (selectedGatewayId && gateways.some((gateway) => gateway.id === selectedGatewayId)) {
return selectedGatewayId;
}
return gateways[0]?.id ?? "";
}, [gateways, selectedGatewayId]);
const skillsQueryKey = getListMarketplaceSkillsApiV1SkillsMarketplaceGetQueryKey(
resolvedGatewayId ? { gateway_id: resolvedGatewayId } : undefined,
);
const skillsQuery = useListMarketplaceSkillsApiV1SkillsMarketplaceGet<
listMarketplaceSkillsApiV1SkillsMarketplaceGetResponse,
ApiError
>(
{ gateway_id: resolvedGatewayId },
{
query: {
enabled: Boolean(isSignedIn && isAdmin && resolvedGatewayId),
refetchOnMount: "always",
refetchInterval: 15_000,
},
},
);
const skills = useMemo<MarketplaceSkillCardRead[]>(
() => (skillsQuery.data?.status === 200 ? skillsQuery.data.data : []),
[skillsQuery.data],
);
const createMutation =
useCreateMarketplaceSkillApiV1SkillsMarketplacePost<ApiError>(
{
mutation: {
onSuccess: async () => {
setSourceUrl("");
setSkillName("");
setDescription("");
await queryClient.invalidateQueries({
queryKey: skillsQueryKey,
});
},
},
},
queryClient,
);
const installMutation =
useInstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdInstallPost<ApiError>(
{
mutation: {
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: skillsQueryKey,
});
},
},
},
queryClient,
);
const uninstallMutation =
useUninstallMarketplaceSkillApiV1SkillsMarketplaceSkillIdUninstallPost<ApiError>(
{
mutation: {
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: skillsQueryKey,
});
},
},
},
queryClient,
);
const deleteMutation =
useDeleteMarketplaceSkillApiV1SkillsMarketplaceSkillIdDelete<ApiError>(
{
mutation: {
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: skillsQueryKey,
});
},
},
},
queryClient,
);
const mutationError =
createMutation.error?.message ??
installMutation.error?.message ??
uninstallMutation.error?.message ??
deleteMutation.error?.message;
const handleAddSkill = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const normalizedUrl = sourceUrl.trim();
if (!normalizedUrl) return;
createMutation.mutate({
data: {
source_url: normalizedUrl,
name: skillName.trim() || undefined,
description: description.trim() || undefined,
},
});
};
const isMutating =
createMutation.isPending ||
installMutation.isPending ||
uninstallMutation.isPending ||
deleteMutation.isPending;
return (
<DashboardPageLayout
signedOut={{
message: "Sign in to manage marketplace skills.",
forceRedirectUrl: "/skills",
}}
title="Skills Marketplace"
description="Register skill links and install or uninstall them per gateway."
isAdmin={isAdmin}
adminOnlyMessage="Only organization owners and admins can manage skills."
stickyHeader
>
<div className="space-y-6">
{gateways.length === 0 ? (
<div className="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-600 shadow-sm">
<p className="font-medium text-slate-900">No gateways available yet.</p>
<p className="mt-2">
Create a gateway first, then return here to install skills.
</p>
<Link
href="/gateways/new"
className={`${buttonVariants({ variant: "primary", size: "md" })} mt-4`}
>
Create gateway
</Link>
</div>
) : (
<Card>
<CardHeader className="border-b border-[color:var(--border)] pb-4">
<h2 className="font-heading text-lg font-semibold text-slate-900">
Add skill source
</h2>
<p className="text-sm text-slate-500">
Add a URL once, then install or uninstall the skill for the selected gateway.
</p>
</CardHeader>
<CardContent className="pt-5">
<form className="space-y-4" onSubmit={handleAddSkill}>
<div className="grid gap-4 md:grid-cols-[260px_1fr]">
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wide text-slate-500">
Gateway
</label>
<Select value={resolvedGatewayId} onValueChange={setSelectedGatewayId}>
<SelectTrigger>
<SelectValue placeholder="Select gateway" />
</SelectTrigger>
<SelectContent>
{gateways.map((gateway) => (
<SelectItem key={gateway.id} value={gateway.id}>
{gateway.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label
htmlFor="skill-url"
className="text-xs font-semibold uppercase tracking-wide text-slate-500"
>
Skill URL
</label>
<Input
id="skill-url"
type="url"
value={sourceUrl}
onChange={(event) => setSourceUrl(event.target.value)}
placeholder="https://github.com/org/skill-repo"
required
/>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<label
htmlFor="skill-name"
className="text-xs font-semibold uppercase tracking-wide text-slate-500"
>
Display name (optional)
</label>
<Input
id="skill-name"
value={skillName}
onChange={(event) => setSkillName(event.target.value)}
placeholder="Deploy Helper"
/>
</div>
<div className="space-y-2">
<label
htmlFor="skill-description"
className="text-xs font-semibold uppercase tracking-wide text-slate-500"
>
Description (optional)
</label>
<Textarea
id="skill-description"
value={description}
onChange={(event) => setDescription(event.target.value)}
placeholder="Short summary shown on the marketplace card."
className="min-h-[44px] py-3"
/>
</div>
</div>
<div className="flex items-center gap-3">
<Button
type="submit"
disabled={createMutation.isPending || !resolvedGatewayId}
>
<PlusCircle className="h-4 w-4" />
{createMutation.isPending ? "Adding…" : "Add skill"}
</Button>
{createMutation.error ? (
<p className="text-sm text-rose-600">
{createMutation.error.message}
</p>
) : null}
</div>
</form>
</CardContent>
</Card>
)}
{mutationError ? <p className="text-sm text-rose-600">{mutationError}</p> : null}
<div className="space-y-3">
<h2 className="font-heading text-lg font-semibold text-slate-900">
Marketplace skills
</h2>
{skillsQuery.isLoading ? (
<div className="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-500 shadow-sm">
Loading skills
</div>
) : skillsQuery.error ? (
<div className="rounded-xl border border-rose-200 bg-rose-50 p-6 text-sm text-rose-700">
{skillsQuery.error.message}
</div>
) : skills.length === 0 ? (
<div className="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-600 shadow-sm">
No skill links added yet.
</div>
) : (
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{skills.map((skill) => (
<Card key={skill.id}>
<CardHeader className="border-b border-[color:var(--border)] pb-4">
<div className="flex items-start justify-between gap-3">
<div className="space-y-1">
<h3 className="text-base font-semibold text-slate-900">
{skill.name}
</h3>
<p className="text-sm text-slate-500">
{skill.description || "No description provided."}
</p>
</div>
<Badge variant={skill.installed ? "success" : "outline"}>
{skill.installed ? "Installed" : "Not installed"}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4 pt-5">
<a
href={skill.source_url}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 text-sm font-medium text-[color:var(--accent)] hover:underline"
>
<ExternalLink className="h-4 w-4" />
Open source link
</a>
<p className="text-xs text-slate-500">
{skill.installed && skill.installed_at
? `Installed ${formatRelativeTimestamp(skill.installed_at)}`
: "Not installed on selected gateway"}
</p>
<div className="flex items-center gap-2">
{skill.installed ? (
<Button
type="button"
variant="outline"
onClick={() =>
uninstallMutation.mutate({
skillId: skill.id,
params: { gateway_id: resolvedGatewayId },
})
}
disabled={isMutating || !resolvedGatewayId}
>
<Package className="h-4 w-4" />
Uninstall
</Button>
) : (
<Button
type="button"
onClick={() =>
installMutation.mutate({
skillId: skill.id,
params: { gateway_id: resolvedGatewayId },
})
}
disabled={isMutating || !resolvedGatewayId}
>
<Package className="h-4 w-4" />
Install
</Button>
)}
<Button
type="button"
variant="ghost"
onClick={() => deleteMutation.mutate({ skillId: skill.id })}
disabled={isMutating}
aria-label={`Delete ${skill.name}`}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
</div>
</DashboardPageLayout>
);
}

View File

@@ -11,6 +11,7 @@ import {
Building2,
LayoutGrid,
Network,
Package,
Settings,
Tags,
} from "lucide-react";
@@ -195,6 +196,20 @@ export function DashboardSidebar() {
Gateways
</Link>
) : null}
{isAdmin ? (
<Link
href="/skills"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/skills")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100",
)}
>
<Package className="h-4 w-4" />
Skills
</Link>
) : null}
{isAdmin ? (
<Link
href="/agents"

View File

@@ -11,6 +11,7 @@ import {
ChevronDown,
LayoutDashboard,
LogOut,
Package,
Plus,
Server,
Settings,
@@ -155,6 +156,7 @@ export function UserMenu({
{ href: "/activity", label: "Activity", icon: Activity },
{ href: "/agents", label: "Agents", icon: Bot },
{ href: "/gateways", label: "Gateways", icon: Server },
{ href: "/skills", label: "Skills", icon: Package },
{ href: "/settings", label: "Settings", icon: Settings },
] as const
).map((item) => (