Jake Shore 96e52666c5 MCPEngine full sync — studio scaffold, factory v2, server updates, state.json — 2026-02-12
=== NEW ===
- studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan)
- docs/FACTORY-V2.md — Factory v2 architecture doc
- docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report

=== UPDATED SERVERS ===
- fieldedge: Added jobs-tools, UI build script, main entry update
- lightspeed: Updated main + server entry points
- squarespace: Added collection-browser + page-manager apps
- toast: Added main + server entry points

=== INFRA ===
- infra/command-center/state.json — Updated pipeline state
- infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
2026-02-12 17:58:33 -05:00

153 lines
5.1 KiB
TypeScript

'use client';
import React, { useState, useCallback } from 'react';
import { GripVertical, Trash2, ChevronDown, ChevronRight } from 'lucide-react';
import type { SchemaProperty } from '@mcpengine/ai-pipeline/types';
interface ParamEditorProps {
name: string;
property: SchemaProperty;
required: boolean;
onUpdate: (updates: Partial<SchemaProperty>) => void;
onToggleRequired: () => void;
onDelete: () => void;
}
const TYPE_OPTIONS = ['string', 'number', 'boolean', 'array', 'object'] as const;
export function ParamEditor({
name,
property,
required,
onUpdate,
onToggleRequired,
onDelete,
}: ParamEditorProps) {
const [expanded, setExpanded] = useState(false);
const toggleExpand = useCallback(() => setExpanded((prev) => !prev), []);
return (
<div className="bg-gray-800/60 border border-gray-700 rounded-lg overflow-hidden">
{/* Collapsed header row */}
<div className="flex items-center gap-2 px-2 py-2">
{/* Drag handle */}
<GripVertical className="w-3.5 h-3.5 text-gray-600 cursor-grab shrink-0" />
{/* Expand toggle */}
<button onClick={toggleExpand} className="shrink-0 text-gray-500 hover:text-gray-300">
{expanded ? (
<ChevronDown className="w-3.5 h-3.5" />
) : (
<ChevronRight className="w-3.5 h-3.5" />
)}
</button>
{/* Name */}
<span className="text-sm text-gray-200 font-mono flex-1 truncate">{name}</span>
{/* Type badge */}
<span className="text-[10px] bg-gray-700 text-gray-400 px-1.5 py-0.5 rounded shrink-0">
{property.type}
</span>
{/* Required badge */}
{required && (
<span className="text-[10px] bg-amber-500/20 text-amber-400 px-1.5 py-0.5 rounded shrink-0">
req
</span>
)}
{/* Delete */}
<button
onClick={onDelete}
className="shrink-0 p-1 text-gray-600 hover:text-red-400 transition-colors"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
{/* Expanded fields */}
{expanded && (
<div className="px-3 pb-3 pt-1 space-y-2.5 border-t border-gray-700/50">
{/* Name input */}
<div>
<label className="block text-[10px] text-gray-500 mb-1">Name</label>
<input
type="text"
value={name}
readOnly
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 font-mono outline-none"
/>
</div>
{/* Type select */}
<div>
<label className="block text-[10px] text-gray-500 mb-1">Type</label>
<select
value={property.type}
onChange={(e) => onUpdate({ type: e.target.value })}
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 outline-none"
>
{TYPE_OPTIONS.map((t) => (
<option key={t} value={t}>
{t}
</option>
))}
</select>
</div>
{/* Required toggle */}
<div className="flex items-center justify-between">
<label className="text-[10px] text-gray-500">Required</label>
<button
onClick={onToggleRequired}
className={`
relative w-8 h-4.5 rounded-full transition-colors duration-200
${required ? 'bg-amber-500' : 'bg-gray-600'}
`}
>
<span
className={`
absolute top-0.5 left-0.5 w-3.5 h-3.5 bg-white rounded-full
transition-transform duration-200
${required ? 'translate-x-3.5' : 'translate-x-0'}
`}
/>
</button>
</div>
{/* Description */}
<div>
<label className="block text-[10px] text-gray-500 mb-1">Description</label>
<input
type="text"
value={property.description}
onChange={(e) => onUpdate({ description: e.target.value })}
placeholder="Describe this parameter..."
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 outline-none focus:border-indigo-500 transition-colors"
/>
</div>
{/* Default value */}
<div>
<label className="block text-[10px] text-gray-500 mb-1">Default Value</label>
<input
type="text"
value={property.default != null ? String(property.default) : ''}
onChange={(e) =>
onUpdate({
default: e.target.value === '' ? undefined : e.target.value,
})
}
placeholder="Optional default..."
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 outline-none focus:border-indigo-500 transition-colors"
/>
</div>
</div>
)}
</div>
);
}