{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ui",
  "title": "Basic UI",
  "description": "Basic components to render previews, command generation and json output",
  "dependencies": [
    "react",
    "lucide-react",
    "sonner"
  ],
  "registryDependencies": [
    "button",
    "dialog",
    "input",
    "label",
    "select",
    "textarea",
    "tabs",
    "tooltip",
    "scroll-area",
    "hover-card"
  ],
  "files": [
    {
      "path": "registry/commandly/generated-command.tsx",
      "content": "import { ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { generateCommand } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { Label } from \"@/components/ui/label\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { cn } from \"@/lib/utils\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  type ComponentProps,\n  type ReactNode,\n} from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n  tool: Tool;\n  selectedCommand?: Command | null;\n  parameterValues: Record<string, ParameterValue>;\n  onSaveCommand?: (command: string) => void;\n  useLongFlag?: boolean;\n  children?: ReactNode;\n}\n\ninterface GeneratedCommandContextValue {\n  generatedCommand: string;\n  useLongFlag: boolean;\n  setUseLongFlag: (value: boolean) => void;\n  supportsFlagPreference: boolean;\n  onCopyCommand: () => void;\n  onSaveCommand?: (command: string) => void;\n}\n\nconst GeneratedCommandContext = createContext<GeneratedCommandContextValue | null>(null);\n\nfunction useGeneratedCommandContext() {\n  const context = useContext(GeneratedCommandContext);\n\n  if (!context) {\n    throw new Error(\"GeneratedCommand compound components must be used within GeneratedCommand.\");\n  }\n\n  return context;\n}\n\nfunction GeneratedCommandRoot({\n  tool,\n  selectedCommand: providedCommand,\n  parameterValues,\n  onSaveCommand,\n  useLongFlag = false,\n  children,\n}: GeneratedCommandProps) {\n  const [prefersLongFlag, setPrefersLongFlag] = useState(useLongFlag);\n  const supportsFlagPreference = useMemo(\n    () =>\n      tool.parameters.some(\n        (parameter) =>\n          parameter.parameterType !== \"Argument\" &&\n          Boolean(parameter.shortFlag) &&\n          Boolean(parameter.longFlag),\n      ),\n    [tool.parameters],\n  );\n\n  useEffect(() => {\n    setPrefersLongFlag(useLongFlag);\n  }, [useLongFlag]);\n\n  const generatedCommand = useMemo(\n    () =>\n      generateCommand(tool, parameterValues, {\n        selectedCommand: providedCommand,\n        useLongFlag: prefersLongFlag,\n      }),\n    [tool, parameterValues, providedCommand, prefersLongFlag],\n  );\n\n  const copyCommand = useCallback(() => {\n    navigator.clipboard.writeText(generatedCommand);\n    toast(\"Command copied!\");\n  }, [generatedCommand]);\n\n  const contextValue = useMemo(\n    () => ({\n      generatedCommand,\n      useLongFlag: prefersLongFlag,\n      setUseLongFlag: setPrefersLongFlag,\n      supportsFlagPreference,\n      onCopyCommand: copyCommand,\n      onSaveCommand,\n    }),\n    [generatedCommand, prefersLongFlag, supportsFlagPreference, copyCommand, onSaveCommand],\n  );\n\n  return (\n    <GeneratedCommandContext.Provider value={contextValue}>\n      {generatedCommand ? (\n        (children ?? (\n          <div className=\"min-w-0 space-y-4\">\n            <GeneratedCommandOutput />\n            <GeneratedCommandActions />\n          </div>\n        ))\n      ) : (\n        <GeneratedCommandEmptyState />\n      )}\n    </GeneratedCommandContext.Provider>\n  );\n}\n\nfunction GeneratedCommandToolbar({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\"flex flex-wrap items-center justify-end gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction GeneratedCommandHeader({\n  children,\n  className,\n  ...props\n}: ComponentProps<typeof CardHeader>) {\n  return (\n    <CardHeader\n      className={cn(\"flex flex-row items-center justify-between gap-3 space-y-0\", className)}\n      {...props}\n    >\n      <CardTitle className=\"flex items-center gap-2\">\n        <TerminalIcon className=\"h-5 w-5\" />\n        Generated Command\n      </CardTitle>\n      {children}\n    </CardHeader>\n  );\n}\n\nfunction GeneratedCommandFlagPreference({\n  className,\n  ...props\n}: Omit<ComponentProps<typeof Switch>, \"checked\" | \"onCheckedChange\">) {\n  const { supportsFlagPreference, useLongFlag, setUseLongFlag } = useGeneratedCommandContext();\n\n  if (!supportsFlagPreference) return null;\n\n  return (\n    <Label className=\"shrink-0 gap-2 text-xs text-muted-foreground\">\n      <span className=\"font-mono tracking-[0.12em] uppercase\">Long flags</span>\n      <Switch\n        checked={useLongFlag}\n        onCheckedChange={setUseLongFlag}\n        aria-label=\"Long flags\"\n        className={className}\n        {...props}\n      />\n    </Label>\n  );\n}\n\nfunction GeneratedCommandOutput({ className, ...props }: ComponentProps<\"div\">) {\n  const { generatedCommand } = useGeneratedCommandContext();\n\n  return (\n    <div\n      className={cn(\"overflow-x-auto rounded bg-muted p-4 font-mono text-sm\", className)}\n      {...props}\n    >\n      <div className=\"min-w-max whitespace-nowrap\">{generatedCommand}</div>\n    </div>\n  );\n}\n\nfunction GeneratedCommandActions({ className, ...props }: ComponentProps<\"div\">) {\n  const { generatedCommand, onCopyCommand, onSaveCommand } = useGeneratedCommandContext();\n\n  return (\n    <div\n      className={cn(\"flex flex-col gap-2 sm:flex-row\", className)}\n      {...props}\n    >\n      <Button\n        onClick={onCopyCommand}\n        variant=\"outline\"\n        className=\"w-full sm:flex-1\"\n      >\n        <CopyIcon className=\"mr-2 h-4 w-4\" />\n        Copy Command\n      </Button>\n      {onSaveCommand && (\n        <Button\n          onClick={() => onSaveCommand(generatedCommand)}\n          variant=\"outline\"\n          className=\"w-full sm:flex-1\"\n        >\n          <SaveIcon className=\"mr-2 h-4 w-4\" />\n          Save Command\n        </Button>\n      )}\n    </div>\n  );\n}\n\nfunction GeneratedCommandEmptyState() {\n  return (\n    <div className=\"py-8 text-center\">\n      <TerminalIcon className=\"mx-auto mb-4 h-12 w-12 text-muted-foreground\" />\n      <p className=\"text-muted-foreground\">Configure parameters to generate the command.</p>\n    </div>\n  );\n}\n\nexport const GeneratedCommand = Object.assign(GeneratedCommandRoot, {\n  Header: GeneratedCommandHeader,\n  Toolbar: GeneratedCommandToolbar,\n  FlagPreference: GeneratedCommandFlagPreference,\n  Output: GeneratedCommandOutput,\n  Actions: GeneratedCommandActions,\n  EmptyState: GeneratedCommandEmptyState,\n});\n",
      "type": "registry:component",
      "target": "components/commandly/generated-command.tsx"
    },
    {
      "path": "registry/commandly/json-output.tsx",
      "content": "import { Tool } from \"@/components/commandly/types/flat\";\nimport { exportToStructuredJSON } from \"@/components/commandly/utils/flat\";\nimport { convertToNestedStructure } from \"@/components/commandly/utils/nested\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport {\n  Command as UICommand,\n  CommandGroup,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\nimport { CheckIcon, ChevronsUpDownIcon, CopyIcon, Edit2Icon, XIcon } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nconst jsonOptions = [\n  { value: \"nested\", label: \"Nested\" },\n  { value: \"flat\", label: \"Flat\" },\n];\n\ntype DiffLine = { type: \"same\" | \"added\" | \"removed\"; text: string };\n\nfunction diffLines(before: string, after: string): DiffLine[] {\n  const a = before.split(\"\\n\");\n  const b = after.split(\"\\n\");\n  const m = a.length;\n  const n = b.length;\n  const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n  for (let i = 1; i <= m; i++)\n    for (let j = 1; j <= n; j++)\n      dp[i][j] =\n        a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]);\n  const result: DiffLine[] = [];\n  let i = m,\n    j = n;\n  while (i > 0 || j > 0) {\n    if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {\n      result.unshift({ type: \"same\", text: a[i - 1] });\n      i--;\n      j--;\n    } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {\n      result.unshift({ type: \"added\", text: b[j - 1] });\n      j--;\n    } else {\n      result.unshift({ type: \"removed\", text: a[i - 1] });\n      i--;\n    }\n  }\n  return result;\n}\n\ninterface JsonTypeComponentProps {\n  tool: Tool;\n  originalTool?: Tool;\n  onApply?: (tool: Tool) => void;\n}\n\nexport function JsonOutput({ tool, originalTool, onApply }: JsonTypeComponentProps) {\n  const [open, setOpen] = useState(false);\n  const [jsonString, setJsonString] = useState<string>();\n  const [originalJsonString, setOriginalJsonString] = useState<string>();\n  const [jsonType, setJsonType] = useState<\"nested\" | \"flat\">(\"flat\");\n  const [isEditing, setIsEditing] = useState(false);\n  const [editValue, setEditValue] = useState(\"\");\n  const [showDiff, setShowDiff] = useState(true);\n\n  useEffect(() => {\n    const config =\n      jsonType === \"flat\" ? exportToStructuredJSON(tool) : convertToNestedStructure(tool);\n    setJsonString(JSON.stringify(config, null, 2));\n  }, [jsonType, tool]);\n\n  useEffect(() => {\n    if (!originalTool) {\n      setOriginalJsonString(undefined);\n      return;\n    }\n    const config =\n      jsonType === \"flat\"\n        ? exportToStructuredJSON(originalTool)\n        : convertToNestedStructure(originalTool);\n    setOriginalJsonString(JSON.stringify(config, null, 2));\n  }, [jsonType, originalTool]);\n\n  const diff = useMemo(() => {\n    if (!originalJsonString || !jsonString || originalJsonString === jsonString) return null;\n    return diffLines(originalJsonString, jsonString);\n  }, [originalJsonString, jsonString]);\n\n  const diffStats = useMemo(() => {\n    if (!diff) return null;\n    const added = diff.filter((l) => l.type === \"added\").length;\n    const removed = diff.filter((l) => l.type === \"removed\").length;\n    return { added, removed };\n  }, [diff]);\n\n  const handleEditToggle = () => {\n    setEditValue(jsonString ?? \"\");\n    setIsEditing(true);\n  };\n\n  const handleApply = () => {\n    try {\n      const parsed = JSON.parse(editValue) as Tool;\n      onApply!(parsed);\n      setIsEditing(false);\n    } catch {\n      toast.error(\"Invalid JSON\", { description: \"Please fix the JSON before applying.\" });\n    }\n  };\n\n  const handleCancel = () => {\n    setIsEditing(false);\n    setEditValue(\"\");\n  };\n\n  return (\n    <Card className=\"max-w-full\">\n      <CardHeader className=\"gap-4\">\n        <CardTitle className=\"flex min-w-0 flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n          <div className=\"flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center\">\n            <span className=\"text-sm\">Output type:</span>\n            <Popover\n              open={open}\n              onOpenChange={setOpen}\n            >\n              <PopoverTrigger asChild>\n                <Button\n                  variant=\"outline\"\n                  role=\"combobox\"\n                  aria-expanded={open}\n                  className=\"w-full min-w-0 justify-between sm:w-48\"\n                >\n                  {jsonOptions.find((option) => option.value === jsonType)?.label}\n                  <ChevronsUpDownIcon className=\"opacity-50\" />\n                </Button>\n              </PopoverTrigger>\n              <PopoverContent className=\"w-(--radix-popover-trigger-width) min-w-40 p-0 sm:w-48\">\n                <UICommand>\n                  <CommandList>\n                    <CommandGroup>\n                      {jsonOptions.map((option) => (\n                        <CommandItem\n                          key={option.value}\n                          value={option.value}\n                          onSelect={(currentValue) => {\n                            setJsonType(currentValue as \"nested\" | \"flat\");\n                            setOpen(false);\n                          }}\n                        >\n                          {option.label}\n                          <CheckIcon\n                            className={cn(\n                              \"ml-auto h-4 w-4\",\n                              jsonType === option.value ? \"opacity-100\" : \"opacity-0\",\n                            )}\n                          />\n                        </CommandItem>\n                      ))}\n                    </CommandGroup>\n                  </CommandList>\n                </UICommand>\n              </PopoverContent>\n            </Popover>\n          </div>\n        </CardTitle>\n        <div className=\"flex shrink-0 items-center gap-3 self-start sm:self-auto\">\n          {onApply && !isEditing && (\n            <CardAction\n              className=\"rounded-md\"\n              onClick={handleEditToggle}\n            >\n              <Edit2Icon className=\"h-4 w-4 dark:stroke-primary\" />\n            </CardAction>\n          )}\n          {onApply && isEditing && (\n            <CardAction\n              className=\"rounded-md\"\n              onClick={handleCancel}\n            >\n              <XIcon className=\"h-4 w-4 dark:stroke-primary\" />\n            </CardAction>\n          )}\n          <CardAction\n            className=\"rounded-md\"\n            onClick={() => {\n              navigator.clipboard.writeText(jsonString!);\n              toast(\"Copied!\");\n            }}\n          >\n            <CopyIcon className=\"h-4 w-4 dark:stroke-primary\" />\n          </CardAction>\n        </div>\n      </CardHeader>\n      <CardContent>\n        {diffStats && !isEditing && (\n          <div className=\"mb-2 flex flex-wrap items-center gap-2\">\n            {diffStats.added > 0 && (\n              <Badge\n                variant=\"outline\"\n                className=\"border-green-500/40 bg-green-500/10 text-green-600 dark:text-green-400\"\n              >\n                +{diffStats.added} added\n              </Badge>\n            )}\n            {diffStats.removed > 0 && (\n              <Badge\n                variant=\"outline\"\n                className=\"border-red-500/40 bg-red-500/10 text-red-600 dark:text-red-400\"\n              >\n                -{diffStats.removed} removed\n              </Badge>\n            )}\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              className=\"h-6 px-2 text-xs sm:ml-auto\"\n              onClick={() => setShowDiff((v) => !v)}\n            >\n              {showDiff ? \"Full view\" : \"Diff view\"}\n            </Button>\n          </div>\n        )}\n        {isEditing ? (\n          <div className=\"flex flex-col gap-2\">\n            <ScrollArea\n              className=\"max-w-full *:data-radix-scroll-area-viewport:max-h-[calc(100vh-360px)]\"\n              type=\"hover\"\n            >\n              <Textarea\n                className=\"min-h-80 font-mono text-sm sm:min-h-[calc(100vh-400px)]\"\n                value={editValue}\n                onChange={(e) => setEditValue(e.target.value)}\n                spellCheck={false}\n              />\n              <ScrollBar orientation=\"vertical\" />\n              <ScrollBar orientation=\"horizontal\" />\n            </ScrollArea>\n            <div className=\"flex justify-end gap-2\">\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={handleCancel}\n              >\n                Cancel\n              </Button>\n              <Button\n                size=\"sm\"\n                onClick={handleApply}\n              >\n                Apply\n              </Button>\n            </div>\n          </div>\n        ) : diff && showDiff ? (\n          <ScrollArea\n            className=\"max-w-full *:data-radix-scroll-area-viewport:max-h-[calc(100vh-320px)]\"\n            type=\"hover\"\n          >\n            <pre className=\"w-fit min-w-full rounded-md font-mono text-sm whitespace-pre\">\n              {diff.map((line, idx) => (\n                <div\n                  key={idx}\n                  className={cn(\n                    \"px-1\",\n                    line.type === \"added\" && \"bg-green-500/10 text-green-700 dark:text-green-400\",\n                    line.type === \"removed\" && \"bg-red-500/10 text-red-700 dark:text-red-400\",\n                    line.type === \"same\" && \"text-foreground/80\",\n                  )}\n                >\n                  <span className=\"opacity-50 select-none\">\n                    {line.type === \"added\" ? \"+ \" : line.type === \"removed\" ? \"- \" : \"  \"}\n                  </span>\n                  {line.text}\n                </div>\n              ))}\n            </pre>\n            <ScrollBar orientation=\"vertical\" />\n            <ScrollBar orientation=\"horizontal\" />\n          </ScrollArea>\n        ) : (\n          <ScrollArea\n            className=\"max-w-full *:data-radix-scroll-area-viewport:max-h-[calc(100vh-320px)]\"\n            type=\"hover\"\n          >\n            <pre className=\"w-fit min-w-full rounded-md bg-card font-mono text-sm whitespace-pre dark:text-gray-200\">\n              {jsonString}\n            </pre>\n            <ScrollBar orientation=\"vertical\" />\n            <ScrollBar orientation=\"horizontal\" />\n          </ScrollArea>\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/commandly/json-output.tsx"
    },
    {
      "path": "registry/commandly/tool-renderer.tsx",
      "content": "import { ParameterValue } from \"@/components/commandly/types/flat\";\nimport { Command, Tool } from \"@/components/commandly/types/flat\";\nimport {\n  ParameterRenderContext,\n  ParameterRendererEntry,\n} from \"@/components/commandly/types/renderer\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command as UICommand,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { MultiSelect } from \"@/components/ui/multi-select\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\nimport { CheckIcon, ChevronsUpDownIcon, InfoIcon, PlusIcon, XIcon } from \"lucide-react\";\nimport React, { useMemo } from \"react\";\n\nconst findDefaultCommand = (tool: Tool): Command | null => {\n  const nameMatchCommand = tool.commands.find(\n    (command) => command.name.toLowerCase() === tool.binaryName.toLowerCase(),\n  );\n  if (nameMatchCommand) return nameMatchCommand;\n\n  return tool.commands.length > 0 ? tool.commands[0] : null;\n};\n\ninterface ParameterLabelProps {\n  name: string;\n  longFlag?: string;\n  shortFlag?: string;\n  isRequired?: boolean;\n  isGlobal?: boolean;\n  description?: string;\n  className?: string;\n  children?: React.ReactNode;\n}\n\nfunction ParameterLabel({\n  name,\n  longFlag,\n  shortFlag,\n  isRequired,\n  isGlobal,\n  description,\n  className,\n  children,\n}: ParameterLabelProps) {\n  return (\n    <Label className={className}>\n      {name}\n      {(longFlag || shortFlag) && (\n        <span className=\"ml-1 text-muted-foreground\">\n          ({[longFlag, shortFlag].filter(Boolean).join(\", \")})\n        </span>\n      )}\n      {isRequired && <span className=\"ml-1 text-destructive\">*</span>}\n      {description?.trim() && (\n        <Tooltip>\n          <TooltipTrigger>\n            <InfoIcon className=\"h-3.5 w-3.5\" />\n          </TooltipTrigger>\n          <TooltipContent>\n            <span>{description}</span>\n          </TooltipContent>\n        </Tooltip>\n      )}\n      {children}\n      {isGlobal && (\n        <Badge\n          variant=\"outline\"\n          className=\"ml-2 text-xs\"\n        >\n          global\n        </Badge>\n      )}\n    </Label>\n  );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n  return (\n    <div className=\"flex items-center space-x-2\">\n      <Switch\n        checked={value === \"true\" || value === true}\n        onCheckedChange={onUpdate}\n      />\n      <ParameterLabel\n        className=\"flex-1 select-auto\"\n        name={parameter.name}\n        longFlag={parameter.longFlag}\n        shortFlag={parameter.shortFlag}\n        isRequired={parameter.isRequired}\n        isGlobal={parameter.isGlobal}\n        description={parameter.description}\n      />\n    </div>\n  );\n}\n\nfunction OptionEnumInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n  const [open, setOpen] = React.useState(false);\n  const separator = parameter.enum?.separator || \",\";\n  const options =\n    parameter.enum?.values?.map((e) => ({\n      value: e.value,\n      label: e.displayName || e.value,\n    })) ?? [];\n\n  const label = (\n    <ParameterLabel\n      className=\"select-auto\"\n      name={parameter.name}\n      longFlag={parameter.longFlag}\n      shortFlag={parameter.shortFlag}\n      isRequired={parameter.isRequired}\n      isGlobal={parameter.isGlobal}\n      description={parameter.description}\n    />\n  );\n\n  if (parameter.enum?.allowMultiple) {\n    const selected = Array.isArray(value)\n      ? (value as string[]).filter(Boolean)\n      : value\n        ? (value as string).split(separator).filter(Boolean)\n        : [];\n    return (\n      <div className=\"space-y-2\">\n        {label}\n        <MultiSelect\n          options={options}\n          defaultValue={selected}\n          onValueChange={(vals) => onUpdate(vals.join(separator))}\n          placeholder=\"Select options\"\n        />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      {label}\n      <Popover\n        open={open}\n        onOpenChange={setOpen}\n      >\n        <PopoverTrigger asChild>\n          <Button\n            variant=\"outline\"\n            role=\"combobox\"\n            aria-expanded={open}\n            className=\"w-full justify-between font-normal\"\n          >\n            {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n            <ChevronsUpDownIcon className=\"opacity-50\" />\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent\n          className=\"w-[--radix-popover-trigger-width] p-0\"\n          align=\"start\"\n        >\n          <UICommand>\n            <CommandInput placeholder=\"Search...\" />\n            <CommandList>\n              <CommandEmpty>No option found.</CommandEmpty>\n              <CommandGroup>\n                {options.map((option) => (\n                  <CommandItem\n                    key={option.value}\n                    value={option.value}\n                    onSelect={(currentValue) => {\n                      onUpdate(currentValue === value ? \"\" : currentValue);\n                      setOpen(false);\n                    }}\n                  >\n                    {option.label}\n                    <CheckIcon\n                      className={cn(\n                        \"ml-auto\",\n                        value === option.value ? \"opacity-100\" : \"opacity-0\",\n                      )}\n                    />\n                  </CommandItem>\n                ))}\n              </CommandGroup>\n            </CommandList>\n          </UICommand>\n        </PopoverContent>\n      </Popover>\n    </div>\n  );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n  return (\n    <div className=\"flex items-center space-x-2\">\n      <Switch\n        checked={value === \"true\" || value === true}\n        onCheckedChange={(checked) => onUpdate(checked.toString())}\n      />\n      <ParameterLabel\n        className=\"flex-1 select-auto\"\n        name={parameter.name}\n        longFlag={parameter.longFlag}\n        shortFlag={parameter.shortFlag}\n        isRequired={parameter.isRequired}\n        isGlobal={parameter.isGlobal}\n        description={parameter.description}\n      />\n    </div>\n  );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n  return (\n    <div className=\"space-y-2\">\n      <ParameterLabel\n        className=\"flex-1 select-auto\"\n        name={parameter.name}\n        longFlag={parameter.longFlag}\n        shortFlag={parameter.shortFlag}\n        isRequired={parameter.isRequired}\n        isGlobal={parameter.isGlobal}\n        description={parameter.description}\n      />\n      <Input\n        type={parameter.dataType === \"Number\" ? \"number\" : \"text\"}\n        value={parameter.dataType === \"Number\" ? (value as number) : (value as string)}\n        onChange={(e) => onUpdate(e.target.value)}\n        placeholder=\"Enter value\"\n      />\n    </div>\n  );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n  return (\n    <div className=\"space-y-2\">\n      <ParameterLabel\n        name={parameter.name}\n        isRequired={parameter.isRequired}\n        description={parameter.description}\n      >\n        <Badge\n          variant=\"secondary\"\n          className=\"ml-2 text-xs\"\n        >\n          {parameter.parameterType}\n          {parameter.position !== undefined && ` (${parameter.position})`}\n        </Badge>\n      </ParameterLabel>\n      <Input\n        type={parameter.dataType === \"Number\" ? \"number\" : \"text\"}\n        value={parameter.dataType === \"Number\" ? (value as number) : (value as string)}\n        onChange={(e) => onUpdate(e.target.value)}\n        placeholder=\"Enter value\"\n      />\n    </div>\n  );\n}\n\ninterface RepeatableWrapperProps {\n  parameter: ParameterRenderContext[\"parameter\"];\n  value: ParameterRenderContext[\"value\"];\n  onUpdate: ParameterRenderContext[\"onUpdate\"];\n  renderEntry: ParameterRendererEntry[\"component\"];\n}\n\nfunction RepeatableWrapper({ parameter, value, onUpdate, renderEntry }: RepeatableWrapperProps) {\n  const toArray = (v: ParameterRenderContext[\"value\"]): string[] => {\n    if (Array.isArray(v)) return v;\n    if (v !== undefined && v !== \"\" && v !== false) return [String(v)];\n    return [\"\"];\n  };\n\n  const values = toArray(value);\n\n  const updateAt = (index: number, val: ParameterRenderContext[\"value\"]) => {\n    const next = [...values];\n    next[index] = String(val);\n    onUpdate(next);\n  };\n\n  const addRow = () => onUpdate([...values, \"\"]);\n\n  const removeAt = (index: number) => {\n    const next = values.filter((_, i) => i !== index);\n    onUpdate(next);\n  };\n\n  return (\n    <div className=\"space-y-2\">\n      {values.map((val, index) => (\n        <div\n          key={index}\n          className=\"flex items-start gap-2\"\n        >\n          <div className=\"flex-1\">\n            {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n          </div>\n          {index > 0 && (\n            <Button\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"mt-6 shrink-0\"\n              onClick={() => removeAt(index)}\n            >\n              <XIcon className=\"h-4 w-4\" />\n            </Button>\n          )}\n        </div>\n      ))}\n      <Button\n        type=\"button\"\n        variant=\"ghost\"\n        size=\"sm\"\n        className=\"h-7 px-2 text-muted-foreground\"\n        onClick={addRow}\n      >\n        <PlusIcon className=\"mr-1 h-3.5 w-3.5\" />\n        Add another\n      </Button>\n    </div>\n  );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n  return [\n    { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => <FlagInput {...ctx} /> },\n    {\n      condition: (p) => p.parameterType === \"Argument\",\n      component: (ctx) => <ArgumentInput {...ctx} />,\n    },\n    {\n      condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n      component: (ctx) => <OptionEnumInput {...ctx} />,\n    },\n    {\n      condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n      component: (ctx) => <OptionBooleanInput {...ctx} />,\n    },\n    {\n      condition: (p) => p.parameterType === \"Option\",\n      component: (ctx) => <OptionTextInput {...ctx} />,\n    },\n  ];\n}\n\ninterface ToolRendererProps {\n  selectedCommand?: Command | null;\n  tool: Tool;\n  catalog?: ParameterRendererEntry[];\n  parameterValues: Record<string, ParameterValue>;\n  updateParameterValue: (parameterKey: string, value: ParameterValue) => void;\n}\n\nexport function ToolRenderer({\n  selectedCommand: providedCommand,\n  tool,\n  catalog = defaultComponents(),\n  parameterValues,\n  updateParameterValue,\n}: ToolRendererProps) {\n  const selectedCommand =\n    providedCommand === undefined ? findDefaultCommand(tool) : providedCommand;\n  const hasCommands = tool.commands.length > 0;\n\n  const visibleParameters = useMemo(() => {\n    if (!hasCommands || !selectedCommand) {\n      return tool.parameters.filter((p) => !p.commandKey || p.isGlobal);\n    }\n    return tool.parameters.filter(\n      (param) => param.commandKey === selectedCommand?.key || param.isGlobal,\n    );\n  }, [tool, hasCommands, selectedCommand]);\n\n  return (\n    <React.Fragment>\n      <div className=\"space-y-4\">\n        {visibleParameters.length > 0 ? (\n          visibleParameters.map((parameter) => {\n            const value = parameterValues[parameter.key] ?? \"\";\n            const onUpdate = (val: ParameterValue) => updateParameterValue(parameter.key, val);\n            const entry = catalog.find((e) => e.condition(parameter));\n            if (!entry) return null;\n            return (\n              <React.Fragment key={parameter.key}>\n                {parameter.isRepeatable ? (\n                  <RepeatableWrapper\n                    parameter={parameter}\n                    value={value}\n                    onUpdate={onUpdate}\n                    renderEntry={entry.component}\n                  />\n                ) : (\n                  entry.component({ parameter, value, onUpdate })\n                )}\n              </React.Fragment>\n            );\n          })\n        ) : (\n          <p className=\"text-sm text-muted-foreground\">No parameters available for this command.</p>\n        )}\n      </div>\n    </React.Fragment>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/commandly/tool-renderer.tsx"
    },
    {
      "path": "registry/commandly/types/renderer.ts",
      "content": "import type { Parameter, ParameterValue } from \"@/components/commandly/types/flat\";\nimport type { ReactElement } from \"react\";\n\nexport type ParameterRenderContext = {\n  parameter: Parameter;\n  value: ParameterValue;\n  onUpdate: (value: ParameterValue) => void;\n};\n\nexport type ParameterRendererEntry = {\n  condition: (parameter: Parameter) => boolean;\n  component: (context: ParameterRenderContext) => ReactElement | null;\n};\n",
      "type": "registry:file",
      "target": "components/commandly/types/renderer.ts"
    },
    {
      "path": "registry/commandly/types/flat.ts",
      "content": "export interface ToolInfo {\n  /** A brief human-readable description of what the tool does. */\n  description?: string;\n  /** The version string of the tool (e.g. \"1.0.0\"). */\n  version?: string;\n  /** The homepage or documentation URL for the tool. */\n  url?: string;\n}\n\nexport interface Command {\n  /** Unique identifier for this command within the tool. */\n  key: string;\n  /** Key of the parent command; used to represent subcommand nesting. */\n  parentCommandKey?: string;\n  /** Human-readable display name of the command. */\n  name: string;\n  /** Brief description of what this command does. */\n  description?: string;\n  /** Whether this command opens an interactive session or prompt. */\n  interactive?: boolean;\n  /** Display sort position relative to sibling commands. */\n  sortOrder?: number;\n}\n\nexport interface ParameterEnumValue {\n  /** The raw value passed to the CLI for this choice. */\n  value: string;\n  /** Human-readable label shown to the user for this enum choice. */\n  displayName: string;\n  /** Description of what this enum value does or represents. */\n  description?: string;\n  /** Whether this is the default selection when no value is provided. */\n  isDefault?: boolean;\n  /** Display sort position relative to sibling enum values. */\n  sortOrder?: number;\n}\n\nexport interface ParameterEnumValues {\n  /** The list of allowed enum choices. */\n  values: ParameterEnumValue[];\n  /** Whether the user can select multiple values at once. */\n  allowMultiple?: boolean;\n  /** Separator character used when joining multiple selected values. */\n  separator?: string;\n}\n\nexport type ParameterValidationType =\n  | \"min_length\"\n  | \"max_length\"\n  | \"min_value\"\n  | \"max_value\"\n  | \"regex\";\n\nexport interface ParameterValidation {\n  /** Unique identifier for this validation rule. */\n  key: string;\n  /** The type of validation to apply. */\n  validationType: ParameterValidationType;\n  /** The value to validate against (e.g. the max length number, or a regex pattern). */\n  validationValue: string;\n  /** The error message to display when validation fails. */\n  errorMessage: string;\n}\n\nexport type ParameterDependencyType = \"requires\" | \"conflicts_with\";\n\nexport interface ParameterDependency {\n  /** Unique identifier for this dependency rule. */\n  key: string;\n  /** Key of the parameter that owns this dependency. */\n  parameterKey: string;\n  /** Key of the parameter this dependency references. */\n  dependsOnParameterKey: string;\n  /** Whether this parameter requires or conflicts with the referenced parameter. */\n  dependencyType: ParameterDependencyType;\n  /** Optional value that the referenced parameter must have for this dependency to apply. */\n  conditionValue?: string;\n}\n\nexport type ParameterValue = string | number | boolean | string[];\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ToolMetadata {}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ParameterMetadata {\n  /** Arbitrary tags for categorising or filtering parameters. */\n  tags?: string[];\n}\n\nexport type ParameterType = \"Flag\" | \"Option\" | \"Argument\";\n\nexport type ParameterDataType = \"String\" | \"Number\" | \"Boolean\" | \"Enum\";\n\nexport interface Parameter {\n  /** Unique identifier for this parameter within the tool. */\n  key: string;\n  /** Human-readable display name of the parameter. */\n  name: string;\n  /** Key of the command this parameter belongs to; omit for global parameters. */\n  commandKey?: string;\n  /** Brief description of what this parameter does or accepts. */\n  description?: string;\n  /** Optional grouping label for organising related parameters in the UI. */\n  group?: string;\n  /** Additional metadata such as tags. */\n  metadata?: ParameterMetadata;\n  /** Whether this is a boolean flag, a key-value option, or a positional argument. */\n  parameterType: ParameterType;\n  /** The data type of the parameter's value. */\n  dataType: ParameterDataType;\n  /** Whether the user must provide this parameter. */\n  isRequired?: boolean;\n  /** Whether this parameter can be specified multiple times. */\n  isRepeatable?: boolean;\n  /** Whether this parameter applies to all commands rather than a single command. */\n  isGlobal?: boolean;\n  /** The single-character short flag (e.g. \"-v\"). */\n  shortFlag?: string;\n  /** The long-form flag or option name (e.g. \"--verbose\"). */\n  longFlag?: string;\n  /** Zero-based position index for positional arguments. */\n  position?: number;\n  /** Display sort position relative to sibling parameters. */\n  sortOrder?: number;\n  /** Separator character used when the option accepts multiple values in one argument (e.g. \",\"). */\n  arraySeparator?: string;\n  /** Separator between key and value for key=value style options (e.g. \"=\"). */\n  keyValueSeparator?: string;\n  /** Allowed enum choices when dataType is \"Enum\". */\n  enum?: ParameterEnumValues;\n  /** Validation rules applied to this parameter's value. */\n  validations?: ParameterValidation[];\n  /** Dependencies on other parameters (requires or conflicts-with relationships). */\n  dependencies?: ParameterDependency[];\n}\n\nexport type ExclusionType = \"mutual_exclusive\" | \"required_one_of\";\n\nexport interface ExclusionGroup {\n  /** Unique identifier for this exclusion group. */\n  key?: string;\n  /** Key of the command this exclusion group belongs to; omit for global groups. */\n  commandKey?: string;\n  /** Human-readable name for this exclusion group. */\n  name: string;\n  /** Whether parameters in this group are mutually exclusive or one is required. */\n  exclusionType: ExclusionType;\n  /** Keys of the parameters that participate in this exclusion group. */\n  parameterKeys: string[];\n}\n\nexport interface Tool {\n  /** Unique binary name for the tool that it can be invoked from the command line (e.g. \"httpx\"). */\n  binaryName: string;\n  /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n  displayName: string;\n  /** Whether the root tool invocation opens an interactive session or prompt. */\n  interactive?: boolean;\n  /** General information about the tool such as description, version, and URL. */\n  info?: ToolInfo;\n  /** List of all commands and subcommands defined for this tool. */\n  commands: Command[];\n  /** Flat list of all parameters across all commands and global scope. */\n  parameters: Parameter[];\n  /** Groups of parameters with mutual exclusion or required-one-of constraints. */\n  exclusionGroups?: ExclusionGroup[];\n  /** Arbitrary metadata attached to the tool. */\n  metadata?: ToolMetadata;\n}\n",
      "type": "registry:file",
      "target": "components/commandly/types/flat.ts"
    },
    {
      "path": "registry/commandly/types/nested.ts",
      "content": "import type {\n  ExclusionType,\n  ParameterDataType,\n  ParameterDependencyType,\n  ParameterEnumValues,\n  ParameterMetadata,\n  ParameterType,\n  ParameterValidationType,\n  ToolInfo,\n  ToolMetadata,\n} from \"@/components/commandly/types/flat\";\n\nexport interface NestedParameterValidation {\n  /** The type of validation to apply. */\n  validationType: ParameterValidationType;\n  /** The value to validate against (e.g. max length number or regex pattern). */\n  validationValue: string;\n  /** The error message to display when validation fails. */\n  errorMessage: string;\n}\n\nexport interface NestedParameterDependency {\n  /** Name of the parameter this dependency references. */\n  dependsOnParameter: string;\n  /** Whether this parameter requires or conflicts with the referenced parameter. */\n  dependencyType: ParameterDependencyType;\n  /** Optional value that the referenced parameter must have for this dependency to apply. */\n  conditionValue?: string;\n}\n\nexport interface NestedParameter {\n  /** Human-readable display name of the parameter. */\n  name: string;\n  /** Brief description of what this parameter does or accepts. */\n  description?: string;\n  /** Optional grouping label for organising related parameters in the UI. */\n  group?: string;\n  /** Whether this is a boolean flag, a key-value option, or a positional argument. */\n  parameterType: ParameterType;\n  /** The data type of the parameter's value. */\n  dataType: ParameterDataType;\n  /** Additional metadata such as tags. */\n  metadata?: ParameterMetadata;\n  /** Whether the user must provide this parameter. */\n  isRequired?: boolean;\n  /** Whether this parameter can be specified multiple times. */\n  isRepeatable?: boolean;\n  /** Whether this parameter applies to all commands rather than a single command. */\n  isGlobal?: boolean;\n  /** The single-character short flag (e.g. \"-v\"). */\n  shortFlag?: string;\n  /** The long-form flag or option name (e.g. \"--verbose\"). */\n  longFlag?: string;\n  /** Zero-based position index for positional arguments. */\n  position?: number;\n  /** Display sort position relative to sibling parameters. */\n  sortOrder?: number;\n  /** Separator character used when the option accepts multiple values in one argument (e.g. \",\"). */\n  arraySeparator?: string;\n  /** Separator between key and value for key=value style options (e.g. \"=\"). */\n  keyValueSeparator?: string;\n  /** Allowed enum choices when dataType is \"Enum\". */\n  enum?: ParameterEnumValues;\n  /** Validation rules applied to this parameter's value. */\n  validations?: NestedParameterValidation[];\n  /** Dependencies on other parameters (requires or conflicts-with relationships). */\n  dependencies?: NestedParameterDependency[];\n}\n\nexport interface NestedCommand {\n  /** Human-readable display name of the command. */\n  name: string;\n  /** Brief description of what this command does. */\n  description?: string;\n  /** Whether this command opens an interactive session or prompt. */\n  interactive?: boolean;\n  /** Display sort position relative to sibling commands. */\n  sortOrder?: number;\n  /** Parameters that belong directly to this command. */\n  parameters: NestedParameter[];\n  /** Nested subcommands of this command. */\n  subcommands: NestedCommand[] /** Groups of parameters with mutual exclusion or required-one-of constraints scoped to this command. */;\n  exclusionGroups?: NestedExclusionGroup[];\n}\n\nexport interface NestedExclusionGroup {\n  /** Human-readable name for this exclusion group. */\n  name: string;\n  /** Whether parameters in this group are mutually exclusive or one is required. */\n  exclusionType: ExclusionType;\n  /** Names of the parameters that participate in this exclusion group. */\n  parameters: string[];\n}\n\nexport interface NestedTool {\n  /** Unique binary name for the tool that it can be invoked from the command line (e.g. \"httpx\"). */\n  binaryName: string;\n  /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n  displayName: string;\n  /** Whether the root tool invocation opens an interactive session or prompt. */\n  interactive?: boolean;\n  /** General information about the tool such as description, version, and URL. */\n  info?: ToolInfo;\n  /** The homepage or documentation URL for the tool. */\n  url?: string;\n  /** Parameters that belong to the root invocation when no commands exist. */\n  rootParameters: NestedParameter[];\n  /** Parameters that apply to all commands globally. */\n  globalParameters: NestedParameter[];\n  /** Hierarchical list of commands and their nested subcommands. */\n  commands: NestedCommand[];\n  /** Groups of parameters with mutual exclusion or required-one-of constraints. */\n  exclusionGroups?: NestedExclusionGroup[] | null;\n  /** Arbitrary metadata attached to the tool. */\n  metadata?: ToolMetadata;\n}\n",
      "type": "registry:file",
      "target": "components/commandly/types/nested.ts"
    },
    {
      "path": "registry/commandly/utils/flat.ts",
      "content": "import { SCHEMA_URL } from \"@/components/ai-chat/tool-rules\";\nimport type {\n  Command,\n  ExclusionGroup,\n  Parameter,\n  ParameterMetadata,\n  ParameterValue,\n  Tool,\n} from \"@/components/commandly/types/flat\";\n\nexport const slugify = (text: string): string => {\n  return text\n    .toString()\n    .toLowerCase()\n    .trim()\n    .replace(/\\s+/g, \"-\") // Replace spaces with -\n    .replace(/[^\\w-]+/g, \"\") // Remove all non-word chars\n    .replace(/--+/g, \"-\") // Replace multiple - with single -\n    .replace(/^-+/, \"\") // Trim - from start of text\n    .replace(/-+$/, \"\"); // Trim - from end of text\n};\n\nexport const getCommandPath = (command: Command, tool: Tool): string => {\n  const allCommands = tool.commands;\n  const findCommandPath = (\n    targetKey: string,\n    commands: Command[],\n    path: string[] = [],\n  ): string[] | null => {\n    for (const cmd of commands) {\n      if (cmd.name === targetKey) {\n        return [...path, cmd.name];\n      }\n\n      const childCommands = allCommands.filter((c) => c.parentCommandKey === cmd.key);\n      if (childCommands.length > 0) {\n        const subPath = findCommandPath(targetKey, childCommands, [...path, cmd.name]);\n        if (subPath) {\n          return subPath;\n        }\n      }\n    }\n    return null;\n  };\n\n  const rootCommands = tool.commands.filter((c) => !c.parentCommandKey);\n  const path = findCommandPath(command.name, rootCommands);\n\n  if (!path) return command.name;\n\n  return path.join(\" \");\n};\n\nexport const getAllSubcommands = (commandKey: string, commands: Command[]): Command[] => {\n  const result: Command[] = [];\n\n  const findSubcommands = (parentKey: string) => {\n    commands.forEach((cmd) => {\n      if (cmd.parentCommandKey === parentKey) {\n        result.push(cmd);\n        findSubcommands(cmd.key);\n      }\n    });\n  };\n\n  findSubcommands(commandKey);\n  return result;\n};\n\nexport interface GenerateCommandOptions {\n  selectedCommand?: Command | null;\n  useLongFlag?: boolean;\n}\n\nfunction getPreferredFlag(param: Parameter, useLongFlag: boolean): string | undefined {\n  return useLongFlag ? param.longFlag || param.shortFlag : param.shortFlag || param.longFlag;\n}\n\nexport function generateCommand(\n  tool: Tool,\n  parameterValues: Record<string, ParameterValue>,\n  options: GenerateCommandOptions = {},\n): string {\n  const hasCommands = tool.commands.length > 0;\n  const selectedCommand =\n    options.selectedCommand === undefined ? (tool.commands[0] ?? null) : options.selectedCommand;\n  const useLongFlag = options.useLongFlag ?? false;\n\n  let command = tool.binaryName;\n\n  if (hasCommands && selectedCommand) {\n    const commandPath = getCommandPath(selectedCommand, tool);\n    if (tool.binaryName !== commandPath) {\n      command = `${tool.binaryName} ${commandPath}`;\n    }\n  }\n\n  const parametersWithValues: Array<{\n    param: Parameter;\n    value: ParameterValue;\n  }> = [];\n  const globalParameters = tool.parameters?.filter((param) => param.isGlobal) ?? [];\n  const rootParameters =\n    hasCommands && selectedCommand\n      ? []\n      : (tool.parameters?.filter((param) => !param.commandKey && !param.isGlobal) ?? []);\n  const currentParameters = selectedCommand\n    ? (tool.parameters?.filter(\n        (param) => param.commandKey === selectedCommand.key && !param.isGlobal,\n      ) ?? [])\n    : [];\n\n  [...globalParameters, ...rootParameters, ...currentParameters].forEach((param) => {\n    const value = parameterValues[param.key];\n    if (value !== undefined && value !== \"\" && value !== false) {\n      parametersWithValues.push({ param, value });\n    }\n  });\n\n  const positionalParams = parametersWithValues\n    .filter(({ param }) => param.parameterType === \"Argument\")\n    .sort((a, b) => (a.param.position || 0) - (b.param.position || 0));\n\n  parametersWithValues.forEach(({ param, value }) => {\n    if (param.parameterType === \"Flag\") {\n      if (value === true) {\n        const flag = getPreferredFlag(param, useLongFlag);\n        if (flag) command += ` ${flag}`;\n      } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n        const flag = getPreferredFlag(param, useLongFlag);\n        if (flag) command += ` ${flag}`.repeat(value);\n      }\n      return;\n    }\n\n    if (param.parameterType === \"Option\") {\n      const flag = getPreferredFlag(param, useLongFlag);\n      if (!flag) return;\n\n      const separator = param.keyValueSeparator ?? \" \";\n      if (Array.isArray(value)) {\n        const entries = value.filter((entry) => entry !== \"\");\n        if (entries.length === 0) return;\n\n        if (param.arraySeparator) {\n          command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n          return;\n        }\n\n        entries.forEach((entry) => {\n          command += ` ${flag}${separator}${entry}`;\n        });\n        return;\n      }\n\n      command += ` ${flag}${separator}${value}`;\n    }\n  });\n\n  positionalParams.forEach(({ value }) => {\n    if (!Array.isArray(value)) {\n      command += ` ${value}`;\n    }\n  });\n\n  return command;\n}\n\nexport const exportToStructuredJSON = (tool: Tool) => {\n  return {\n    $schema: SCHEMA_URL,\n    name: tool.binaryName,\n    displayName: tool.displayName,\n    info: tool.info,\n    commands: tool.commands.map((cmd) => ({ ...cmd })),\n    parameters: tool.parameters.map(({ metadata: _metadata, ...param }) => param),\n    exclusionGroups: tool.exclusionGroups,\n    metadata: tool.metadata,\n  };\n};\n\nfunction isEmptyArray(value: unknown): boolean {\n  return Array.isArray(value) && value.length === 0;\n}\n\nfunction isEmptyObject(value: unknown): boolean {\n  return (\n    value != null &&\n    typeof value === \"object\" &&\n    !Array.isArray(value) &&\n    Object.keys(value).length === 0\n  );\n}\n\nfunction cleanParameter(param: Parameter): Parameter {\n  const cleaned = { ...param };\n\n  if (cleaned.isRequired === false) delete cleaned.isRequired;\n  if (cleaned.isRepeatable === false) delete cleaned.isRepeatable;\n  if (cleaned.isGlobal === false) delete cleaned.isGlobal;\n  if (cleaned.keyValueSeparator === \" \") delete cleaned.keyValueSeparator;\n  if (cleaned.arraySeparator === \",\" && cleaned.isRepeatable !== true)\n    delete cleaned.arraySeparator;\n\n  if (!cleaned.enum || isEmptyArray(cleaned.enum.values)) delete cleaned.enum;\n  if (isEmptyArray(cleaned.validations)) delete cleaned.validations;\n  if (isEmptyArray(cleaned.dependencies)) delete cleaned.dependencies;\n\n  if (cleaned.metadata) {\n    const meta = { ...cleaned.metadata } as ParameterMetadata;\n    if (isEmptyArray(meta.tags)) delete meta.tags;\n    if (isEmptyObject(meta)) {\n      delete cleaned.metadata;\n    } else {\n      cleaned.metadata = meta;\n    }\n  }\n\n  return cleaned;\n}\n\nfunction cleanCommand(cmd: Command): Command {\n  const cleaned = { ...cmd };\n  if (cleaned.interactive === false) delete cleaned.interactive;\n  return cleaned;\n}\n\nfunction cleanExclusionGroup(group: ExclusionGroup): ExclusionGroup {\n  return { ...group };\n}\n\nexport interface FixToolOptions {\n  addSchema?: boolean;\n  removeMetadata?: boolean;\n}\n\nexport function fixTool(tool: Tool, options?: FixToolOptions): Tool {\n  const addSchema = options?.addSchema ?? false;\n  const removeMetadata = options?.removeMetadata ?? false;\n\n  const cleaned: Record<string, unknown> = { ...tool };\n\n  if (addSchema) {\n    cleaned[\"$schema\"] = SCHEMA_URL;\n  }\n\n  if (cleaned.interactive === false) delete cleaned.interactive;\n  if (isEmptyObject(cleaned.metadata) || removeMetadata) delete cleaned.metadata;\n  if (isEmptyArray(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n\n  if (\"description\" in cleaned && cleaned.info == null) {\n    cleaned.info = { description: cleaned.description as string };\n    delete cleaned.description;\n  }\n\n  if (\"version\" in cleaned && typeof cleaned.version === \"string\") {\n    if (cleaned.info && typeof cleaned.info === \"object\") {\n      (cleaned.info as Record<string, unknown>).version = cleaned.version;\n    } else {\n      cleaned.info = { version: cleaned.version as string };\n    }\n    delete cleaned.version;\n  }\n\n  if (Array.isArray(cleaned.commands)) {\n    cleaned.commands = (cleaned.commands as Command[]).map(cleanCommand);\n  }\n\n  if (Array.isArray(cleaned.parameters)) {\n    cleaned.parameters = (cleaned.parameters as Parameter[]).map((p) => {\n      const fixed = cleanParameter(p);\n      if (removeMetadata) delete fixed.metadata;\n      return fixed;\n    });\n  }\n\n  if (\n    Array.isArray(cleaned.exclusionGroups) &&\n    (cleaned.exclusionGroups as ExclusionGroup[]).length > 0\n  ) {\n    cleaned.exclusionGroups = (cleaned.exclusionGroups as ExclusionGroup[]).map(\n      cleanExclusionGroup,\n    );\n  }\n\n  return cleaned as unknown as Tool;\n}\n",
      "type": "registry:file",
      "target": "components/commandly/utils/flat.ts"
    },
    {
      "path": "registry/commandly/utils/nested.ts",
      "content": "import type { Tool, Command, Parameter } from \"@/components/commandly/types/flat\";\nimport {\n  NestedCommand,\n  NestedExclusionGroup,\n  NestedParameter,\n  NestedTool,\n} from \"@/components/commandly/types/nested\";\n\nexport const convertToNestedStructure = (tool: Tool): NestedTool => {\n  const globalParameters = tool.parameters.filter((p) => p.isGlobal);\n\n  const convertParameter = (param: Parameter): NestedParameter => {\n    const { ...rest } = param;\n    return {\n      ...rest,\n      validations: param.validations?.map((v) => {\n        return {\n          validationType: v.validationType,\n          validationValue: v.validationValue,\n          errorMessage: v.errorMessage,\n        };\n      }),\n      metadata: param.metadata,\n      dataType: param.dataType,\n      dependencies: param.dependencies?.map((dep) => {\n        const dependsOnParam = tool.parameters.find((p) => p.key === dep.dependsOnParameterKey);\n        return {\n          dependsOnParameter: dependsOnParam?.longFlag || \"\",\n          dependencyType: dep.dependencyType,\n          conditionValue: dep.conditionValue,\n        };\n      }),\n    };\n  };\n\n  const buildNestedCommands = (commands: Command[], parentKey?: string): NestedCommand[] => {\n    return commands\n      .filter((cmd) => cmd.parentCommandKey === parentKey)\n      .map((cmd) => {\n        const commandParameters = tool.parameters.filter(\n          (p) => p.commandKey === cmd.key && !p.isGlobal,\n        );\n        const commandExclusionGroups = tool.exclusionGroups\n          ?.filter((g) => g.commandKey === cmd.key)\n          .map((group) => ({\n            name: group.name,\n            exclusionType: group.exclusionType,\n            parameters: group.parameterKeys.map((pk) => {\n              const param = tool.parameters.find((p) => p.key === pk);\n              return param?.longFlag || \"\";\n            }),\n          }));\n        return {\n          name: cmd.name,\n          description: cmd.description,\n          interactive: cmd.interactive,\n          sortOrder: cmd.sortOrder ?? 0,\n          parameters: commandParameters.map(convertParameter),\n          subcommands: buildNestedCommands(commands, cmd.key),\n          ...(commandExclusionGroups?.length ? { exclusionGroups: commandExclusionGroups } : {}),\n        };\n      });\n  };\n\n  const nestedExclusionGroups: NestedExclusionGroup[] | undefined = tool.exclusionGroups\n    ?.filter((g) => !g.commandKey)\n    .map((group) => ({\n      name: group.name,\n      exclusionType: group.exclusionType,\n      parameters: group.parameterKeys.map((pk) => {\n        const param = tool.parameters.find((p) => p.key === pk);\n        return param?.longFlag || \"\";\n      }),\n    }));\n\n  const rootParameters =\n    tool.commands.length === 0 ? tool.parameters.filter((p) => !p.commandKey && !p.isGlobal) : [];\n\n  return {\n    binaryName: tool.binaryName,\n    url: tool.info?.url,\n    displayName: tool.displayName,\n    interactive: tool.interactive,\n    info: tool.info,\n    metadata: tool.metadata,\n    rootParameters: rootParameters.map(convertParameter),\n    globalParameters: globalParameters.map(convertParameter),\n    commands: buildNestedCommands(tool.commands),\n    exclusionGroups: nestedExclusionGroups,\n  };\n};\n",
      "type": "registry:file",
      "target": "components/commandly/utils/nested.ts"
    }
  ],
  "type": "registry:block"
}