- Implemented functionality to install skills from GitHub URLs. - Updated API to handle new installation requests from GitHub. - Enhanced UI to allow users to input GitHub skill URLs for installation. - Added translations for new GitHub installation features in English, Thai, and Chinese. - Created tests for the new skill installation service and API routes to ensure proper functionality.
180 lines
5.5 KiB
TypeScript
180 lines
5.5 KiB
TypeScript
import type { HostApiResult } from '@src/types/runtime';
|
|
import type { HostApiContext } from '../context';
|
|
import type { NormalizedHostApiRequest } from '../route-utils';
|
|
import { fail, ok, parseJsonBody } from '../route-utils';
|
|
import { getAllSkillConfigs, updateSkillConfig } from '../../utils/skill-config';
|
|
import { shell } from 'electron';
|
|
import { getOpenClawConfigDir } from '../../utils/paths';
|
|
import {
|
|
SkillInstallService,
|
|
SkillInstallServiceError,
|
|
type SkillInstallRequest,
|
|
} from '@service/skill-install-service';
|
|
|
|
export async function handleSkillRoutes(
|
|
request: NormalizedHostApiRequest,
|
|
ctx: HostApiContext,
|
|
): Promise<HostApiResult<unknown> | null> {
|
|
const { pathname, method } = request;
|
|
const installService = new SkillInstallService({ clawHubService: ctx.clawHubService });
|
|
|
|
if (pathname === '/api/skills/configs' && method === 'GET') {
|
|
try {
|
|
return ok(await getAllSkillConfigs());
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/skills/config' && method === 'PUT') {
|
|
try {
|
|
const body = parseJsonBody<{
|
|
skillKey: string;
|
|
apiKey?: string;
|
|
env?: Record<string, string>;
|
|
enabled?: boolean;
|
|
}>(request.body);
|
|
if (!body?.skillKey || !String(body.skillKey).trim()) {
|
|
return fail(400, 'skillKey is required');
|
|
}
|
|
const result = await updateSkillConfig(body.skillKey, {
|
|
apiKey: body.apiKey,
|
|
env: body.env,
|
|
enabled: body.enabled,
|
|
});
|
|
return ok(result);
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/capability' && method === 'GET') {
|
|
try {
|
|
return ok({
|
|
success: true,
|
|
capability: await ctx.clawHubService.getMarketplaceCapability(),
|
|
});
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/search' && method === 'POST') {
|
|
try {
|
|
const body = parseJsonBody<Record<string, unknown>>(request.body);
|
|
return ok({
|
|
success: true,
|
|
results: await ctx.clawHubService.search(body as { query: string; limit?: number }),
|
|
});
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/skills/install' && method === 'POST') {
|
|
try {
|
|
const body = parseJsonBody<SkillInstallRequest>(request.body);
|
|
return ok(await installService.install(body));
|
|
} catch (error) {
|
|
return failInstall(error);
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/install' && method === 'POST') {
|
|
try {
|
|
const body = parseJsonBody<{ slug: string; version?: string; force?: boolean }>(request.body);
|
|
return ok(await installService.install({
|
|
kind: 'marketplace',
|
|
slug: body.slug,
|
|
version: body.version,
|
|
force: body.force,
|
|
}));
|
|
} catch (error) {
|
|
return failInstall(error);
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/uninstall' && method === 'POST') {
|
|
try {
|
|
const body = parseJsonBody<Record<string, unknown>>(request.body);
|
|
await ctx.clawHubService.uninstall(body as { slug: string });
|
|
return ok({ success: true });
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/list' && method === 'GET') {
|
|
try {
|
|
return ok({
|
|
success: true,
|
|
results: await ctx.clawHubService.listInstalled(),
|
|
});
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/open-readme' && method === 'POST') {
|
|
try {
|
|
const body = parseJsonBody<{ slug?: string; skillKey?: string; baseDir?: string }>(request.body);
|
|
await ctx.clawHubService.openSkillReadme(
|
|
body.skillKey || body.slug || '',
|
|
body.slug,
|
|
body.baseDir,
|
|
);
|
|
return ok({ success: true });
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/open-path' && method === 'POST') {
|
|
try {
|
|
const body = parseJsonBody<{ slug?: string; skillKey?: string; baseDir?: string }>(request.body);
|
|
await ctx.clawHubService.openSkillPath(
|
|
body.skillKey || body.slug || '',
|
|
body.slug,
|
|
body.baseDir,
|
|
);
|
|
return ok({ success: true });
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/skills-dir' && method === 'GET') {
|
|
try {
|
|
return ok({
|
|
success: true,
|
|
path: `${getOpenClawConfigDir()}/skills`,
|
|
});
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
if (pathname === '/api/clawhub/open-skills-dir' && method === 'POST') {
|
|
try {
|
|
const skillsDir = `${getOpenClawConfigDir()}/skills`;
|
|
const openResult = await shell.openPath(skillsDir);
|
|
if (openResult) {
|
|
return fail(500, openResult);
|
|
}
|
|
return ok({ success: true });
|
|
} catch (error) {
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function failInstall(error: unknown): HostApiResult<unknown> {
|
|
if (error instanceof SkillInstallServiceError) {
|
|
return fail(error.status, error.message);
|
|
}
|
|
|
|
return fail(500, error instanceof Error ? error.message : String(error));
|
|
}
|