{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "json-output",
  "title": "JSON Output",
  "description": "A component for displaying formatted JSON output with syntax highlighting and copy functionality.",
  "dependencies": [
    "react"
  ],
  "registryDependencies": [
    "button",
    "scroll-area"
  ],
  "files": [
    {
      "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/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:component"
}