diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx
index d33023b6..e6f3f270 100644
--- a/ui/src/pages/CompanySkills.tsx
+++ b/ui/src/pages/CompanySkills.tsx
@@ -7,6 +7,7 @@ import type {
CompanySkillFileDetail,
CompanySkillFileInventoryEntry,
CompanySkillListItem,
+ CompanySkillProjectScanResult,
CompanySkillSourceBadge,
CompanySkillUpdateStatus,
} from "@paperclipai/shared";
@@ -167,6 +168,17 @@ function shortRef(ref: string | null | undefined) {
return ref.slice(0, 7);
}
+function formatProjectScanSummary(result: CompanySkillProjectScanResult) {
+ const parts = [
+ `${result.discovered} found`,
+ `${result.imported.length} imported`,
+ `${result.updated.length} updated`,
+ ];
+ if (result.conflicts.length > 0) parts.push(`${result.conflicts.length} conflicts`);
+ if (result.skipped.length > 0) parts.push(`${result.skipped.length} skipped`);
+ return `${parts.join(", ")} across ${result.scannedWorkspaces} workspace${result.scannedWorkspaces === 1 ? "" : "s"}.`;
+}
+
function fileIcon(kind: CompanySkillFileInventoryEntry["kind"]) {
if (kind === "script" || kind === "reference") return FileCode2;
return FileText;
@@ -542,11 +554,6 @@ function SkillPane({
- {detail.key.includes("/") && (
-
- {detail.key.split("/").slice(0, -1).join("/")}
-
- )}
{detail.name}
@@ -734,6 +741,7 @@ export function CompanySkills() {
const [draft, setDraft] = useState("");
const [displayedDetail, setDisplayedDetail] = useState(null);
const [displayedFile, setDisplayedFile] = useState(null);
+ const [scanStatusMessage, setScanStatusMessage] = useState(null);
const parsedRoute = useMemo(() => parseSkillRoute(routePath), [routePath]);
const routeSkillId = parsedRoute.skillId;
const selectedPath = parsedRoute.filePath;
@@ -876,6 +884,45 @@ export function CompanySkills() {
},
});
+ const scanProjects = useMutation({
+ mutationFn: () => companySkillsApi.scanProjects(selectedCompanyId!),
+ onMutate: () => {
+ setScanStatusMessage("Scanning project workspaces for skills...");
+ },
+ onSuccess: async (result) => {
+ setScanStatusMessage("Refreshing skills list...");
+ await queryClient.invalidateQueries({ queryKey: queryKeys.companySkills.list(selectedCompanyId!) });
+ const summary = formatProjectScanSummary(result);
+ setScanStatusMessage(summary);
+ pushToast({
+ tone: "success",
+ title: "Project skill scan complete",
+ body: summary,
+ });
+ if (result.conflicts[0]) {
+ pushToast({
+ tone: "warn",
+ title: "Skill conflicts found",
+ body: result.conflicts[0].reason,
+ });
+ } else if (result.warnings[0]) {
+ pushToast({
+ tone: "warn",
+ title: "Scan warnings",
+ body: result.warnings[0],
+ });
+ }
+ },
+ onError: (error) => {
+ setScanStatusMessage(null);
+ pushToast({
+ tone: "error",
+ title: "Project skill scan failed",
+ body: error instanceof Error ? error.message : "Failed to scan project workspaces.",
+ });
+ },
+ });
+
const saveFile = useMutation({
mutationFn: () => companySkillsApi.updateFile(
selectedCompanyId!,
@@ -1002,10 +1049,11 @@ export function CompanySkills() {
+ {scanStatusMessage && (
+
+ {scanStatusMessage}
+
+ )}
{createOpen && (