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
69 changes: 69 additions & 0 deletions data/workflows.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[
{
"id": "wf-oncall-triage",
"name": "On-call Alert Triage",
"description": "Ingest alerts, score severity, and assign to the correct owner.",
"enabled": true,
"createdAt": "2026-02-01T08:00:00.000Z",
"updatedAt": "2026-02-01T08:00:00.000Z",
"steps": [
{ "id": "step-1", "name": "Collect incoming alerts", "description": "Read PagerDuty and Slack alert feeds" },
{ "id": "step-2", "name": "Classify severity", "description": "Apply severity rules and confidence score" },
{ "id": "step-3", "name": "Route to service owner", "description": "Assign incident to the owning squad" },
{ "id": "step-4", "name": "Post timeline update", "description": "Publish initial status update in incident channel" }
]
},
{
"id": "wf-content-ops",
"name": "Content Ops Publishing",
"description": "Turn approved drafts into scheduled posts across channels.",
"enabled": true,
"createdAt": "2026-02-02T09:30:00.000Z",
"updatedAt": "2026-02-02T09:30:00.000Z",
"steps": [
{ "id": "step-1", "name": "Pull approved drafts", "description": "Fetch approved content from Notion" },
{ "id": "step-2", "name": "Generate social snippets", "description": "Create channel-specific excerpts" },
{ "id": "step-3", "name": "Schedule posts", "description": "Queue publication in social scheduler" }
]
},
{
"id": "wf-release-checklist",
"name": "Release Checklist Automation",
"description": "Automate pre-release quality checks and release notes generation.",
"enabled": false,
"createdAt": "2026-02-03T12:15:00.000Z",
"updatedAt": "2026-02-03T12:15:00.000Z",
"steps": [
{ "id": "step-1", "name": "Run CI test suite", "description": "Execute unit and integration tests" },
{ "id": "step-2", "name": "Summarize changes", "description": "Aggregate merged PR highlights" },
{ "id": "step-3", "name": "Draft release notes", "description": "Generate release notes markdown" },
{ "id": "step-4", "name": "Create release ticket", "description": "Open release ticket with checklist" },
{ "id": "step-5", "name": "Notify launch channel", "description": "Share release candidate status" }
]
},
{
"id": "wf-customer-escalation",
"name": "Customer Escalation Flow",
"description": "Detect high-risk tickets and escalate them to response leads.",
"enabled": true,
"createdAt": "2026-02-04T14:00:00.000Z",
"updatedAt": "2026-02-04T14:00:00.000Z",
"steps": [
{ "id": "step-1", "name": "Watch support inbox", "description": "Poll incoming support tickets" },
{ "id": "step-2", "name": "Identify risk signals", "description": "Flag churn and outage keywords" },
{ "id": "step-3", "name": "Escalate to response lead", "description": "Assign lead and start SLA timer" }
]
},
{
"id": "wf-data-quality",
"name": "Data Quality Guardrail",
"description": "Validate critical datasets before they are used for reporting.",
"enabled": false,
"createdAt": "2026-02-05T10:45:00.000Z",
"updatedAt": "2026-02-05T10:45:00.000Z",
"steps": [
{ "id": "step-1", "name": "Load daily extract", "description": "Read warehouse export" },
{ "id": "step-2", "name": "Run schema checks", "description": "Ensure required fields and types" }
]
}
]
2 changes: 1 addition & 1 deletion src/app/api/agents/[handle]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function GET(
return NextResponse.json(
{
agent: mergedAgent,
totalActivity: activity.total,
totalActivity: activity.items.length,
},
{
headers: {
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/bounties/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function validateTransitionBody(
return {
ok: true,
value: {
action,
action: action as BountyAction,
agentHandle,
...(notes ? { notes } : {}),
},
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/bounties/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function validateTransitionBody(
ok: true,
value: {
bountyId,
action,
action: action as BountyAction,
agentHandle,
...(notes ? { notes } : {}),
},
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/skills/[slug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function GET(
? reviews.reduce((sum, review) => sum + review.rating, 0) / reviewsCount
: 0;

const allVersions = versionsData as SkillVersionEntry[];
const allVersions = versionsData as unknown as SkillVersionEntry[];
const versions = allVersions.find((entry) => entry.slug === slug)?.versions ?? [];

const compatibility =
Expand Down
98 changes: 98 additions & 0 deletions src/app/api/workflows/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { NextRequest, NextResponse } from "next/server";

import { normalizeSteps, readWorkflows, writeWorkflows } from "@/lib/server/workflowStore";

export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const workflows = await readWorkflows();
const workflow = workflows.find((item) => item.id === id);

if (!workflow) {
return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
}

return NextResponse.json({ workflow });
}

export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;

try {
const body = await request.json() as {
name?: unknown;
description?: unknown;
steps?: unknown;
enabled?: unknown;
};

const workflows = await readWorkflows();
const index = workflows.findIndex((item) => item.id === id);

if (index === -1) {
return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
}

const current = workflows[index];
const now = new Date().toISOString();

const next = {
...current,
updatedAt: now,
};

if (typeof body.name === "string") {
const trimmed = body.name.trim();
if (!trimmed) {
return NextResponse.json({ error: "name cannot be empty" }, { status: 400 });
}
next.name = trimmed;
}

if (typeof body.description === "string") {
next.description = body.description.trim();
}

if (body.steps !== undefined) {
const steps = normalizeSteps(body.steps);
if (steps.length === 0) {
return NextResponse.json({ error: "steps must be a non-empty array" }, { status: 400 });
}
next.steps = steps;
}

if (typeof body.enabled === "boolean") {
next.enabled = body.enabled;
}

workflows[index] = next;
await writeWorkflows(workflows);

return NextResponse.json({ workflow: next });
} catch {
return NextResponse.json({ error: "Invalid request body. Expected JSON." }, { status: 400 });
}
}

export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;

const workflows = await readWorkflows();
const nextWorkflows = workflows.filter((item) => item.id !== id);

if (nextWorkflows.length === workflows.length) {
return NextResponse.json({ error: "Workflow not found" }, { status: 404 });
}

await writeWorkflows(nextWorkflows);

return new NextResponse(null, { status: 204 });
}
67 changes: 67 additions & 0 deletions src/app/api/workflows/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextRequest, NextResponse } from "next/server";

import {
normalizeSteps,
readWorkflows,
writeWorkflows,
type WorkflowRecord,
} from "@/lib/server/workflowStore";

export async function GET(request: NextRequest) {
const search = request.nextUrl.searchParams.get("search")?.trim().toLowerCase() || "";

const workflows = await readWorkflows();
const filtered = !search
? workflows
: workflows.filter((workflow) => {
return (
workflow.name.toLowerCase().includes(search)
|| workflow.description.toLowerCase().includes(search)
|| workflow.steps.some((step) => step.name.toLowerCase().includes(search))
);
});

return NextResponse.json({ workflows: filtered });
}

export async function POST(request: NextRequest) {
try {
const body = await request.json() as {
name?: unknown;
description?: unknown;
steps?: unknown;
};

const name = typeof body.name === "string" ? body.name.trim() : "";
const description = typeof body.description === "string" ? body.description.trim() : "";
const steps = normalizeSteps(body.steps);

if (!name) {
return NextResponse.json({ error: "name is required" }, { status: 400 });
}

if (steps.length === 0) {
return NextResponse.json({ error: "steps must be a non-empty array" }, { status: 400 });
}

const workflows = await readWorkflows();
const now = new Date().toISOString();

const workflow: WorkflowRecord = {
id: `wf-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
name,
description,
steps,
enabled: true,
createdAt: now,
updatedAt: now,
};

workflows.unshift(workflow);
await writeWorkflows(workflows);

return NextResponse.json({ workflow }, { status: 201 });
} catch {
return NextResponse.json({ error: "Invalid request body. Expected JSON." }, { status: 400 });
}
}
2 changes: 1 addition & 1 deletion src/app/collections/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export default async function CollectionRoute(props: {
<div className="min-w-0">
<div className="text-white font-semibold truncate flex items-center gap-2">
<span className="truncate">🧰 {s.name}</span>
<VerifiedSkillBadge info={s.verification ?? null} mode="icon" />
<VerifiedSkillBadge info={(s.verification as { slug: string } | null) ? (s.verification as import("@/lib/verification").VerifiedSkillInfo) : null} mode="icon" />
</div>
<div className="text-xs text-slate-400 mt-1">by {s.author}</div>
<div className="text-sm text-slate-300/80 mt-2 line-clamp-2">{s.description}</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/governance/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const metadata: Metadata = {
};

export default function GovernancePage() {
const data = governanceFramework as FrameworkData;
const data = governanceFramework as unknown as FrameworkData;

return (
<div className="min-h-screen bg-[#0a0a0a]">
Expand Down
6 changes: 4 additions & 2 deletions src/app/mcp/[slug]/mcp-detail-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,13 @@ export function McpDetailClient({ slug, initialServer }: { slug: string; initial
const response = await fetch(`/api/mcp/${encodeURIComponent(slug)}/install`, { method: "POST" });
const json = (await response.json()) as { installs?: number };

if (typeof json.installs === "number") {
const installCount = json.installs;

if (typeof installCount === "number") {
setPayload((prev) => ({
server: prev?.server ?? activeServer,
health: prev?.health ?? health ?? UNKNOWN_HEALTH,
installs: json.installs,
installs: installCount,
}));
}

Expand Down
6 changes: 5 additions & 1 deletion src/app/setup/SetupWizardClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ function HostDetectionStep({
onHostChange: (host: HostId) => void;
}) {
const detectedHint = useMemo(() => {
if (typeof window === "undefined" || typeof navigator === "undefined") {
return "Auto-detect hint: host detection runs after the page loads.";
}

const userAgent = navigator.userAgent.toLowerCase();
const platform = navigator.platform.toLowerCase();

Expand Down Expand Up @@ -370,7 +374,7 @@ function VerificationStep({ report }: { report: VerificationReport }) {
}

function copyText(text: string) {
if (navigator?.clipboard?.writeText) {
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
void navigator.clipboard.writeText(text);
}
}
Expand Down
Loading
Loading