Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/server/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ const buildCmd = Command.make(
})`bun tsdown`,
);

// Copy bundled skill catalog assets so import.meta.url resolution works at runtime.
const sharedSkillsCatalog = path.join(repoRoot, "packages/shared/src/skills-catalog");
const distSkillsCatalog = path.join(serverDir, "dist/skills-catalog");
if (yield* fs.exists(sharedSkillsCatalog)) {
yield* fs.copy(sharedSkillsCatalog, distSkillsCatalog);
yield* Effect.log("[cli] Copied skills-catalog into dist/skills-catalog");
} else {
yield* Effect.logWarning("[cli] skills-catalog not found — skipping.");
}

const webDist = path.join(repoRoot, "apps/web/dist");
const clientTarget = path.join(serverDir, "dist/client");

Expand Down
12 changes: 11 additions & 1 deletion apps/server/src/skills/SkillService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,17 @@ function toSkillEntry(entry: ReturnType<typeof listSkills>[number]) {
}

const catalogEntries = listBundledSkills();
ensureSystemSkillsInstalled();

// Deferred initialization — avoid crashing the module if bundled skill assets
// are missing (e.g. the skills-catalog directory wasn't copied into dist/).
try {
ensureSystemSkillsInstalled();
} catch (error) {
console.warn(
"[SkillService] Failed to bootstrap system skills — bundled skill assets may be missing:",
error instanceof Error ? error.message : String(error),
);
}

export const SkillServiceLive = Layer.succeed(SkillService, {
catalog: (input) =>
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/components/skills/SkillsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useEffect, useMemo, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import {
AlertTriangleIcon,
BookOpenIcon,
CheckIcon,
FileSpreadsheetIcon,
Expand All @@ -18,6 +19,7 @@ import {
PlayIcon,
PlusIcon,
PlugIcon,
RefreshCwIcon,
SearchIcon,
SparklesIcon,
} from "lucide-react";
Expand Down Expand Up @@ -416,6 +418,9 @@ export function SkillsPage(props: {
return (installedSkillsQuery.data?.skills ?? []).filter((skill) => !skill.system);
}, [installedSkillsQuery.data?.skills]);

const hasQueryError = catalogQuery.isError || installedSkillsQuery.isError;
const queryErrorMessage = catalogQuery.error?.message ?? installedSkillsQuery.error?.message;

const matchesSearch = (name: string, description: string, tags: readonly string[]) => {
const query = searchValue.trim().toLowerCase();
if (!query) return true;
Expand Down Expand Up @@ -501,6 +506,31 @@ export function SkillsPage(props: {
</div>
</div>

{hasQueryError ? (
<div className="flex items-start gap-3 rounded-2xl border border-destructive/30 bg-destructive/5 p-4">
<AlertTriangleIcon className="mt-0.5 size-5 shrink-0 text-destructive" />
<div className="min-w-0 flex-1">
<p className="font-medium text-sm text-foreground">
Unable to load skills
</p>
<p className="mt-1 text-sm text-muted-foreground">
{queryErrorMessage ?? "The skills service is unavailable. Check that the server is running."}
</p>
<Button
variant="outline"
size="sm"
className="mt-3"
onClick={() => {
void queryClient.invalidateQueries({ queryKey: skillQueryKeys.all });
}}
>
<RefreshCwIcon className="size-3.5" />
Retry
</Button>
</div>
</div>
) : null}

<section className="space-y-4">
<SectionHeader
title="Recommended"
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/lib/skillReactQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function skillCatalogQueryOptions(input: {
enabled: input.enabled !== false,
staleTime: input.staleTime ?? 30_000,
placeholderData: EMPTY_SKILL_CATALOG_RESULT,
retry: 2,
});
}

Expand All @@ -44,6 +45,7 @@ export function skillListQueryOptions(input: {
enabled: input.enabled !== false,
staleTime: input.staleTime ?? 30_000,
placeholderData: EMPTY_SKILL_LIST_RESULT,
retry: 2,
});
}

Expand Down
Loading