From 6a3a13c6537c3a0e95dff4b8d9bf2dcdecd40abd Mon Sep 17 00:00:00 2001 From: divyeshio <79130336+divyeshio@users.noreply.github.com> Date: Thu, 16 Apr 2026 02:30:52 +0530 Subject: [PATCH 1/5] feat: add exclusionGroups to NestedCommand and update related logic --- public/specification/nested.json | 7 +++- registry/commandly/types/nested.ts | 6 ++-- registry/commandly/utils/nested.ts | 33 +++++++++++------- src/components/tool-editor/command-tree.tsx | 2 +- .../tool-editor/dialogs/command-dialog.tsx | 2 -- tests/tool-editor/command-tree.test.tsx | 34 +++++++++++++++++++ .../dialogs/command-dialog.test.tsx | 17 ---------- 7 files changed, 65 insertions(+), 36 deletions(-) diff --git a/public/specification/nested.json b/public/specification/nested.json index e7361c1..4184c14 100644 --- a/public/specification/nested.json +++ b/public/specification/nested.json @@ -339,12 +339,17 @@ "items": { "$ref": "#/definitions/NestedCommand" } + }, + "exclusionGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/NestedExclusionGroup" + } } }, "required": [ "name", "parameters", - "sortOrder", "subcommands" ] }, diff --git a/registry/commandly/types/nested.ts b/registry/commandly/types/nested.ts index 4bd6b54..8ece0cd 100644 --- a/registry/commandly/types/nested.ts +++ b/registry/commandly/types/nested.ts @@ -75,12 +75,12 @@ export interface NestedCommand { /** Whether this command opens an interactive session or prompt. */ interactive?: boolean; /** Display sort position relative to sibling commands. */ - sortOrder: number; + sortOrder?: number; /** Parameters that belong directly to this command. */ parameters: NestedParameter[]; /** Nested subcommands of this command. */ - subcommands: NestedCommand[]; -} + subcommands: NestedCommand[]; /** Groups of parameters with mutual exclusion or required-one-of constraints scoped to this command. */ + exclusionGroups?: NestedExclusionGroup[];} export interface NestedExclusionGroup { /** Human-readable name for this exclusion group. */ diff --git a/registry/commandly/utils/nested.ts b/registry/commandly/utils/nested.ts index 9b9ce2e..a27e110 100644 --- a/registry/commandly/utils/nested.ts +++ b/registry/commandly/utils/nested.ts @@ -40,6 +40,16 @@ export const convertToNestedStructure = (tool: Tool): NestedTool => { const commandParameters = tool.parameters.filter( (p) => p.commandKey === cmd.key && !p.isGlobal, ); + const commandExclusionGroups = tool.exclusionGroups + ?.filter((g) => g.commandKey === cmd.key) + .map((group) => ({ + name: group.name, + exclusionType: group.exclusionType, + parameters: group.parameterKeys.map((pk) => { + const param = tool.parameters.find((p) => p.key === pk); + return param?.longFlag || ""; + }), + })); return { name: cmd.name, description: cmd.description, @@ -47,22 +57,21 @@ export const convertToNestedStructure = (tool: Tool): NestedTool => { sortOrder: cmd.sortOrder ?? 0, parameters: commandParameters.map(convertParameter), subcommands: buildNestedCommands(commands, cmd.key), + ...(commandExclusionGroups?.length ? { exclusionGroups: commandExclusionGroups } : {}), }; }); }; - const nestedExclusionGroups: NestedExclusionGroup[] | undefined = tool.exclusionGroups?.map( - (group) => { - return { - name: group.name, - exclusionType: group.exclusionType, - parameters: group.parameterKeys.map((pk) => { - const param = tool.parameters.find((p) => p.key === pk); - return param?.longFlag || ""; - }), - }; - }, - ); + const nestedExclusionGroups: NestedExclusionGroup[] | undefined = tool.exclusionGroups + ?.filter((g) => !g.commandKey) + .map((group) => ({ + name: group.name, + exclusionType: group.exclusionType, + parameters: group.parameterKeys.map((pk) => { + const param = tool.parameters.find((p) => p.key === pk); + return param?.longFlag || ""; + }), + })); return { $schema: "https://commandly.divyeshio.in/specification/nested.json", diff --git a/src/components/tool-editor/command-tree.tsx b/src/components/tool-editor/command-tree.tsx index 88a07ba..4dfcc1c 100644 --- a/src/components/tool-editor/command-tree.tsx +++ b/src/components/tool-editor/command-tree.tsx @@ -264,7 +264,7 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { { setIsDialogOpen(open); diff --git a/src/components/tool-editor/dialogs/command-dialog.tsx b/src/components/tool-editor/dialogs/command-dialog.tsx index e648364..c9839dc 100644 --- a/src/components/tool-editor/dialogs/command-dialog.tsx +++ b/src/components/tool-editor/dialogs/command-dialog.tsx @@ -30,7 +30,6 @@ export function CommandDialog({ onOpenChange, command, parentKey, - toolName, onSave, }: CommandDialogProps) { const isNewCommand = !command; @@ -65,7 +64,6 @@ export function CommandDialog({ setCommand((prev) => ({ ...prev, diff --git a/tests/tool-editor/command-tree.test.tsx b/tests/tool-editor/command-tree.test.tsx index f422d40..75f09e2 100644 --- a/tests/tool-editor/command-tree.test.tsx +++ b/tests/tool-editor/command-tree.test.tsx @@ -492,6 +492,40 @@ describe("CommandTree", () => { } }); + it("saves subcommand with correct parentCommandKey when added via + button", async () => { + renderWithProvider(, complexToolState()); + const initialCount = capturedCtx.tool.commands.length; + + const configElement = screen.getByText("config").closest("div"); + const buttons = Array.from(configElement?.querySelectorAll("button") || []); + const actionButtons = buttons.filter( + (btn) => + btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), + ); + const addButton = actionButtons[1]; + + if (addButton) { + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + }); + + const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + fireEvent.change(nameInput, { target: { value: "new-sub" } }); + + const saveButton = screen.getByRole("button", { name: "Add" }); + fireEvent.click(saveButton); + + await waitFor(() => { + expect(capturedCtx.tool.commands.length).toBe(initialCount + 1); + }); + + const newCmd = capturedCtx.tool.commands.find((c) => c.name === "new-sub"); + expect(newCmd).toBeDefined(); + expect(newCmd!.parentCommandKey).toBe("config"); + } + }); + it("removes commands from context when deleting", () => { renderWithProvider(, complexToolState()); const initialCommandCount = capturedCtx.tool.commands.length; diff --git a/tests/tool-editor/dialogs/command-dialog.test.tsx b/tests/tool-editor/dialogs/command-dialog.test.tsx index 342951e..1f30a6c 100644 --- a/tests/tool-editor/dialogs/command-dialog.test.tsx +++ b/tests/tool-editor/dialogs/command-dialog.test.tsx @@ -189,23 +189,6 @@ describe("CommandDialog - Form Fields", () => { expect((nameInput as HTMLInputElement).value).toBe("new-command"); }); - it("disables command name input when command name matches tool name", () => { - const command = createTestCommand({ name: "test-tool" }); - renderWithProvider( - , - createTestState(command, "test-tool"), - ); - - const nameInput = screen.getByLabelText("Command Name"); - expect(nameInput).toBeDisabled(); - }); - it("displays current sort order in the input", () => { const command = createTestCommand({ sortOrder: 5 }); renderWithProvider( From 130f1ec0a09ca21119f6a758ee2071fbd594a53b Mon Sep 17 00:00:00 2001 From: divyeshio <79130336+divyeshio@users.noreply.github.com> Date: Thu, 16 Apr 2026 23:12:22 +0530 Subject: [PATCH 2/5] refactor: update tool properties to use binaryName instead of name test: modify CommandDialog tests to remove sort order references test: enhance HelpMenu tests to handle missing command descriptions test: add drag and drop functionality tests for ParameterList test: ensure ToolEditor does not crash with undefined binaryName or displayName feat: implement applyMergePatch function with comprehensive tests chore: update vitest configuration for coverage reporting and exclusions Co-authored-by: Copilot --- .../skills/commandly-tool-generation/SKILL.md | 23 +- .../references/examples.md | 28 +- .../references/schema.md | 46 +- .github/workflows/pr.yml | 21 +- components.json | 3 +- mcp/index.ts | 2 +- public/r/generated-command.json | 10 +- public/r/json-output.json | 8 +- public/r/tool-renderer.json | 6 +- public/r/ui.json | 12 +- public/specification/flat.json | 6 +- public/specification/nested.json | 18 +- public/tools-collection/asnmap.json | 59 +- public/tools-collection/cdncheck.json | 64 +- public/tools-collection/curl.json | 823 ++++++----------- public/tools-collection/dnsx.json | 114 +-- public/tools-collection/gospider.json | 116 +-- public/tools-collection/httpx.json | 478 ++++------ public/tools-collection/katana.json | 259 ++---- public/tools-collection/mapcidr.json | 37 +- public/tools-collection/naabu.json | 229 ++--- public/tools-collection/notify.json | 29 +- public/tools-collection/nuclei.json | 568 ++++-------- public/tools-collection/shuffledns.json | 78 +- public/tools-collection/subfinder.json | 110 +-- public/tools-collection/urlfinder.json | 74 +- public/tools-collection/yt-dlp.json | 53 +- public/tools.json | 44 +- .../__tests__/generated-command.test.tsx | 141 ++- .../commandly/__tests__/json-output.test.tsx | 4 +- .../__tests__/tool-renderer.test.tsx | 154 +++- registry/commandly/generated-command.tsx | 48 +- registry/commandly/tool-renderer.tsx | 94 +- registry/commandly/types/flat.ts | 4 +- registry/commandly/types/nested.ts | 12 +- registry/commandly/utils/flat.ts | 5 +- registry/commandly/utils/nested.ts | 6 +- scripts/generate-tools-json.ts | 4 +- scripts/validate-tool-collection.ts | 22 +- .../docs/demos/generated-command-demo.tsx | 2 +- .../docs/demos/json-output-demo.tsx | 2 +- .../docs/demos/tool-renderer-demo.tsx | 2 +- src/components/tool-card.tsx | 10 +- src/components/tool-editor/ai-chat-store.ts | 4 +- src/components/tool-editor/ai-chat.tsx | 2 +- src/components/tool-editor/command-tree.tsx | 368 ++++---- .../tool-editor/dialogs/command-dialog.tsx | 77 +- .../dialogs/tool-details-dialog.tsx | 10 +- src/components/tool-editor/help-menu.tsx | 128 +-- src/components/tool-editor/parameter-list.tsx | 196 ++-- src/components/tool-editor/preview-tabs.tsx | 1 + .../tool-editor/tool-editor.context.tsx | 84 +- src/components/tool-editor/tool-editor.tsx | 32 +- src/components/tool-editor/tools.ts | 8 +- src/components/ui/file-tree.tsx | 538 +++++++++++ src/lib/utils.ts | 12 +- src/routes/tools/$toolName/edit.tsx | 8 +- src/routes/tools/$toolName/index.tsx | 190 ++-- src/routes/tools/index.tsx | 26 +- .../ai-chat-message-mapping.test.ts | 4 +- tests/tool-editor/command-tree.test.tsx | 838 ++++++++---------- .../dialogs/command-dialog.test.tsx | 63 +- .../dialogs/parameter-details-dialog.test.tsx | 2 +- tests/tool-editor/help-menu.test.tsx | 20 +- tests/tool-editor/parameter-list.test.tsx | 43 +- tests/tool-editor/tool-editor.test.tsx | 15 + tests/tool-editor/tools.test.ts | 54 ++ vitest.config.ts | 10 +- 68 files changed, 3245 insertions(+), 3316 deletions(-) create mode 100644 src/components/ui/file-tree.tsx create mode 100644 tests/tool-editor/tools.test.ts diff --git a/.agents/skills/commandly-tool-generation/SKILL.md b/.agents/skills/commandly-tool-generation/SKILL.md index fb2cbd2..33cf6bf 100644 --- a/.agents/skills/commandly-tool-generation/SKILL.md +++ b/.agents/skills/commandly-tool-generation/SKILL.md @@ -18,9 +18,9 @@ Generate and edit CLI tool definitions in the Commandly flat JSON schema format. ### Parsing Help Text 1. Identify the tool name and any description/version info → populate `name`, `displayName`, `info`. -2. Identify commands and subcommands → `commands[]` array with `key`, `name`, optional `parentCommandKey`. +2. Identify commands and subcommands → `commands[]` array with `key`, `name`, optional `parentCommandKey`. If the tool has no subcommands, leave `commands` as an empty array `[]`. 3. Map each flag/option/argument to a parameter → `parameters[]` array. -4. Assign `commandKey` to non-global parameters. +4. If commands exist, assign `commandKey` to non-global parameters. If no commands exist, parameters are **root parameters** — omit both `commandKey` and `isGlobal`. 5. Output pure JSON — no code fences, no comments. 6. Identify options which can take pre-defined values and create `Enum` parameters with `enum.values[]`. Note: "e.g." in help text does not necessarily mean the values are free-form — cross-check with documentation to determine if the full value set is known and fixed before using `Enum`. @@ -33,7 +33,7 @@ Generate and edit CLI tool definitions in the Commandly flat JSON schema format. ### Creating from Scratch 1. Use tool name as `name` (lowercase, hyphenated) and a display-friendly `displayName`. -2. Create at minimum one command (use the tool name if there are no subcommands, mark `isDefault: true`). +2. If the tool has subcommands, add them to `commands[]`. If it has no subcommands, use `commands: []`. 3. Map all known parameters following the type rules below. ## Parameter Type Rules @@ -53,15 +53,14 @@ Generate and edit CLI tool definitions in the Commandly flat JSON schema format. ## Key Rules 1. Every `key` must be unique across the entire `parameters[]` array. It should be meaningful and derived from the parameter name or description. -2. Non-global parameters **must** have `commandKey`. Global parameters **must not**. -3. `name` should be user-friendly title case (e.g. `--output-file` → `"Output File"`). -4. Descriptions in sentence case, trimmed. -5. Do not add `defaultValue` — it does not exist in the schema. -6. Do not add empty arrays/objects for optional properties (`validations`, `exclusionGroups`, `tags`, `dependencies`, `enum.values` when empty). -7. Tool description/version live under `info: { description, version, url }` — never at top level. `version` is **required** and must reflect the current release (no `v` prefix, e.g. `"1.9.0"` not `"v1.9.0"`). To find the latest version, call `GET https://api.github.com/repos/{owner}/{repo}/releases/latest` and use the `tag_name` field with the leading `v` stripped. For tools with non-standard tag formats (e.g. curl uses `curl-8_19_0`), use the release `name` field instead. For date-based versioning (e.g. yt-dlp uses `2026.03.17`), use `tag_name` as-is. -8. If only one command exists, do not mark all parameters as global. -9. There must always be at least one command. If no subcommand is found, create one with the tool name. -10. Output is pure JSON — no backticks, no trailing commas, proper indentation. +2. When `commands` is non-empty: non-global parameters **must** have `commandKey`, global parameters **must** have `isGlobal: true` and no `commandKey`. +3. When `commands` is empty: parameters are **root parameters** — they must **not** have `commandKey` or `isGlobal`. Do not create a dummy command matching the tool name. +4. `name` should be user-friendly title case (e.g. `--output-file` → `"Output File"`). +5. Descriptions in sentence case, trimmed. +6. Do not add `defaultValue` — it does not exist in the schema. +7. Do not add empty arrays/objects for optional properties (`validations`, `exclusionGroups`, `tags`, `dependencies`, `enum.values` when empty). +8. Tool description/version live under `info: { description, version, url }` — never at top level. `version` is **required** and must reflect the current release (no `v` prefix, e.g. `"1.9.0"` not `"v1.9.0"`). To find the latest version, call `GET https://api.github.com/repos/{owner}/{repo}/releases/latest` and use the `tag_name` field with the leading `v` stripped. For tools with non-standard tag formats (e.g. curl uses `curl-8_19_0`), use the release `name` field instead. For date-based versioning (e.g. yt-dlp uses `2026.03.17`), use `tag_name` as-is. +9. Output is pure JSON — no backticks, no trailing commas, proper indentation. ## Schema Reference diff --git a/.agents/skills/commandly-tool-generation/references/examples.md b/.agents/skills/commandly-tool-generation/references/examples.md index 9855cd3..9b444e0 100644 --- a/.agents/skills/commandly-tool-generation/references/examples.md +++ b/.agents/skills/commandly-tool-generation/references/examples.md @@ -1,8 +1,8 @@ # Commandly Tool JSON Examples -## 1. Simple single-command tool (curl) +## 1. Simple tool with no subcommands (curl) -Demonstrates: single default command, Flag / Option / Argument parameter types, `keyValueSeparator`. +Demonstrates: root parameters (no commands, no `commandKey`), Flag / Option / Argument parameter types, `keyValueSeparator`. ```json { @@ -13,15 +13,7 @@ Demonstrates: single default command, Flag / Option / Argument parameter types, "description": "curl is a command line tool and library for transferring data with URLs.", "url": "https://curl.se/" }, - "commands": [ - { - "key": "curl", - "name": "curl", - "description": "Run curl to download files.", - "isDefault": true, - "sortOrder": 1 - } - ], + "commands": [], "parameters": [ { "key": "target", @@ -31,8 +23,7 @@ Demonstrates: single default command, Flag / Option / Argument parameter types, "dataType": "String", "isRequired": true, "position": 1, - "sortOrder": 5, - "commandKey": "curl" + "sortOrder": 5 }, { "key": "output", @@ -43,8 +34,7 @@ Demonstrates: single default command, Flag / Option / Argument parameter types, "shortFlag": "-o", "longFlag": "--output", "keyValueSeparator": " ", - "sortOrder": 4, - "commandKey": "curl" + "sortOrder": 4 }, { "key": "location", @@ -54,8 +44,7 @@ Demonstrates: single default command, Flag / Option / Argument parameter types, "dataType": "Boolean", "shortFlag": "-L", "longFlag": "--location", - "sortOrder": 1, - "commandKey": "curl" + "sortOrder": 1 }, { "key": "silent", @@ -65,8 +54,7 @@ Demonstrates: single default command, Flag / Option / Argument parameter types, "dataType": "Boolean", "shortFlag": "-s", "longFlag": "--silent", - "sortOrder": 2, - "commandKey": "curl" + "sortOrder": 2 } ] } @@ -104,7 +92,7 @@ Demonstrates: `dataType: "Enum"`, `enum.values[]`, `isRepeatable`. } ] }, - "commandKey": "nuclei" + "sortOrder": 1 } ``` diff --git a/.agents/skills/commandly-tool-generation/references/schema.md b/.agents/skills/commandly-tool-generation/references/schema.md index e65022c..04300a5 100644 --- a/.agents/skills/commandly-tool-generation/references/schema.md +++ b/.agents/skills/commandly-tool-generation/references/schema.md @@ -7,7 +7,7 @@ | `name` | string | ✓ | Lowercase, hyphenated CLI name (e.g. `"curl"`) | | `displayName` | string | ✓ | Human-friendly title (e.g. `"Curl"`) | | `info` | ToolInfo | | Description, version, URL | -| `commands` | Command[] | ✓ | At least one required | +| `commands` | Command[] | ✓ | Can be empty for tools with no subcommands | | `parameters` | Parameter[] | ✓ | Can be empty array | | `exclusionGroups` | ExclusionGroup[] | | Omit if unused | | `metadata` | ToolMetadata | | Omit if unused | @@ -34,28 +34,28 @@ ## Parameter -| Field | Type | Required | Notes | -| ------------------- | --------------------- | -------- | --------------------------------------------------- | -| `key` | string | ✓ | Unique across all parameters | -| `name` | string | ✓ | User-friendly title case | -| `parameterType` | ParameterType | ✓ | `"Flag"` \| `"Option"` \| `"Argument"` | -| `dataType` | ParameterDataType | ✓ | `"Boolean"` \| `"String"` \| `"Number"` \| `"Enum"` | -| `commandKey` | string | | Required if not global; omit if global | -| `description` | string | | Sentence case | -| `group` | string | | Visual grouping label | -| `isRequired` | boolean | | | -| `isRepeatable` | boolean | | True if flag can appear multiple times | -| `isGlobal` | boolean | | True if applies to all commands | -| `shortFlag` | string | | e.g. `"-o"`. Omit if none. | -| `longFlag` | string | | e.g. `"--output"`. Preserve exact prefix. | -| `position` | number | | 1-based; only for `Argument` type | -| `sortOrder` | number | | Display order | -| `arraySeparator` | string | | For array-valued options | -| `keyValueSeparator` | string | | `" "` or `"="` | -| `enum` | ParameterEnumValues | | Required when `dataType` is `"Enum"` | -| `validations` | ParameterValidation[] | | Omit if unused | -| `dependencies` | ParameterDependency[] | | Omit if unused | -| `metadata` | ParameterMetadata | | Contains `tags[]` | +| Field | Type | Required | Notes | +| ------------------- | --------------------- | -------- | --------------------------------------------------------------------------------------------------------- | +| `key` | string | ✓ | Unique across all parameters | +| `name` | string | ✓ | User-friendly title case | +| `parameterType` | ParameterType | ✓ | `"Flag"` \| `"Option"` \| `"Argument"` | +| `dataType` | ParameterDataType | ✓ | `"Boolean"` \| `"String"` \| `"Number"` \| `"Enum"` | +| `commandKey` | string | | Required when commands exist and not global; omit for root parameters (no commands) and global parameters | +| `description` | string | | Sentence case | +| `group` | string | | Visual grouping label | +| `isRequired` | boolean | | | +| `isRepeatable` | boolean | | True if flag can appear multiple times | +| `isGlobal` | boolean | | True if applies to all commands; must not be set when commands is empty | +| `shortFlag` | string | | e.g. `"-o"`. Omit if none. | +| `longFlag` | string | | e.g. `"--output"`. Preserve exact prefix. | +| `position` | number | | 1-based; only for `Argument` type | +| `sortOrder` | number | | Display order | +| `arraySeparator` | string | | For array-valued options | +| `keyValueSeparator` | string | | `" "` or `"="` | +| `enum` | ParameterEnumValues | | Required when `dataType` is `"Enum"` | +| `validations` | ParameterValidation[] | | Omit if unused | +| `dependencies` | ParameterDependency[] | | Omit if unused | +| `metadata` | ParameterMetadata | | Contains `tags[]` | ## ParameterEnumValues diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5c94d1e..4dd1b61 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -83,4 +83,23 @@ jobs: fi - run: bun run build - - run: bun run test + - run: bun run coverage + + - name: Write coverage summary + if: always() + run: | + bun -e " + const s = require('./coverage/coverage-summary.json'); + const t = s.total; + const fmt = (m) => \`\${m.pct}% (\${m.covered}/\${m.total})\`; + const lines = [ + '## Test Coverage', + '| Metric | Coverage |', + '|--------|----------|', + \`| Statements | \${fmt(t.statements)} |\`, + \`| Branches | \${fmt(t.branches)} |\`, + \`| Functions | \${fmt(t.functions)} |\`, + \`| Lines | \${fmt(t.lines)} |\`, + ].join('\n'); + require('fs').appendFileSync(process.env.GITHUB_STEP_SUMMARY, lines + '\n'); + " diff --git a/components.json b/components.json index 4c284e8..08ab07b 100644 --- a/components.json +++ b/components.json @@ -23,6 +23,7 @@ }, "registries": { "@ai-elements": "https://ai-sdk.dev/elements/api/registry/{name}.json", - "@diceui": "https://diceui.com/r/{name}.json" + "@diceui": "https://diceui.com/r/{name}.json", + "@magicui": "https://magicui.design/r/{name}" } } diff --git a/mcp/index.ts b/mcp/index.ts index 5d56695..459ca28 100644 --- a/mcp/index.ts +++ b/mcp/index.ts @@ -59,7 +59,7 @@ server.registerTool( const tools = loadToolsFromCollection(); const toolsList = tools.map((tool) => ({ - name: tool.name, + binaryName: tool.binaryName, displayName: tool.displayName, description: tool.info?.description || "", metadata: tool.metadata, diff --git a/public/r/generated-command.json b/public/r/generated-command.json index 3cee7cd..a0f479f 100644 --- a/public/r/generated-command.json +++ b/public/r/generated-command.json @@ -14,31 +14,31 @@ "files": [ { "path": "registry/commandly/generated-command.tsx", - "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const currentParameters = useMemo(() => {\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\n if (!selectedCommand) return;\n const commandPath = getCommandPath(selectedCommand, tool);\n let command = tool.name == commandPath ? tool.name : `${tool.name} ${commandPath}`;\n\n const parametersWithValues: Array<{\n param: Parameter;\n value: ParameterValue;\n }> = [];\n\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [tool, parameterValues, selectedCommand, globalParameters, currentParameters]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {tool.commands.length === 0 ? (\n
\n \n

No commands available for this tool.

\n
\n ) : generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", + "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/generated-command.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 machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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", + "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 /** 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[];\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 /** Optional JSON schema URI for validation. */\n $schema?: string;\n /** Unique machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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 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", + "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 /** 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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\n};\n\nexport const exportToStructuredJSON = (tool: Tool) => {\n return {\n $schema: SCHEMA_URL,\n name: tool.name,\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\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 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 };\n });\n };\n\n const nestedExclusionGroups: NestedExclusionGroup[] | undefined = tool.exclusionGroups?.map(\n (group) => {\n return {\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 );\n\n return {\n $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n name: tool.name,\n url: tool.info?.url,\n displayName: tool.displayName,\n info: tool.info,\n metadata: tool.metadata,\n globalParameters: globalParameters.map(convertParameter),\n commands: buildNestedCommands(tool.commands),\n exclusionGroups: nestedExclusionGroups,\n };\n};\n", + "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 $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n binaryName: tool.binaryName,\n url: tool.info?.url,\n displayName: tool.displayName,\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" } diff --git a/public/r/json-output.json b/public/r/json-output.json index d7340ba..f084603 100644 --- a/public/r/json-output.json +++ b/public/r/json-output.json @@ -19,25 +19,25 @@ }, { "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 machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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", + "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 /** 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[];\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 /** Optional JSON schema URI for validation. */\n $schema?: string;\n /** Unique machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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 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", + "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 /** 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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\n};\n\nexport const exportToStructuredJSON = (tool: Tool) => {\n return {\n $schema: SCHEMA_URL,\n name: tool.name,\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\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 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 };\n });\n };\n\n const nestedExclusionGroups: NestedExclusionGroup[] | undefined = tool.exclusionGroups?.map(\n (group) => {\n return {\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 );\n\n return {\n $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n name: tool.name,\n url: tool.info?.url,\n displayName: tool.displayName,\n info: tool.info,\n metadata: tool.metadata,\n globalParameters: globalParameters.map(convertParameter),\n commands: buildNestedCommands(tool.commands),\n exclusionGroups: nestedExclusionGroups,\n };\n};\n", + "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 $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n binaryName: tool.binaryName,\n url: tool.info?.url,\n displayName: tool.displayName,\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" } diff --git a/public/r/tool-renderer.json b/public/r/tool-renderer.json index 04535ee..e79c2bf 100644 --- a/public/r/tool-renderer.json +++ b/public/r/tool-renderer.json @@ -14,7 +14,7 @@ "files": [ { "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 from \"react\";\n\nconst findDefaultCommand = (tool: Tool): Command | null => {\n const nameMatchCommand = tool.commands.find(\n (command) => command.name.toLowerCase() === tool.name.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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n \n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n\n return (\n \n {selectedCommand && tool.commands.length === 0 ? (\n

No commands available for this tool.

\n ) : (\n
\n {tool.parameters.length > 0 ? (\n tool.parameters\n .filter((param) => param.commandKey === selectedCommand?.key || param.isGlobal)\n .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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

\n No parameters available for this command.\n

\n )}\n
\n )}\n
\n );\n}\n", + "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n \n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n const hasCommands = tool.commands.length > 0;\n\n const visibleParameters = useMemo(() => {\n if (!hasCommands) {\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/tool-renderer.tsx" }, @@ -26,13 +26,13 @@ }, { "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 machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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", + "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 /** 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[];\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 /** Optional JSON schema URI for validation. */\n $schema?: string;\n /** Unique machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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 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", + "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 /** 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" } diff --git a/public/r/ui.json b/public/r/ui.json index f62eedd..d3315c2 100644 --- a/public/r/ui.json +++ b/public/r/ui.json @@ -23,7 +23,7 @@ "files": [ { "path": "registry/commandly/generated-command.tsx", - "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const currentParameters = useMemo(() => {\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\n if (!selectedCommand) return;\n const commandPath = getCommandPath(selectedCommand, tool);\n let command = tool.name == commandPath ? tool.name : `${tool.name} ${commandPath}`;\n\n const parametersWithValues: Array<{\n param: Parameter;\n value: ParameterValue;\n }> = [];\n\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [tool, parameterValues, selectedCommand, globalParameters, currentParameters]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {tool.commands.length === 0 ? (\n
\n \n

No commands available for this tool.

\n
\n ) : generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", + "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/generated-command.tsx" }, @@ -35,7 +35,7 @@ }, { "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 from \"react\";\n\nconst findDefaultCommand = (tool: Tool): Command | null => {\n const nameMatchCommand = tool.commands.find(\n (command) => command.name.toLowerCase() === tool.name.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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n \n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n\n return (\n \n {selectedCommand && tool.commands.length === 0 ? (\n

No commands available for this tool.

\n ) : (\n
\n {tool.parameters.length > 0 ? (\n tool.parameters\n .filter((param) => param.commandKey === selectedCommand?.key || param.isGlobal)\n .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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

\n No parameters available for this command.\n

\n )}\n
\n )}\n
\n );\n}\n", + "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n \n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n const hasCommands = tool.commands.length > 0;\n\n const visibleParameters = useMemo(() => {\n if (!hasCommands) {\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/tool-renderer.tsx" }, @@ -47,25 +47,25 @@ }, { "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 machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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", + "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 /** 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[];\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 /** Optional JSON schema URI for validation. */\n $schema?: string;\n /** Unique machine-readable identifier for the tool (e.g. \"httpx\"). */\n name: string;\n /** Human-readable display name for the tool (e.g. \"HTTPx\"). */\n displayName: string;\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 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", + "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 /** 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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\n};\n\nexport const exportToStructuredJSON = (tool: Tool) => {\n return {\n $schema: SCHEMA_URL,\n name: tool.name,\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\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 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 };\n });\n };\n\n const nestedExclusionGroups: NestedExclusionGroup[] | undefined = tool.exclusionGroups?.map(\n (group) => {\n return {\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 );\n\n return {\n $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n name: tool.name,\n url: tool.info?.url,\n displayName: tool.displayName,\n info: tool.info,\n metadata: tool.metadata,\n globalParameters: globalParameters.map(convertParameter),\n commands: buildNestedCommands(tool.commands),\n exclusionGroups: nestedExclusionGroups,\n };\n};\n", + "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 $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n binaryName: tool.binaryName,\n url: tool.info?.url,\n displayName: tool.displayName,\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" } diff --git a/public/specification/flat.json b/public/specification/flat.json index e847c65..853c9c8 100644 --- a/public/specification/flat.json +++ b/public/specification/flat.json @@ -1,8 +1,8 @@ { "type": "object", "properties": { - "name": { - "description": "Unique machine-readable identifier for the tool (e.g. \"httpx\").", + "binaryName": { + "description": "Unique binary name for the tool that it can be invoked from the command line (e.g. \"httpx\").", "type": "string" }, "displayName": { @@ -40,9 +40,9 @@ } }, "required": [ + "binaryName", "commands", "displayName", - "name", "parameters" ], "definitions": { diff --git a/public/specification/nested.json b/public/specification/nested.json index 4184c14..5ca6261 100644 --- a/public/specification/nested.json +++ b/public/specification/nested.json @@ -1,12 +1,8 @@ { "type": "object", "properties": { - "$schema": { - "description": "Optional JSON schema URI for validation.", - "type": "string" - }, - "name": { - "description": "Unique machine-readable identifier for the tool (e.g. \"httpx\").", + "binaryName": { + "description": "Unique binary name for the tool that it can be invoked from the command line (e.g. \"httpx\").", "type": "string" }, "displayName": { @@ -21,6 +17,13 @@ "description": "The homepage or documentation URL for the tool.", "type": "string" }, + "rootParameters": { + "description": "Parameters that belong to the root invocation when no commands exist.", + "type": "array", + "items": { + "$ref": "#/definitions/NestedParameter" + } + }, "globalParameters": { "description": "Parameters that apply to all commands globally.", "type": "array", @@ -55,10 +58,11 @@ } }, "required": [ + "binaryName", "commands", "displayName", "globalParameters", - "name" + "rootParameters" ], "definitions": { "ToolInfo": { diff --git a/public/tools-collection/asnmap.json b/public/tools-collection/asnmap.json index 3add69a..f8ac3db 100644 --- a/public/tools-collection/asnmap.json +++ b/public/tools-collection/asnmap.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "asnmap", + "binaryName": "asnmap", "displayName": "ASNMap", "info": { "description": "Go CLI and Library for quickly mapping organization network ranges using ASN information.", "version": "1.1.1", "url": "https://github.com/projectdiscovery/asnmap" }, - "commands": [ - { - "name": "asnmap", - "description": "Map organization network ranges using ASN information.", - "sortOrder": 1, - "key": "asnmap" - } - ], + "commands": [], "parameters": [ { "name": "ASN", @@ -27,8 +20,7 @@ "sortOrder": 1, "arraySeparator": ",", "keyValueSeparator": " ", - "key": "asn", - "commandKey": "asnmap" + "key": "asn" }, { "name": "IP", @@ -41,8 +33,7 @@ "sortOrder": 2, "arraySeparator": ",", "keyValueSeparator": " ", - "key": "ip", - "commandKey": "asnmap" + "key": "ip" }, { "name": "Domain", @@ -55,8 +46,7 @@ "sortOrder": 3, "arraySeparator": ",", "keyValueSeparator": " ", - "key": "domain", - "commandKey": "asnmap" + "key": "domain" }, { "name": "Org", @@ -68,8 +58,7 @@ "sortOrder": 4, "arraySeparator": ",", "keyValueSeparator": " ", - "key": "org", - "commandKey": "asnmap" + "key": "org" }, { "name": "File", @@ -82,8 +71,7 @@ "sortOrder": 5, "arraySeparator": ",", "keyValueSeparator": " ", - "key": "file", - "commandKey": "asnmap" + "key": "file" }, { "name": "Config", @@ -93,8 +81,7 @@ "longFlag": "-config", "sortOrder": 6, "keyValueSeparator": " ", - "key": "config", - "commandKey": "asnmap" + "key": "config" }, { "name": "Resolvers", @@ -107,8 +94,7 @@ "sortOrder": 7, "arraySeparator": ",", "keyValueSeparator": " ", - "key": "resolvers", - "commandKey": "asnmap" + "key": "resolvers" }, { "name": "Update", @@ -118,8 +104,7 @@ "shortFlag": "-up", "longFlag": "-update", "sortOrder": 8, - "key": "update", - "commandKey": "asnmap" + "key": "update" }, { "name": "Disable Update Check", @@ -129,8 +114,7 @@ "shortFlag": "-duc", "longFlag": "-disable-update-check", "sortOrder": 9, - "key": "disable-update-check", - "commandKey": "asnmap" + "key": "disable-update-check" }, { "name": "Output File", @@ -141,8 +125,7 @@ "longFlag": "-output", "sortOrder": 10, "keyValueSeparator": " ", - "key": "output", - "commandKey": "asnmap" + "key": "output" }, { "name": "JSON Output", @@ -152,8 +135,7 @@ "shortFlag": "-j", "longFlag": "-json", "sortOrder": 11, - "key": "json", - "commandKey": "asnmap" + "key": "json" }, { "name": "CSV Output", @@ -163,8 +145,7 @@ "shortFlag": "-c", "longFlag": "-csv", "sortOrder": 12, - "key": "csv", - "commandKey": "asnmap" + "key": "csv" }, { "name": "IPv6 Output", @@ -173,8 +154,7 @@ "dataType": "Boolean", "longFlag": "-v6", "sortOrder": 13, - "key": "v6", - "commandKey": "asnmap" + "key": "v6" }, { "name": "Verbose Output", @@ -184,8 +164,7 @@ "shortFlag": "-v", "longFlag": "-verbose", "sortOrder": 14, - "key": "verbose", - "commandKey": "asnmap" + "key": "verbose" }, { "name": "Silent", @@ -194,8 +173,7 @@ "dataType": "Boolean", "longFlag": "-silent", "sortOrder": 15, - "key": "silent", - "commandKey": "asnmap" + "key": "silent" }, { "name": "Version", @@ -204,8 +182,7 @@ "dataType": "Boolean", "longFlag": "-version", "sortOrder": 16, - "key": "version", - "commandKey": "asnmap" + "key": "version" } ] } diff --git a/public/tools-collection/cdncheck.json b/public/tools-collection/cdncheck.json index c94b5e6..eb6818b 100644 --- a/public/tools-collection/cdncheck.json +++ b/public/tools-collection/cdncheck.json @@ -1,19 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "cdncheck", + "binaryName": "cdncheck", "displayName": "CDNCheck", "info": { "description": "cdncheck is a tool for identifying the technology associated with dns / ip network addresses.", "version": "1.2.27", "url": "https://github.com/projectdiscovery/cdncheck" }, - "commands": [ - { - "name": "cdncheck", - "sortOrder": 1, - "key": "cdncheck" - } - ], + "commands": [], "parameters": [ { "name": "Input", @@ -24,8 +18,7 @@ "shortFlag": "-i", "longFlag": "-input", "sortOrder": 1, - "key": "input", - "commandKey": "cdncheck" + "key": "input" }, { "name": "CDN", @@ -34,8 +27,7 @@ "dataType": "Boolean", "longFlag": "-cdn", "sortOrder": 2, - "key": "cdn", - "commandKey": "cdncheck" + "key": "cdn" }, { "name": "Cloud", @@ -44,8 +36,7 @@ "dataType": "Boolean", "longFlag": "-cloud", "sortOrder": 3, - "key": "cloud", - "commandKey": "cdncheck" + "key": "cloud" }, { "name": "WAF", @@ -54,8 +45,7 @@ "dataType": "Boolean", "longFlag": "-waf", "sortOrder": 4, - "key": "waf", - "commandKey": "cdncheck" + "key": "waf" }, { "name": "Match CDN", @@ -67,7 +57,6 @@ "longFlag": "-match-cdn", "sortOrder": 5, "key": "match-cdn", - "commandKey": "cdncheck", "enum": { "allowMultiple": true, "values": [ @@ -100,7 +89,6 @@ "longFlag": "-match-cloud", "sortOrder": 6, "key": "match-cloud", - "commandKey": "cdncheck", "enum": { "values": [ { @@ -128,7 +116,6 @@ "longFlag": "-match-waf", "sortOrder": 7, "key": "match-waf", - "commandKey": "cdncheck", "enum": { "values": [ { @@ -160,7 +147,6 @@ "longFlag": "-filter-cdn", "sortOrder": 8, "key": "filter-cdn", - "commandKey": "cdncheck", "enum": { "allowMultiple": true, "values": [ @@ -193,7 +179,6 @@ "longFlag": "-filter-cloud", "sortOrder": 9, "key": "filter-cloud", - "commandKey": "cdncheck", "enum": { "allowMultiple": true, "values": [ @@ -222,7 +207,6 @@ "longFlag": "-filter-waf", "sortOrder": 10, "key": "filter-waf", - "commandKey": "cdncheck", "enum": { "values": [ { @@ -251,8 +235,7 @@ "dataType": "Boolean", "longFlag": "-resp", "sortOrder": 11, - "key": "resp", - "commandKey": "cdncheck" + "key": "resp" }, { "name": "Output", @@ -262,8 +245,7 @@ "shortFlag": "-o", "longFlag": "-output", "sortOrder": 12, - "key": "output", - "commandKey": "cdncheck" + "key": "output" }, { "name": "Verbose", @@ -273,8 +255,7 @@ "shortFlag": "-v", "longFlag": "-verbose", "sortOrder": 13, - "key": "verbose", - "commandKey": "cdncheck" + "key": "verbose" }, { "name": "JSONL Output", @@ -283,8 +264,7 @@ "dataType": "Boolean", "longFlag": "-jsonl", "sortOrder": 14, - "key": "jsonl", - "commandKey": "cdncheck" + "key": "jsonl" }, { "name": "No Color", @@ -294,8 +274,7 @@ "shortFlag": "-nc", "longFlag": "-no-color", "sortOrder": 15, - "key": "no-color", - "commandKey": "cdncheck" + "key": "no-color" }, { "name": "Version", @@ -304,8 +283,7 @@ "dataType": "Boolean", "longFlag": "-version", "sortOrder": 16, - "key": "version", - "commandKey": "cdncheck" + "key": "version" }, { "name": "Silent", @@ -314,8 +292,7 @@ "dataType": "Boolean", "longFlag": "-silent", "sortOrder": 17, - "key": "silent", - "commandKey": "cdncheck" + "key": "silent" }, { "name": "Resolver", @@ -326,8 +303,7 @@ "shortFlag": "-r", "longFlag": "-resolver", "sortOrder": 18, - "key": "resolver", - "commandKey": "cdncheck" + "key": "resolver" }, { "name": "Exclude", @@ -337,8 +313,7 @@ "shortFlag": "-e", "longFlag": "-exclude", "sortOrder": 19, - "key": "exclude", - "commandKey": "cdncheck" + "key": "exclude" }, { "name": "Retry", @@ -347,8 +322,7 @@ "dataType": "Number", "longFlag": "-retry", "sortOrder": 20, - "key": "retry", - "commandKey": "cdncheck" + "key": "retry" }, { "name": "Update", @@ -358,8 +332,7 @@ "shortFlag": "-up", "longFlag": "-update", "sortOrder": 21, - "key": "update", - "commandKey": "cdncheck" + "key": "update" }, { "name": "Disable Update Check", @@ -369,8 +342,7 @@ "shortFlag": "-duc", "longFlag": "-disable-update-check", "sortOrder": 22, - "key": "disable-update-check", - "commandKey": "cdncheck" + "key": "disable-update-check" } ], "exclusionGroups": [ diff --git a/public/tools-collection/curl.json b/public/tools-collection/curl.json index 8948d85..8452fca 100644 --- a/public/tools-collection/curl.json +++ b/public/tools-collection/curl.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "curl", + "binaryName": "curl", "displayName": "Curl", "info": { "description": "curl is a command line tool and library for transferring data with URLs.", "version": "8.19.0", "url": "https://curl.se/" }, - "commands": [ - { - "name": "curl", - "description": "Run curl to download files.", - "sortOrder": 1, - "key": "curl" - } - ], + "commands": [], "parameters": [ { "name": "Target", @@ -24,8 +17,7 @@ "isRequired": true, "sortOrder": 5, "position": 1, - "key": "target", - "commandKey": "curl" + "key": "target" }, { "name": "Output", @@ -36,8 +28,7 @@ "longFlag": "--output", "keyValueSeparator": " ", "sortOrder": 4, - "key": "output", - "commandKey": "curl" + "key": "output" }, { "name": "Location", @@ -47,8 +38,7 @@ "shortFlag": "-L", "longFlag": "--location", "sortOrder": 1, - "key": "location", - "commandKey": "curl" + "key": "location" }, { "name": "Silent", @@ -58,8 +48,7 @@ "shortFlag": "-s", "longFlag": "--silent", "sortOrder": 2, - "key": "silent", - "commandKey": "curl" + "key": "silent" }, { "name": "Insecure", @@ -69,8 +58,7 @@ "shortFlag": "-k", "longFlag": "--insecure", "sortOrder": 3, - "key": "insecure", - "commandKey": "curl" + "key": "insecure" }, { "name": "Config", @@ -81,8 +69,7 @@ "longFlag": "--config", "keyValueSeparator": " ", "sortOrder": 6, - "key": "config", - "commandKey": "curl" + "key": "config" }, { "name": "Dump Header", @@ -93,8 +80,7 @@ "longFlag": "--dump-header", "keyValueSeparator": " ", "sortOrder": 7, - "key": "dump-header", - "commandKey": "curl" + "key": "dump-header" }, { "name": "Request", @@ -105,8 +91,7 @@ "longFlag": "--request", "keyValueSeparator": " ", "sortOrder": 8, - "key": "request", - "commandKey": "curl" + "key": "request" }, { "name": "Header", @@ -118,8 +103,7 @@ "longFlag": "--header", "keyValueSeparator": " ", "sortOrder": 9, - "key": "header", - "commandKey": "curl" + "key": "header" }, { "name": "Data", @@ -131,8 +115,7 @@ "longFlag": "--data", "keyValueSeparator": " ", "sortOrder": 10, - "key": "data", - "commandKey": "curl" + "key": "data" }, { "name": "User", @@ -143,8 +126,7 @@ "longFlag": "--user", "keyValueSeparator": " ", "sortOrder": 11, - "key": "user", - "commandKey": "curl" + "key": "user" }, { "name": "User Agent", @@ -155,8 +137,7 @@ "longFlag": "--user-agent", "keyValueSeparator": " ", "sortOrder": 12, - "key": "user-agent", - "commandKey": "curl" + "key": "user-agent" }, { "name": "Verbose", @@ -166,8 +147,7 @@ "shortFlag": "-v", "longFlag": "--verbose", "sortOrder": 13, - "key": "verbose", - "commandKey": "curl" + "key": "verbose" }, { "name": "Fail", @@ -177,8 +157,7 @@ "shortFlag": "-f", "longFlag": "--fail", "sortOrder": 14, - "key": "fail", - "commandKey": "curl" + "key": "fail" }, { "name": "Head", @@ -188,8 +167,7 @@ "shortFlag": "-I", "longFlag": "--head", "sortOrder": 15, - "key": "head", - "commandKey": "curl" + "key": "head" }, { "name": "Get", @@ -199,8 +177,7 @@ "shortFlag": "-G", "longFlag": "--get", "sortOrder": 16, - "key": "get", - "commandKey": "curl" + "key": "get" }, { "name": "Form", @@ -212,8 +189,7 @@ "longFlag": "--form", "keyValueSeparator": " ", "sortOrder": 17, - "key": "form", - "commandKey": "curl" + "key": "form" }, { "name": "Proxy", @@ -224,8 +200,7 @@ "longFlag": "--proxy", "keyValueSeparator": " ", "sortOrder": 18, - "key": "proxy", - "commandKey": "curl" + "key": "proxy" }, { "name": "Max Time", @@ -236,8 +211,7 @@ "longFlag": "--max-time", "keyValueSeparator": " ", "sortOrder": 19, - "key": "max-time", - "commandKey": "curl" + "key": "max-time" }, { "name": "Connect Timeout", @@ -247,8 +221,7 @@ "longFlag": "--connect-timeout", "keyValueSeparator": " ", "sortOrder": 20, - "key": "connect-timeout", - "commandKey": "curl" + "key": "connect-timeout" }, { "name": "Retry", @@ -258,8 +231,7 @@ "longFlag": "--retry", "keyValueSeparator": " ", "sortOrder": 21, - "key": "retry", - "commandKey": "curl" + "key": "retry" }, { "name": "Output Directory", @@ -269,8 +241,7 @@ "longFlag": "--output-dir", "keyValueSeparator": " ", "sortOrder": 22, - "key": "output-dir", - "commandKey": "curl" + "key": "output-dir" }, { "name": "Remote Name", @@ -280,8 +251,7 @@ "shortFlag": "-O", "longFlag": "--remote-name", "sortOrder": 23, - "key": "remote-name", - "commandKey": "curl" + "key": "remote-name" }, { "name": "Show Headers", @@ -291,8 +261,7 @@ "shortFlag": "-i", "longFlag": "--show-headers", "sortOrder": 24, - "key": "show-headers", - "commandKey": "curl" + "key": "show-headers" }, { "name": "Help", @@ -303,8 +272,7 @@ "longFlag": "--help", "keyValueSeparator": " ", "sortOrder": 25, - "key": "help", - "commandKey": "curl" + "key": "help" }, { "name": "Version", @@ -314,8 +282,7 @@ "shortFlag": "-V", "longFlag": "--version", "sortOrder": 26, - "key": "version", - "commandKey": "curl" + "key": "version" }, { "name": "URL", @@ -326,8 +293,7 @@ "longFlag": "--url", "keyValueSeparator": " ", "sortOrder": 27, - "key": "url", - "commandKey": "curl" + "key": "url" }, { "name": "Upload File", @@ -338,8 +304,7 @@ "longFlag": "--upload-file", "keyValueSeparator": " ", "sortOrder": 28, - "key": "upload-file", - "commandKey": "curl" + "key": "upload-file" }, { "name": "Abstract Unix Socket", @@ -349,8 +314,7 @@ "longFlag": "--abstract-unix-socket", "keyValueSeparator": " ", "sortOrder": 29, - "key": "abstract-unix-socket", - "commandKey": "curl" + "key": "abstract-unix-socket" }, { "name": "Alt-Svc", @@ -360,8 +324,7 @@ "longFlag": "--alt-svc", "keyValueSeparator": " ", "sortOrder": 30, - "key": "alt-svc", - "commandKey": "curl" + "key": "alt-svc" }, { "name": "Anyauth", @@ -370,8 +333,7 @@ "dataType": "Boolean", "longFlag": "--anyauth", "sortOrder": 31, - "key": "anyauth", - "commandKey": "curl" + "key": "anyauth" }, { "name": "Append", @@ -381,8 +343,7 @@ "shortFlag": "-a", "longFlag": "--append", "sortOrder": 32, - "key": "append", - "commandKey": "curl" + "key": "append" }, { "name": "AWS SigV4", @@ -392,8 +353,7 @@ "longFlag": "--aws-sigv4", "keyValueSeparator": " ", "sortOrder": 33, - "key": "aws-sigv4", - "commandKey": "curl" + "key": "aws-sigv4" }, { "name": "Basic", @@ -402,8 +362,7 @@ "dataType": "Boolean", "longFlag": "--basic", "sortOrder": 34, - "key": "basic", - "commandKey": "curl" + "key": "basic" }, { "name": "CA Native", @@ -412,8 +371,7 @@ "dataType": "Boolean", "longFlag": "--ca-native", "sortOrder": 35, - "key": "ca-native", - "commandKey": "curl" + "key": "ca-native" }, { "name": "Cacert", @@ -423,8 +381,7 @@ "longFlag": "--cacert", "keyValueSeparator": " ", "sortOrder": 36, - "key": "cacert", - "commandKey": "curl" + "key": "cacert" }, { "name": "Capath", @@ -434,8 +391,7 @@ "longFlag": "--capath", "keyValueSeparator": " ", "sortOrder": 37, - "key": "capath", - "commandKey": "curl" + "key": "capath" }, { "name": "Cert", @@ -446,8 +402,7 @@ "longFlag": "--cert", "keyValueSeparator": " ", "sortOrder": 38, - "key": "cert", - "commandKey": "curl" + "key": "cert" }, { "name": "Cert Status", @@ -456,8 +411,7 @@ "dataType": "Boolean", "longFlag": "--cert-status", "sortOrder": 39, - "key": "cert-status", - "commandKey": "curl" + "key": "cert-status" }, { "name": "Cert Type", @@ -467,8 +421,7 @@ "longFlag": "--cert-type", "keyValueSeparator": " ", "sortOrder": 40, - "key": "cert-type", - "commandKey": "curl" + "key": "cert-type" }, { "name": "Ciphers", @@ -478,8 +431,7 @@ "longFlag": "--ciphers", "keyValueSeparator": " ", "sortOrder": 41, - "key": "ciphers", - "commandKey": "curl" + "key": "ciphers" }, { "name": "Compressed", @@ -488,8 +440,7 @@ "dataType": "Boolean", "longFlag": "--compressed", "sortOrder": 42, - "key": "compressed", - "commandKey": "curl" + "key": "compressed" }, { "name": "Compressed SSH", @@ -498,8 +449,7 @@ "dataType": "Boolean", "longFlag": "--compressed-ssh", "sortOrder": 43, - "key": "compressed-ssh", - "commandKey": "curl" + "key": "compressed-ssh" }, { "name": "Connect To", @@ -509,8 +459,7 @@ "longFlag": "--connect-to", "keyValueSeparator": " ", "sortOrder": 44, - "key": "connect-to", - "commandKey": "curl" + "key": "connect-to" }, { "name": "Continue At", @@ -521,8 +470,7 @@ "longFlag": "--continue-at", "keyValueSeparator": " ", "sortOrder": 45, - "key": "continue-at", - "commandKey": "curl" + "key": "continue-at" }, { "name": "Cookie", @@ -533,8 +481,7 @@ "longFlag": "--cookie", "keyValueSeparator": " ", "sortOrder": 46, - "key": "cookie", - "commandKey": "curl" + "key": "cookie" }, { "name": "Cookie Jar", @@ -545,8 +492,7 @@ "longFlag": "--cookie-jar", "keyValueSeparator": " ", "sortOrder": 47, - "key": "cookie-jar", - "commandKey": "curl" + "key": "cookie-jar" }, { "name": "Create Dirs", @@ -555,8 +501,7 @@ "dataType": "Boolean", "longFlag": "--create-dirs", "sortOrder": 48, - "key": "create-dirs", - "commandKey": "curl" + "key": "create-dirs" }, { "name": "Create File Mode", @@ -566,8 +511,7 @@ "longFlag": "--create-file-mode", "keyValueSeparator": " ", "sortOrder": 49, - "key": "create-file-mode", - "commandKey": "curl" + "key": "create-file-mode" }, { "name": "CRLF", @@ -576,8 +520,7 @@ "dataType": "Boolean", "longFlag": "--crlf", "sortOrder": 50, - "key": "crlf", - "commandKey": "curl" + "key": "crlf" }, { "name": "CRL File", @@ -587,8 +530,7 @@ "longFlag": "--crlfile", "keyValueSeparator": " ", "sortOrder": 51, - "key": "crlfile", - "commandKey": "curl" + "key": "crlfile" }, { "name": "Curves", @@ -598,8 +540,7 @@ "longFlag": "--curves", "keyValueSeparator": " ", "sortOrder": 52, - "key": "curves", - "commandKey": "curl" + "key": "curves" }, { "name": "Data ASCII", @@ -610,8 +551,7 @@ "longFlag": "--data-ascii", "keyValueSeparator": " ", "sortOrder": 53, - "key": "data-ascii", - "commandKey": "curl" + "key": "data-ascii" }, { "name": "Data Binary", @@ -622,8 +562,7 @@ "longFlag": "--data-binary", "keyValueSeparator": " ", "sortOrder": 54, - "key": "data-binary", - "commandKey": "curl" + "key": "data-binary" }, { "name": "Data Raw", @@ -634,8 +573,7 @@ "longFlag": "--data-raw", "keyValueSeparator": " ", "sortOrder": 55, - "key": "data-raw", - "commandKey": "curl" + "key": "data-raw" }, { "name": "Data URL Encode", @@ -646,8 +584,7 @@ "longFlag": "--data-urlencode", "keyValueSeparator": " ", "sortOrder": 56, - "key": "data-urlencode", - "commandKey": "curl" + "key": "data-urlencode" }, { "name": "Delegation", @@ -657,8 +594,7 @@ "longFlag": "--delegation", "keyValueSeparator": " ", "sortOrder": 57, - "key": "delegation", - "commandKey": "curl" + "key": "delegation" }, { "name": "Digest", @@ -667,8 +603,7 @@ "dataType": "Boolean", "longFlag": "--digest", "sortOrder": 58, - "key": "digest", - "commandKey": "curl" + "key": "digest" }, { "name": "Disable", @@ -678,8 +613,7 @@ "shortFlag": "-q", "longFlag": "--disable", "sortOrder": 59, - "key": "disable", - "commandKey": "curl" + "key": "disable" }, { "name": "Disable EPRT", @@ -688,8 +622,7 @@ "dataType": "Boolean", "longFlag": "--disable-eprt", "sortOrder": 60, - "key": "disable-eprt", - "commandKey": "curl" + "key": "disable-eprt" }, { "name": "Disable EPSV", @@ -698,8 +631,7 @@ "dataType": "Boolean", "longFlag": "--disable-epsv", "sortOrder": 61, - "key": "disable-epsv", - "commandKey": "curl" + "key": "disable-epsv" }, { "name": "Disallow Username in URL", @@ -708,8 +640,7 @@ "dataType": "Boolean", "longFlag": "--disallow-username-in-url", "sortOrder": 62, - "key": "disallow-username-in-url", - "commandKey": "curl" + "key": "disallow-username-in-url" }, { "name": "DNS Interface", @@ -719,8 +650,7 @@ "longFlag": "--dns-interface", "keyValueSeparator": " ", "sortOrder": 63, - "key": "dns-interface", - "commandKey": "curl" + "key": "dns-interface" }, { "name": "DNS IPv4 Address", @@ -730,8 +660,7 @@ "longFlag": "--dns-ipv4-addr", "keyValueSeparator": " ", "sortOrder": 64, - "key": "dns-ipv4-addr", - "commandKey": "curl" + "key": "dns-ipv4-addr" }, { "name": "DNS IPv6 Address", @@ -741,8 +670,7 @@ "longFlag": "--dns-ipv6-addr", "keyValueSeparator": " ", "sortOrder": 65, - "key": "dns-ipv6-addr", - "commandKey": "curl" + "key": "dns-ipv6-addr" }, { "name": "DNS Servers", @@ -752,8 +680,7 @@ "longFlag": "--dns-servers", "keyValueSeparator": " ", "sortOrder": 66, - "key": "dns-servers", - "commandKey": "curl" + "key": "dns-servers" }, { "name": "DoH Cert Status", @@ -762,8 +689,7 @@ "dataType": "Boolean", "longFlag": "--doh-cert-status", "sortOrder": 67, - "key": "doh-cert-status", - "commandKey": "curl" + "key": "doh-cert-status" }, { "name": "DoH Insecure", @@ -772,8 +698,7 @@ "dataType": "Boolean", "longFlag": "--doh-insecure", "sortOrder": 68, - "key": "doh-insecure", - "commandKey": "curl" + "key": "doh-insecure" }, { "name": "DoH URL", @@ -783,8 +708,7 @@ "longFlag": "--doh-url", "keyValueSeparator": " ", "sortOrder": 69, - "key": "doh-url", - "commandKey": "curl" + "key": "doh-url" }, { "name": "Dump CA Embed", @@ -793,8 +717,7 @@ "dataType": "Boolean", "longFlag": "--dump-ca-embed", "sortOrder": 70, - "key": "dump-ca-embed", - "commandKey": "curl" + "key": "dump-ca-embed" }, { "name": "Ech", @@ -804,8 +727,7 @@ "longFlag": "--ech", "keyValueSeparator": " ", "sortOrder": 71, - "key": "ech", - "commandKey": "curl" + "key": "ech" }, { "name": "EGD File", @@ -815,8 +737,7 @@ "longFlag": "--egd-file", "keyValueSeparator": " ", "sortOrder": 72, - "key": "egd-file", - "commandKey": "curl" + "key": "egd-file" }, { "name": "Engine", @@ -826,8 +747,7 @@ "longFlag": "--engine", "keyValueSeparator": " ", "sortOrder": 73, - "key": "engine", - "commandKey": "curl" + "key": "engine" }, { "name": "ETag Compare", @@ -837,8 +757,7 @@ "longFlag": "--etag-compare", "keyValueSeparator": " ", "sortOrder": 74, - "key": "etag-compare", - "commandKey": "curl" + "key": "etag-compare" }, { "name": "ETag Save", @@ -848,8 +767,7 @@ "longFlag": "--etag-save", "keyValueSeparator": " ", "sortOrder": 75, - "key": "etag-save", - "commandKey": "curl" + "key": "etag-save" }, { "name": "Expect100 Timeout", @@ -859,8 +777,7 @@ "longFlag": "--expect100-timeout", "keyValueSeparator": " ", "sortOrder": 76, - "key": "expect100-timeout", - "commandKey": "curl" + "key": "expect100-timeout" }, { "name": "Fail Early", @@ -869,8 +786,7 @@ "dataType": "Boolean", "longFlag": "--fail-early", "sortOrder": 77, - "key": "fail-early", - "commandKey": "curl" + "key": "fail-early" }, { "name": "Fail With Body", @@ -879,8 +795,7 @@ "dataType": "Boolean", "longFlag": "--fail-with-body", "sortOrder": 78, - "key": "fail-with-body", - "commandKey": "curl" + "key": "fail-with-body" }, { "name": "False Start", @@ -889,8 +804,7 @@ "dataType": "Boolean", "longFlag": "--false-start", "sortOrder": 79, - "key": "false-start", - "commandKey": "curl" + "key": "false-start" }, { "name": "Follow", @@ -899,8 +813,7 @@ "dataType": "Boolean", "longFlag": "--follow", "sortOrder": 80, - "key": "follow", - "commandKey": "curl" + "key": "follow" }, { "name": "Form Escape", @@ -909,8 +822,7 @@ "dataType": "Boolean", "longFlag": "--form-escape", "sortOrder": 81, - "key": "form-escape", - "commandKey": "curl" + "key": "form-escape" }, { "name": "Form String", @@ -921,8 +833,7 @@ "longFlag": "--form-string", "keyValueSeparator": " ", "sortOrder": 82, - "key": "form-string", - "commandKey": "curl" + "key": "form-string" }, { "name": "FTP Account", @@ -932,8 +843,7 @@ "longFlag": "--ftp-account", "keyValueSeparator": " ", "sortOrder": 83, - "key": "ftp-account", - "commandKey": "curl" + "key": "ftp-account" }, { "name": "FTP Alternative User", @@ -943,8 +853,7 @@ "longFlag": "--ftp-alternative-to-user", "keyValueSeparator": " ", "sortOrder": 84, - "key": "ftp-alternative-to-user", - "commandKey": "curl" + "key": "ftp-alternative-to-user" }, { "name": "FTP Create Dirs", @@ -953,8 +862,7 @@ "dataType": "Boolean", "longFlag": "--ftp-create-dirs", "sortOrder": 85, - "key": "ftp-create-dirs", - "commandKey": "curl" + "key": "ftp-create-dirs" }, { "name": "FTP Method", @@ -964,8 +872,7 @@ "longFlag": "--ftp-method", "keyValueSeparator": " ", "sortOrder": 86, - "key": "ftp-method", - "commandKey": "curl" + "key": "ftp-method" }, { "name": "FTP Passive", @@ -974,8 +881,7 @@ "dataType": "Boolean", "longFlag": "--ftp-pasv", "sortOrder": 87, - "key": "ftp-pasv", - "commandKey": "curl" + "key": "ftp-pasv" }, { "name": "FTP Port", @@ -986,8 +892,7 @@ "longFlag": "--ftp-port", "keyValueSeparator": " ", "sortOrder": 88, - "key": "ftp-port", - "commandKey": "curl" + "key": "ftp-port" }, { "name": "FTP PRET", @@ -996,8 +901,7 @@ "dataType": "Boolean", "longFlag": "--ftp-pret", "sortOrder": 89, - "key": "ftp-pret", - "commandKey": "curl" + "key": "ftp-pret" }, { "name": "FTP Skip Passive IP", @@ -1006,8 +910,7 @@ "dataType": "Boolean", "longFlag": "--ftp-skip-pasv-ip", "sortOrder": 90, - "key": "ftp-skip-pasv-ip", - "commandKey": "curl" + "key": "ftp-skip-pasv-ip" }, { "name": "FTP SSL CCC", @@ -1016,8 +919,7 @@ "dataType": "Boolean", "longFlag": "--ftp-ssl-ccc", "sortOrder": 91, - "key": "ftp-ssl-ccc", - "commandKey": "curl" + "key": "ftp-ssl-ccc" }, { "name": "FTP SSL CCC Mode", @@ -1027,8 +929,7 @@ "longFlag": "--ftp-ssl-ccc-mode", "keyValueSeparator": " ", "sortOrder": 92, - "key": "ftp-ssl-ccc-mode", - "commandKey": "curl" + "key": "ftp-ssl-ccc-mode" }, { "name": "FTP SSL Control", @@ -1037,8 +938,7 @@ "dataType": "Boolean", "longFlag": "--ftp-ssl-control", "sortOrder": 93, - "key": "ftp-ssl-control", - "commandKey": "curl" + "key": "ftp-ssl-control" }, { "name": "Globoff", @@ -1048,8 +948,7 @@ "shortFlag": "-g", "longFlag": "--globoff", "sortOrder": 94, - "key": "globoff", - "commandKey": "curl" + "key": "globoff" }, { "name": "Happy Eyeballs Timeout (ms)", @@ -1059,8 +958,7 @@ "longFlag": "--happy-eyeballs-timeout-ms", "keyValueSeparator": " ", "sortOrder": 95, - "key": "happy-eyeballs-timeout-ms", - "commandKey": "curl" + "key": "happy-eyeballs-timeout-ms" }, { "name": "HAProxy Client IP", @@ -1070,8 +968,7 @@ "longFlag": "--haproxy-clientip", "keyValueSeparator": " ", "sortOrder": 96, - "key": "haproxy-clientip", - "commandKey": "curl" + "key": "haproxy-clientip" }, { "name": "HAProxy Protocol", @@ -1080,8 +977,7 @@ "dataType": "Boolean", "longFlag": "--haproxy-protocol", "sortOrder": 97, - "key": "haproxy-protocol", - "commandKey": "curl" + "key": "haproxy-protocol" }, { "name": "Host Pubkey MD5", @@ -1091,8 +987,7 @@ "longFlag": "--hostpubmd5", "keyValueSeparator": " ", "sortOrder": 98, - "key": "hostpubmd5", - "commandKey": "curl" + "key": "hostpubmd5" }, { "name": "Host Pubkey SHA256", @@ -1102,8 +997,7 @@ "longFlag": "--hostpubsha256", "keyValueSeparator": " ", "sortOrder": 99, - "key": "hostpubsha256", - "commandKey": "curl" + "key": "hostpubsha256" }, { "name": "HSTS", @@ -1113,8 +1007,7 @@ "longFlag": "--hsts", "keyValueSeparator": " ", "sortOrder": 100, - "key": "hsts", - "commandKey": "curl" + "key": "hsts" }, { "name": "HTTP 0.9", @@ -1123,8 +1016,7 @@ "dataType": "Boolean", "longFlag": "--http0.9", "sortOrder": 101, - "key": "http09", - "commandKey": "curl" + "key": "http09" }, { "name": "HTTP 1.0", @@ -1134,8 +1026,7 @@ "shortFlag": "-0", "longFlag": "--http1.0", "sortOrder": 102, - "key": "http10", - "commandKey": "curl" + "key": "http10" }, { "name": "HTTP 1.1", @@ -1144,8 +1035,7 @@ "dataType": "Boolean", "longFlag": "--http1.1", "sortOrder": 103, - "key": "http11", - "commandKey": "curl" + "key": "http11" }, { "name": "HTTP/2", @@ -1154,8 +1044,7 @@ "dataType": "Boolean", "longFlag": "--http2", "sortOrder": 104, - "key": "http2", - "commandKey": "curl" + "key": "http2" }, { "name": "HTTP/2 Prior Knowledge", @@ -1164,8 +1053,7 @@ "dataType": "Boolean", "longFlag": "--http2-prior-knowledge", "sortOrder": 105, - "key": "http2-prior-knowledge", - "commandKey": "curl" + "key": "http2-prior-knowledge" }, { "name": "HTTP/3", @@ -1174,8 +1062,7 @@ "dataType": "Boolean", "longFlag": "--http3", "sortOrder": 106, - "key": "http3", - "commandKey": "curl" + "key": "http3" }, { "name": "HTTP/3 Only", @@ -1184,8 +1071,7 @@ "dataType": "Boolean", "longFlag": "--http3-only", "sortOrder": 107, - "key": "http3-only", - "commandKey": "curl" + "key": "http3-only" }, { "name": "Ignore Content Length", @@ -1194,8 +1080,7 @@ "dataType": "Boolean", "longFlag": "--ignore-content-length", "sortOrder": 108, - "key": "ignore-content-length", - "commandKey": "curl" + "key": "ignore-content-length" }, { "name": "Interface", @@ -1205,8 +1090,7 @@ "longFlag": "--interface", "keyValueSeparator": " ", "sortOrder": 109, - "key": "interface", - "commandKey": "curl" + "key": "interface" }, { "name": "IP TOS", @@ -1216,8 +1100,7 @@ "longFlag": "--ip-tos", "keyValueSeparator": " ", "sortOrder": 110, - "key": "ip-tos", - "commandKey": "curl" + "key": "ip-tos" }, { "name": "IPFS Gateway", @@ -1227,8 +1110,7 @@ "longFlag": "--ipfs-gateway", "keyValueSeparator": " ", "sortOrder": 111, - "key": "ipfs-gateway", - "commandKey": "curl" + "key": "ipfs-gateway" }, { "name": "IPv4", @@ -1238,8 +1120,7 @@ "shortFlag": "-4", "longFlag": "--ipv4", "sortOrder": 112, - "key": "ipv4", - "commandKey": "curl" + "key": "ipv4" }, { "name": "IPv6", @@ -1249,8 +1130,7 @@ "shortFlag": "-6", "longFlag": "--ipv6", "sortOrder": 113, - "key": "ipv6", - "commandKey": "curl" + "key": "ipv6" }, { "name": "JSON", @@ -1261,8 +1141,7 @@ "longFlag": "--json", "keyValueSeparator": " ", "sortOrder": 114, - "key": "json", - "commandKey": "curl" + "key": "json" }, { "name": "Junk Session Cookies", @@ -1272,8 +1151,7 @@ "shortFlag": "-j", "longFlag": "--junk-session-cookies", "sortOrder": 115, - "key": "junk-session-cookies", - "commandKey": "curl" + "key": "junk-session-cookies" }, { "name": "Keepalive Count", @@ -1283,8 +1161,7 @@ "longFlag": "--keepalive-cnt", "keyValueSeparator": " ", "sortOrder": 116, - "key": "keepalive-cnt", - "commandKey": "curl" + "key": "keepalive-cnt" }, { "name": "Keepalive Time", @@ -1294,8 +1171,7 @@ "longFlag": "--keepalive-time", "keyValueSeparator": " ", "sortOrder": 117, - "key": "keepalive-time", - "commandKey": "curl" + "key": "keepalive-time" }, { "name": "Key", @@ -1305,8 +1181,7 @@ "longFlag": "--key", "keyValueSeparator": " ", "sortOrder": 118, - "key": "key", - "commandKey": "curl" + "key": "key" }, { "name": "Key Type", @@ -1316,8 +1191,7 @@ "longFlag": "--key-type", "keyValueSeparator": " ", "sortOrder": 119, - "key": "key-type", - "commandKey": "curl" + "key": "key-type" }, { "name": "Knownhosts", @@ -1327,8 +1201,7 @@ "longFlag": "--knownhosts", "keyValueSeparator": " ", "sortOrder": 120, - "key": "knownhosts", - "commandKey": "curl" + "key": "knownhosts" }, { "name": "Krb", @@ -1338,8 +1211,7 @@ "longFlag": "--krb", "keyValueSeparator": " ", "sortOrder": 121, - "key": "krb", - "commandKey": "curl" + "key": "krb" }, { "name": "Libcurl", @@ -1349,8 +1221,7 @@ "longFlag": "--libcurl", "keyValueSeparator": " ", "sortOrder": 122, - "key": "libcurl", - "commandKey": "curl" + "key": "libcurl" }, { "name": "Limit Rate", @@ -1360,8 +1231,7 @@ "longFlag": "--limit-rate", "keyValueSeparator": " ", "sortOrder": 123, - "key": "limit-rate", - "commandKey": "curl" + "key": "limit-rate" }, { "name": "List Only", @@ -1371,8 +1241,7 @@ "shortFlag": "-l", "longFlag": "--list-only", "sortOrder": 124, - "key": "list-only", - "commandKey": "curl" + "key": "list-only" }, { "name": "Local Port", @@ -1382,8 +1251,7 @@ "longFlag": "--local-port", "keyValueSeparator": " ", "sortOrder": 125, - "key": "local-port", - "commandKey": "curl" + "key": "local-port" }, { "name": "Location Trusted", @@ -1392,8 +1260,7 @@ "dataType": "Boolean", "longFlag": "--location-trusted", "sortOrder": 126, - "key": "location-trusted", - "commandKey": "curl" + "key": "location-trusted" }, { "name": "Login Options", @@ -1403,8 +1270,7 @@ "longFlag": "--login-options", "keyValueSeparator": " ", "sortOrder": 127, - "key": "login-options", - "commandKey": "curl" + "key": "login-options" }, { "name": "Mail Auth", @@ -1414,8 +1280,7 @@ "longFlag": "--mail-auth", "keyValueSeparator": " ", "sortOrder": 128, - "key": "mail-auth", - "commandKey": "curl" + "key": "mail-auth" }, { "name": "Mail From", @@ -1425,8 +1290,7 @@ "longFlag": "--mail-from", "keyValueSeparator": " ", "sortOrder": 129, - "key": "mail-from", - "commandKey": "curl" + "key": "mail-from" }, { "name": "Mail Recipients", @@ -1437,8 +1301,7 @@ "longFlag": "--mail-rcpt", "keyValueSeparator": " ", "sortOrder": 130, - "key": "mail-rcpt", - "commandKey": "curl" + "key": "mail-rcpt" }, { "name": "Mail RCPT Allow Fails", @@ -1447,8 +1310,7 @@ "dataType": "Boolean", "longFlag": "--mail-rcpt-allowfails", "sortOrder": 131, - "key": "mail-rcpt-allowfails", - "commandKey": "curl" + "key": "mail-rcpt-allowfails" }, { "name": "Manual", @@ -1458,8 +1320,7 @@ "shortFlag": "-M", "longFlag": "--manual", "sortOrder": 132, - "key": "manual", - "commandKey": "curl" + "key": "manual" }, { "name": "Max File Size", @@ -1469,8 +1330,7 @@ "longFlag": "--max-filesize", "keyValueSeparator": " ", "sortOrder": 133, - "key": "max-filesize", - "commandKey": "curl" + "key": "max-filesize" }, { "name": "Max Redirects", @@ -1480,8 +1340,7 @@ "longFlag": "--max-redirs", "keyValueSeparator": " ", "sortOrder": 134, - "key": "max-redirs", - "commandKey": "curl" + "key": "max-redirs" }, { "name": "Metalink", @@ -1490,8 +1349,7 @@ "dataType": "Boolean", "longFlag": "--metalink", "sortOrder": 135, - "key": "metalink", - "commandKey": "curl" + "key": "metalink" }, { "name": "Mptcp", @@ -1500,8 +1358,7 @@ "dataType": "Boolean", "longFlag": "--mptcp", "sortOrder": 136, - "key": "mptcp", - "commandKey": "curl" + "key": "mptcp" }, { "name": "Negotiate", @@ -1510,8 +1367,7 @@ "dataType": "Boolean", "longFlag": "--negotiate", "sortOrder": 137, - "key": "negotiate", - "commandKey": "curl" + "key": "negotiate" }, { "name": "Netrc", @@ -1521,8 +1377,7 @@ "shortFlag": "-n", "longFlag": "--netrc", "sortOrder": 138, - "key": "netrc", - "commandKey": "curl" + "key": "netrc" }, { "name": "Netrc File", @@ -1532,8 +1387,7 @@ "longFlag": "--netrc-file", "keyValueSeparator": " ", "sortOrder": 139, - "key": "netrc-file", - "commandKey": "curl" + "key": "netrc-file" }, { "name": "Netrc Optional", @@ -1542,8 +1396,7 @@ "dataType": "Boolean", "longFlag": "--netrc-optional", "sortOrder": 140, - "key": "netrc-optional", - "commandKey": "curl" + "key": "netrc-optional" }, { "name": "Next", @@ -1553,8 +1406,7 @@ "shortFlag": "-:", "longFlag": "--next", "sortOrder": 141, - "key": "next", - "commandKey": "curl" + "key": "next" }, { "name": "No ALPN", @@ -1563,8 +1415,7 @@ "dataType": "Boolean", "longFlag": "--no-alpn", "sortOrder": 142, - "key": "no-alpn", - "commandKey": "curl" + "key": "no-alpn" }, { "name": "No Buffer", @@ -1574,8 +1425,7 @@ "shortFlag": "-N", "longFlag": "--no-buffer", "sortOrder": 143, - "key": "no-buffer", - "commandKey": "curl" + "key": "no-buffer" }, { "name": "No Clobber", @@ -1584,8 +1434,7 @@ "dataType": "Boolean", "longFlag": "--no-clobber", "sortOrder": 144, - "key": "no-clobber", - "commandKey": "curl" + "key": "no-clobber" }, { "name": "No Keepalive", @@ -1594,8 +1443,7 @@ "dataType": "Boolean", "longFlag": "--no-keepalive", "sortOrder": 145, - "key": "no-keepalive", - "commandKey": "curl" + "key": "no-keepalive" }, { "name": "No NPN", @@ -1604,8 +1452,7 @@ "dataType": "Boolean", "longFlag": "--no-npn", "sortOrder": 146, - "key": "no-npn", - "commandKey": "curl" + "key": "no-npn" }, { "name": "No Progress Meter", @@ -1614,8 +1461,7 @@ "dataType": "Boolean", "longFlag": "--no-progress-meter", "sortOrder": 147, - "key": "no-progress-meter", - "commandKey": "curl" + "key": "no-progress-meter" }, { "name": "No Session ID", @@ -1624,8 +1470,7 @@ "dataType": "Boolean", "longFlag": "--no-sessionid", "sortOrder": 148, - "key": "no-sessionid", - "commandKey": "curl" + "key": "no-sessionid" }, { "name": "Noproxy", @@ -1635,8 +1480,7 @@ "longFlag": "--noproxy", "keyValueSeparator": " ", "sortOrder": 149, - "key": "noproxy", - "commandKey": "curl" + "key": "noproxy" }, { "name": "NTLM", @@ -1645,8 +1489,7 @@ "dataType": "Boolean", "longFlag": "--ntlm", "sortOrder": 150, - "key": "ntlm", - "commandKey": "curl" + "key": "ntlm" }, { "name": "NTLM WB", @@ -1655,8 +1498,7 @@ "dataType": "Boolean", "longFlag": "--ntlm-wb", "sortOrder": 151, - "key": "ntlm-wb", - "commandKey": "curl" + "key": "ntlm-wb" }, { "name": "OAuth2 Bearer", @@ -1666,8 +1508,7 @@ "longFlag": "--oauth2-bearer", "keyValueSeparator": " ", "sortOrder": 152, - "key": "oauth2-bearer", - "commandKey": "curl" + "key": "oauth2-bearer" }, { "name": "Output Null", @@ -1676,8 +1517,7 @@ "dataType": "Boolean", "longFlag": "--out-null", "sortOrder": 153, - "key": "out-null", - "commandKey": "curl" + "key": "out-null" }, { "name": "Parallel", @@ -1687,8 +1527,7 @@ "shortFlag": "-Z", "longFlag": "--parallel", "sortOrder": 154, - "key": "parallel", - "commandKey": "curl" + "key": "parallel" }, { "name": "Parallel Immediate", @@ -1697,8 +1536,7 @@ "dataType": "Boolean", "longFlag": "--parallel-immediate", "sortOrder": 155, - "key": "parallel-immediate", - "commandKey": "curl" + "key": "parallel-immediate" }, { "name": "Parallel Max", @@ -1708,8 +1546,7 @@ "longFlag": "--parallel-max", "keyValueSeparator": " ", "sortOrder": 156, - "key": "parallel-max", - "commandKey": "curl" + "key": "parallel-max" }, { "name": "Parallel Max Host", @@ -1719,8 +1556,7 @@ "longFlag": "--parallel-max-host", "keyValueSeparator": " ", "sortOrder": 157, - "key": "parallel-max-host", - "commandKey": "curl" + "key": "parallel-max-host" }, { "name": "Pass", @@ -1730,8 +1566,7 @@ "longFlag": "--pass", "keyValueSeparator": " ", "sortOrder": 158, - "key": "pass", - "commandKey": "curl" + "key": "pass" }, { "name": "Path As Is", @@ -1740,8 +1575,7 @@ "dataType": "Boolean", "longFlag": "--path-as-is", "sortOrder": 159, - "key": "path-as-is", - "commandKey": "curl" + "key": "path-as-is" }, { "name": "Pinnedpubkey", @@ -1751,8 +1585,7 @@ "longFlag": "--pinnedpubkey", "keyValueSeparator": " ", "sortOrder": 160, - "key": "pinnedpubkey", - "commandKey": "curl" + "key": "pinnedpubkey" }, { "name": "Post 301", @@ -1761,8 +1594,7 @@ "dataType": "Boolean", "longFlag": "--post301", "sortOrder": 161, - "key": "post301", - "commandKey": "curl" + "key": "post301" }, { "name": "Post 302", @@ -1771,8 +1603,7 @@ "dataType": "Boolean", "longFlag": "--post302", "sortOrder": 162, - "key": "post302", - "commandKey": "curl" + "key": "post302" }, { "name": "Post 303", @@ -1781,8 +1612,7 @@ "dataType": "Boolean", "longFlag": "--post303", "sortOrder": 163, - "key": "post303", - "commandKey": "curl" + "key": "post303" }, { "name": "Preproxy", @@ -1792,8 +1622,7 @@ "longFlag": "--preproxy", "keyValueSeparator": " ", "sortOrder": 164, - "key": "preproxy", - "commandKey": "curl" + "key": "preproxy" }, { "name": "Progress Bar", @@ -1803,8 +1632,7 @@ "shortFlag": "-#", "longFlag": "--progress-bar", "sortOrder": 165, - "key": "progress-bar", - "commandKey": "curl" + "key": "progress-bar" }, { "name": "Proto", @@ -1814,8 +1642,7 @@ "longFlag": "--proto", "keyValueSeparator": " ", "sortOrder": 166, - "key": "proto", - "commandKey": "curl" + "key": "proto" }, { "name": "Proto Default", @@ -1825,8 +1652,7 @@ "longFlag": "--proto-default", "keyValueSeparator": " ", "sortOrder": 167, - "key": "proto-default", - "commandKey": "curl" + "key": "proto-default" }, { "name": "Proto Redirect", @@ -1836,8 +1662,7 @@ "longFlag": "--proto-redir", "keyValueSeparator": " ", "sortOrder": 168, - "key": "proto-redir", - "commandKey": "curl" + "key": "proto-redir" }, { "name": "Proxy anyauth", @@ -1846,8 +1671,7 @@ "dataType": "Boolean", "longFlag": "--proxy-anyauth", "sortOrder": 169, - "key": "proxy-anyauth", - "commandKey": "curl" + "key": "proxy-anyauth" }, { "name": "Proxy basic", @@ -1856,8 +1680,7 @@ "dataType": "Boolean", "longFlag": "--proxy-basic", "sortOrder": 170, - "key": "proxy-basic", - "commandKey": "curl" + "key": "proxy-basic" }, { "name": "Proxy CA Native", @@ -1866,8 +1689,7 @@ "dataType": "Boolean", "longFlag": "--proxy-ca-native", "sortOrder": 171, - "key": "proxy-ca-native", - "commandKey": "curl" + "key": "proxy-ca-native" }, { "name": "Proxy CA Cert", @@ -1877,8 +1699,7 @@ "longFlag": "--proxy-cacert", "keyValueSeparator": " ", "sortOrder": 172, - "key": "proxy-cacert", - "commandKey": "curl" + "key": "proxy-cacert" }, { "name": "Proxy CA Path", @@ -1888,8 +1709,7 @@ "longFlag": "--proxy-capath", "keyValueSeparator": " ", "sortOrder": 173, - "key": "proxy-capath", - "commandKey": "curl" + "key": "proxy-capath" }, { "name": "Proxy cert", @@ -1899,8 +1719,7 @@ "longFlag": "--proxy-cert", "keyValueSeparator": " ", "sortOrder": 174, - "key": "proxy-cert", - "commandKey": "curl" + "key": "proxy-cert" }, { "name": "Proxy Cert Type", @@ -1910,8 +1729,7 @@ "longFlag": "--proxy-cert-type", "keyValueSeparator": " ", "sortOrder": 175, - "key": "proxy-cert-type", - "commandKey": "curl" + "key": "proxy-cert-type" }, { "name": "Proxy Ciphers", @@ -1921,8 +1739,7 @@ "longFlag": "--proxy-ciphers", "keyValueSeparator": " ", "sortOrder": 176, - "key": "proxy-ciphers", - "commandKey": "curl" + "key": "proxy-ciphers" }, { "name": "Proxy CRL File", @@ -1932,8 +1749,7 @@ "longFlag": "--proxy-crlfile", "keyValueSeparator": " ", "sortOrder": 177, - "key": "proxy-crlfile", - "commandKey": "curl" + "key": "proxy-crlfile" }, { "name": "Proxy digest", @@ -1942,8 +1758,7 @@ "dataType": "Boolean", "longFlag": "--proxy-digest", "sortOrder": 178, - "key": "proxy-digest", - "commandKey": "curl" + "key": "proxy-digest" }, { "name": "Proxy Header", @@ -1954,8 +1769,7 @@ "longFlag": "--proxy-header", "keyValueSeparator": " ", "sortOrder": 179, - "key": "proxy-header", - "commandKey": "curl" + "key": "proxy-header" }, { "name": "Proxy HTTP/2", @@ -1964,8 +1778,7 @@ "dataType": "Boolean", "longFlag": "--proxy-http2", "sortOrder": 180, - "key": "proxy-http2", - "commandKey": "curl" + "key": "proxy-http2" }, { "name": "Proxy insecure", @@ -1974,8 +1787,7 @@ "dataType": "Boolean", "longFlag": "--proxy-insecure", "sortOrder": 181, - "key": "proxy-insecure", - "commandKey": "curl" + "key": "proxy-insecure" }, { "name": "Proxy key", @@ -1985,8 +1797,7 @@ "longFlag": "--proxy-key", "keyValueSeparator": " ", "sortOrder": 182, - "key": "proxy-key", - "commandKey": "curl" + "key": "proxy-key" }, { "name": "Proxy Key Type", @@ -1996,8 +1807,7 @@ "longFlag": "--proxy-key-type", "keyValueSeparator": " ", "sortOrder": 183, - "key": "proxy-key-type", - "commandKey": "curl" + "key": "proxy-key-type" }, { "name": "Proxy Negotiate", @@ -2006,8 +1816,7 @@ "dataType": "Boolean", "longFlag": "--proxy-negotiate", "sortOrder": 184, - "key": "proxy-negotiate", - "commandKey": "curl" + "key": "proxy-negotiate" }, { "name": "Proxy NTLM", @@ -2016,8 +1825,7 @@ "dataType": "Boolean", "longFlag": "--proxy-ntlm", "sortOrder": 185, - "key": "proxy-ntlm", - "commandKey": "curl" + "key": "proxy-ntlm" }, { "name": "Proxy Password", @@ -2027,8 +1835,7 @@ "longFlag": "--proxy-pass", "keyValueSeparator": " ", "sortOrder": 186, - "key": "proxy-pass", - "commandKey": "curl" + "key": "proxy-pass" }, { "name": "Proxy Pinned Public Key", @@ -2038,8 +1845,7 @@ "longFlag": "--proxy-pinnedpubkey", "keyValueSeparator": " ", "sortOrder": 187, - "key": "proxy-pinnedpubkey", - "commandKey": "curl" + "key": "proxy-pinnedpubkey" }, { "name": "Proxy Service Name", @@ -2049,8 +1855,7 @@ "longFlag": "--proxy-service-name", "keyValueSeparator": " ", "sortOrder": 188, - "key": "proxy-service-name", - "commandKey": "curl" + "key": "proxy-service-name" }, { "name": "Proxy SSL Allow BEAST", @@ -2059,8 +1864,7 @@ "dataType": "Boolean", "longFlag": "--proxy-ssl-allow-beast", "sortOrder": 189, - "key": "proxy-ssl-allow-beast", - "commandKey": "curl" + "key": "proxy-ssl-allow-beast" }, { "name": "Proxy SSL Auto Client Cert", @@ -2069,8 +1873,7 @@ "dataType": "Boolean", "longFlag": "--proxy-ssl-auto-client-cert", "sortOrder": 190, - "key": "proxy-ssl-auto-client-cert", - "commandKey": "curl" + "key": "proxy-ssl-auto-client-cert" }, { "name": "Proxy TLS 1.3 Ciphers", @@ -2080,8 +1883,7 @@ "longFlag": "--proxy-tls13-ciphers", "keyValueSeparator": " ", "sortOrder": 191, - "key": "proxy-tls13-ciphers", - "commandKey": "curl" + "key": "proxy-tls13-ciphers" }, { "name": "Proxy TLS Auth Type", @@ -2091,8 +1893,7 @@ "longFlag": "--proxy-tlsauthtype", "keyValueSeparator": " ", "sortOrder": 192, - "key": "proxy-tlsauthtype", - "commandKey": "curl" + "key": "proxy-tlsauthtype" }, { "name": "Proxy TLS Password", @@ -2102,8 +1903,7 @@ "longFlag": "--proxy-tlspassword", "keyValueSeparator": " ", "sortOrder": 193, - "key": "proxy-tlspassword", - "commandKey": "curl" + "key": "proxy-tlspassword" }, { "name": "Proxy TLS User", @@ -2113,8 +1913,7 @@ "longFlag": "--proxy-tlsuser", "keyValueSeparator": " ", "sortOrder": 194, - "key": "proxy-tlsuser", - "commandKey": "curl" + "key": "proxy-tlsuser" }, { "name": "Proxy TLS v1", @@ -2123,8 +1922,7 @@ "dataType": "Boolean", "longFlag": "--proxy-tlsv1", "sortOrder": 195, - "key": "proxy-tlsv1", - "commandKey": "curl" + "key": "proxy-tlsv1" }, { "name": "Proxy User", @@ -2135,8 +1933,7 @@ "longFlag": "--proxy-user", "keyValueSeparator": " ", "sortOrder": 196, - "key": "proxy-user", - "commandKey": "curl" + "key": "proxy-user" }, { "name": "Proxy 1.0", @@ -2146,8 +1943,7 @@ "longFlag": "--proxy1.0", "keyValueSeparator": " ", "sortOrder": 197, - "key": "proxy10", - "commandKey": "curl" + "key": "proxy10" }, { "name": "Proxytunnel", @@ -2157,8 +1953,7 @@ "shortFlag": "-p", "longFlag": "--proxytunnel", "sortOrder": 198, - "key": "proxytunnel", - "commandKey": "curl" + "key": "proxytunnel" }, { "name": "Pubkey", @@ -2168,8 +1963,7 @@ "longFlag": "--pubkey", "keyValueSeparator": " ", "sortOrder": 199, - "key": "pubkey", - "commandKey": "curl" + "key": "pubkey" }, { "name": "Quote", @@ -2181,8 +1975,7 @@ "longFlag": "--quote", "keyValueSeparator": " ", "sortOrder": 200, - "key": "quote", - "commandKey": "curl" + "key": "quote" }, { "name": "Random File", @@ -2192,8 +1985,7 @@ "longFlag": "--random-file", "keyValueSeparator": " ", "sortOrder": 201, - "key": "random-file", - "commandKey": "curl" + "key": "random-file" }, { "name": "Range", @@ -2204,8 +1996,7 @@ "longFlag": "--range", "keyValueSeparator": " ", "sortOrder": 202, - "key": "range", - "commandKey": "curl" + "key": "range" }, { "name": "Rate", @@ -2215,8 +2006,7 @@ "longFlag": "--rate", "keyValueSeparator": " ", "sortOrder": 203, - "key": "rate", - "commandKey": "curl" + "key": "rate" }, { "name": "Raw", @@ -2225,8 +2015,7 @@ "dataType": "Boolean", "longFlag": "--raw", "sortOrder": 204, - "key": "raw", - "commandKey": "curl" + "key": "raw" }, { "name": "Referer", @@ -2237,8 +2026,7 @@ "longFlag": "--referer", "keyValueSeparator": " ", "sortOrder": 205, - "key": "referer", - "commandKey": "curl" + "key": "referer" }, { "name": "Remote Header Name", @@ -2248,8 +2036,7 @@ "shortFlag": "-J", "longFlag": "--remote-header-name", "sortOrder": 206, - "key": "remote-header-name", - "commandKey": "curl" + "key": "remote-header-name" }, { "name": "Remote Name All", @@ -2258,8 +2045,7 @@ "dataType": "Boolean", "longFlag": "--remote-name-all", "sortOrder": 207, - "key": "remote-name-all", - "commandKey": "curl" + "key": "remote-name-all" }, { "name": "Remote Time", @@ -2269,8 +2055,7 @@ "shortFlag": "-R", "longFlag": "--remote-time", "sortOrder": 208, - "key": "remote-time", - "commandKey": "curl" + "key": "remote-time" }, { "name": "Remove On Error", @@ -2279,8 +2064,7 @@ "dataType": "Boolean", "longFlag": "--remove-on-error", "sortOrder": 209, - "key": "remove-on-error", - "commandKey": "curl" + "key": "remove-on-error" }, { "name": "Request Target", @@ -2290,8 +2074,7 @@ "longFlag": "--request-target", "keyValueSeparator": " ", "sortOrder": 210, - "key": "request-target", - "commandKey": "curl" + "key": "request-target" }, { "name": "Resolve", @@ -2302,8 +2085,7 @@ "longFlag": "--resolve", "keyValueSeparator": " ", "sortOrder": 211, - "key": "resolve", - "commandKey": "curl" + "key": "resolve" }, { "name": "Retry All Errors", @@ -2313,7 +2095,6 @@ "longFlag": "--retry-all-errors", "sortOrder": 212, "key": "retry-all-errors", - "commandKey": "curl", "dependencies": [ { "key": "dep-retry-all-errors-retry", @@ -2331,7 +2112,6 @@ "longFlag": "--retry-connrefused", "sortOrder": 213, "key": "retry-connrefused", - "commandKey": "curl", "dependencies": [ { "key": "dep-retry-connrefused-retry", @@ -2350,7 +2130,6 @@ "keyValueSeparator": " ", "sortOrder": 214, "key": "retry-delay", - "commandKey": "curl", "dependencies": [ { "key": "dep-retry-delay-retry", @@ -2369,7 +2148,6 @@ "keyValueSeparator": " ", "sortOrder": 215, "key": "retry-max-time", - "commandKey": "curl", "dependencies": [ { "key": "dep-retry-max-time-retry", @@ -2387,8 +2165,7 @@ "longFlag": "--sasl-authzid", "keyValueSeparator": " ", "sortOrder": 216, - "key": "sasl-authzid", - "commandKey": "curl" + "key": "sasl-authzid" }, { "name": "SASL IR", @@ -2397,8 +2174,7 @@ "dataType": "Boolean", "longFlag": "--sasl-ir", "sortOrder": 217, - "key": "sasl-ir", - "commandKey": "curl" + "key": "sasl-ir" }, { "name": "Service Name", @@ -2408,8 +2184,7 @@ "longFlag": "--service-name", "keyValueSeparator": " ", "sortOrder": 218, - "key": "service-name", - "commandKey": "curl" + "key": "service-name" }, { "name": "Show Error", @@ -2420,7 +2195,6 @@ "longFlag": "--show-error", "sortOrder": 219, "key": "show-error", - "commandKey": "curl", "dependencies": [ { "key": "dep-show-error-silent", @@ -2438,8 +2212,7 @@ "longFlag": "--sigalgs", "keyValueSeparator": " ", "sortOrder": 220, - "key": "sigalgs", - "commandKey": "curl" + "key": "sigalgs" }, { "name": "Skip Existing", @@ -2448,8 +2221,7 @@ "dataType": "Boolean", "longFlag": "--skip-existing", "sortOrder": 221, - "key": "skip-existing", - "commandKey": "curl" + "key": "skip-existing" }, { "name": "SOCKS4", @@ -2459,8 +2231,7 @@ "longFlag": "--socks4", "keyValueSeparator": " ", "sortOrder": 222, - "key": "socks4", - "commandKey": "curl" + "key": "socks4" }, { "name": "SOCKS4a", @@ -2470,8 +2241,7 @@ "longFlag": "--socks4a", "keyValueSeparator": " ", "sortOrder": 223, - "key": "socks4a", - "commandKey": "curl" + "key": "socks4a" }, { "name": "SOCKS5", @@ -2481,8 +2251,7 @@ "longFlag": "--socks5", "keyValueSeparator": " ", "sortOrder": 224, - "key": "socks5", - "commandKey": "curl" + "key": "socks5" }, { "name": "SOCKS5 Basic", @@ -2491,8 +2260,7 @@ "dataType": "Boolean", "longFlag": "--socks5-basic", "sortOrder": 225, - "key": "socks5-basic", - "commandKey": "curl" + "key": "socks5-basic" }, { "name": "SOCKS5 GSSAPI", @@ -2501,8 +2269,7 @@ "dataType": "Boolean", "longFlag": "--socks5-gssapi", "sortOrder": 226, - "key": "socks5-gssapi", - "commandKey": "curl" + "key": "socks5-gssapi" }, { "name": "SOCKS5 GSSAPI NEC", @@ -2511,8 +2278,7 @@ "dataType": "Boolean", "longFlag": "--socks5-gssapi-nec", "sortOrder": 227, - "key": "socks5-gssapi-nec", - "commandKey": "curl" + "key": "socks5-gssapi-nec" }, { "name": "SOCKS5 GSSAPI Service", @@ -2522,8 +2288,7 @@ "longFlag": "--socks5-gssapi-service", "keyValueSeparator": " ", "sortOrder": 228, - "key": "socks5-gssapi-service", - "commandKey": "curl" + "key": "socks5-gssapi-service" }, { "name": "SOCKS5 Hostname", @@ -2533,8 +2298,7 @@ "longFlag": "--socks5-hostname", "keyValueSeparator": " ", "sortOrder": 229, - "key": "socks5-hostname", - "commandKey": "curl" + "key": "socks5-hostname" }, { "name": "Speed Limit", @@ -2545,8 +2309,7 @@ "longFlag": "--speed-limit", "keyValueSeparator": " ", "sortOrder": 230, - "key": "speed-limit", - "commandKey": "curl" + "key": "speed-limit" }, { "name": "Speed Time", @@ -2557,8 +2320,7 @@ "longFlag": "--speed-time", "keyValueSeparator": " ", "sortOrder": 231, - "key": "speed-time", - "commandKey": "curl" + "key": "speed-time" }, { "name": "SSL", @@ -2567,8 +2329,7 @@ "dataType": "Boolean", "longFlag": "--ssl", "sortOrder": 232, - "key": "ssl", - "commandKey": "curl" + "key": "ssl" }, { "name": "SSL Allow BEAST", @@ -2577,8 +2338,7 @@ "dataType": "Boolean", "longFlag": "--ssl-allow-beast", "sortOrder": 233, - "key": "ssl-allow-beast", - "commandKey": "curl" + "key": "ssl-allow-beast" }, { "name": "SSL Auto Client Cert", @@ -2587,8 +2347,7 @@ "dataType": "Boolean", "longFlag": "--ssl-auto-client-cert", "sortOrder": 234, - "key": "ssl-auto-client-cert", - "commandKey": "curl" + "key": "ssl-auto-client-cert" }, { "name": "SSL No Revoke", @@ -2597,8 +2356,7 @@ "dataType": "Boolean", "longFlag": "--ssl-no-revoke", "sortOrder": 235, - "key": "ssl-no-revoke", - "commandKey": "curl" + "key": "ssl-no-revoke" }, { "name": "SSL Required", @@ -2607,8 +2365,7 @@ "dataType": "Boolean", "longFlag": "--ssl-reqd", "sortOrder": 236, - "key": "ssl-reqd", - "commandKey": "curl" + "key": "ssl-reqd" }, { "name": "SSL Revoke Best Effort", @@ -2617,8 +2374,7 @@ "dataType": "Boolean", "longFlag": "--ssl-revoke-best-effort", "sortOrder": 237, - "key": "ssl-revoke-best-effort", - "commandKey": "curl" + "key": "ssl-revoke-best-effort" }, { "name": "SSL Sessions", @@ -2628,8 +2384,7 @@ "longFlag": "--ssl-sessions", "keyValueSeparator": " ", "sortOrder": 238, - "key": "ssl-sessions", - "commandKey": "curl" + "key": "ssl-sessions" }, { "name": "SSLv2", @@ -2639,8 +2394,7 @@ "shortFlag": "-2", "longFlag": "--sslv2", "sortOrder": 239, - "key": "sslv2", - "commandKey": "curl" + "key": "sslv2" }, { "name": "SSLv3", @@ -2650,8 +2404,7 @@ "shortFlag": "-3", "longFlag": "--sslv3", "sortOrder": 240, - "key": "sslv3", - "commandKey": "curl" + "key": "sslv3" }, { "name": "Stderr", @@ -2661,8 +2414,7 @@ "longFlag": "--stderr", "keyValueSeparator": " ", "sortOrder": 241, - "key": "stderr", - "commandKey": "curl" + "key": "stderr" }, { "name": "Styled Output", @@ -2671,8 +2423,7 @@ "dataType": "Boolean", "longFlag": "--styled-output", "sortOrder": 242, - "key": "styled-output", - "commandKey": "curl" + "key": "styled-output" }, { "name": "Suppress Connect Headers", @@ -2681,8 +2432,7 @@ "dataType": "Boolean", "longFlag": "--suppress-connect-headers", "sortOrder": 243, - "key": "suppress-connect-headers", - "commandKey": "curl" + "key": "suppress-connect-headers" }, { "name": "TCP Fast Open", @@ -2691,8 +2441,7 @@ "dataType": "Boolean", "longFlag": "--tcp-fastopen", "sortOrder": 244, - "key": "tcp-fastopen", - "commandKey": "curl" + "key": "tcp-fastopen" }, { "name": "TCP No Delay", @@ -2701,8 +2450,7 @@ "dataType": "Boolean", "longFlag": "--tcp-nodelay", "sortOrder": 245, - "key": "tcp-nodelay", - "commandKey": "curl" + "key": "tcp-nodelay" }, { "name": "Telnet Option", @@ -2714,8 +2462,7 @@ "longFlag": "--telnet-option", "keyValueSeparator": " ", "sortOrder": 246, - "key": "telnet-option", - "commandKey": "curl" + "key": "telnet-option" }, { "name": "TFTP Block Size", @@ -2725,8 +2472,7 @@ "longFlag": "--tftp-blksize", "keyValueSeparator": " ", "sortOrder": 247, - "key": "tftp-blksize", - "commandKey": "curl" + "key": "tftp-blksize" }, { "name": "TFTP No Options", @@ -2735,8 +2481,7 @@ "dataType": "Boolean", "longFlag": "--tftp-no-options", "sortOrder": 248, - "key": "tftp-no-options", - "commandKey": "curl" + "key": "tftp-no-options" }, { "name": "Time Condition", @@ -2747,8 +2492,7 @@ "longFlag": "--time-cond", "keyValueSeparator": " ", "sortOrder": 249, - "key": "time-cond", - "commandKey": "curl" + "key": "time-cond" }, { "name": "TLS Early Data", @@ -2757,8 +2501,7 @@ "dataType": "Boolean", "longFlag": "--tls-earlydata", "sortOrder": 250, - "key": "tls-earlydata", - "commandKey": "curl" + "key": "tls-earlydata" }, { "name": "TLS Max", @@ -2768,8 +2511,7 @@ "longFlag": "--tls-max", "keyValueSeparator": " ", "sortOrder": 251, - "key": "tls-max", - "commandKey": "curl" + "key": "tls-max" }, { "name": "TLS 1.3 Ciphers", @@ -2779,8 +2521,7 @@ "longFlag": "--tls13-ciphers", "keyValueSeparator": " ", "sortOrder": 252, - "key": "tls13-ciphers", - "commandKey": "curl" + "key": "tls13-ciphers" }, { "name": "TLS Auth Type", @@ -2790,8 +2531,7 @@ "longFlag": "--tlsauthtype", "keyValueSeparator": " ", "sortOrder": 253, - "key": "tlsauthtype", - "commandKey": "curl" + "key": "tlsauthtype" }, { "name": "TLS Password", @@ -2801,8 +2541,7 @@ "longFlag": "--tlspassword", "keyValueSeparator": " ", "sortOrder": 254, - "key": "tlspassword", - "commandKey": "curl" + "key": "tlspassword" }, { "name": "TLS User", @@ -2812,8 +2551,7 @@ "longFlag": "--tlsuser", "keyValueSeparator": " ", "sortOrder": 255, - "key": "tlsuser", - "commandKey": "curl" + "key": "tlsuser" }, { "name": "TLS v1", @@ -2823,8 +2561,7 @@ "shortFlag": "-1", "longFlag": "--tlsv1", "sortOrder": 256, - "key": "tlsv1", - "commandKey": "curl" + "key": "tlsv1" }, { "name": "TLS v1.0", @@ -2833,8 +2570,7 @@ "dataType": "Boolean", "longFlag": "--tlsv1.0", "sortOrder": 257, - "key": "tlsv10", - "commandKey": "curl" + "key": "tlsv10" }, { "name": "TLS v1.1", @@ -2843,8 +2579,7 @@ "dataType": "Boolean", "longFlag": "--tlsv1.1", "sortOrder": 258, - "key": "tlsv11", - "commandKey": "curl" + "key": "tlsv11" }, { "name": "TLS v1.2", @@ -2853,8 +2588,7 @@ "dataType": "Boolean", "longFlag": "--tlsv1.2", "sortOrder": 259, - "key": "tlsv12", - "commandKey": "curl" + "key": "tlsv12" }, { "name": "TLS v1.3", @@ -2863,8 +2597,7 @@ "dataType": "Boolean", "longFlag": "--tlsv1.3", "sortOrder": 260, - "key": "tlsv13", - "commandKey": "curl" + "key": "tlsv13" }, { "name": "Transfer-Encoding", @@ -2873,8 +2606,7 @@ "dataType": "Boolean", "longFlag": "--tr-encoding", "sortOrder": 261, - "key": "tr-encoding", - "commandKey": "curl" + "key": "tr-encoding" }, { "name": "Trace", @@ -2884,8 +2616,7 @@ "longFlag": "--trace", "keyValueSeparator": " ", "sortOrder": 262, - "key": "trace", - "commandKey": "curl" + "key": "trace" }, { "name": "Trace ASCII", @@ -2895,8 +2626,7 @@ "longFlag": "--trace-ascii", "keyValueSeparator": " ", "sortOrder": 263, - "key": "trace-ascii", - "commandKey": "curl" + "key": "trace-ascii" }, { "name": "Trace Config", @@ -2906,8 +2636,7 @@ "longFlag": "--trace-config", "keyValueSeparator": " ", "sortOrder": 264, - "key": "trace-config", - "commandKey": "curl" + "key": "trace-config" }, { "name": "Trace IDs", @@ -2916,8 +2645,7 @@ "dataType": "Boolean", "longFlag": "--trace-ids", "sortOrder": 265, - "key": "trace-ids", - "commandKey": "curl" + "key": "trace-ids" }, { "name": "Trace Time", @@ -2926,8 +2654,7 @@ "dataType": "Boolean", "longFlag": "--trace-time", "sortOrder": 266, - "key": "trace-time", - "commandKey": "curl" + "key": "trace-time" }, { "name": "Unix Socket", @@ -2937,8 +2664,7 @@ "longFlag": "--unix-socket", "keyValueSeparator": " ", "sortOrder": 267, - "key": "unix-socket", - "commandKey": "curl" + "key": "unix-socket" }, { "name": "Upload Flags", @@ -2948,8 +2674,7 @@ "longFlag": "--upload-flags", "keyValueSeparator": " ", "sortOrder": 268, - "key": "upload-flags", - "commandKey": "curl" + "key": "upload-flags" }, { "name": "URL Query", @@ -2960,8 +2685,7 @@ "longFlag": "--url-query", "keyValueSeparator": " ", "sortOrder": 269, - "key": "url-query", - "commandKey": "curl" + "key": "url-query" }, { "name": "Use ASCII", @@ -2971,8 +2695,7 @@ "shortFlag": "-B", "longFlag": "--use-ascii", "sortOrder": 270, - "key": "use-ascii", - "commandKey": "curl" + "key": "use-ascii" }, { "name": "Variable", @@ -2983,8 +2706,7 @@ "longFlag": "--variable", "keyValueSeparator": " ", "sortOrder": 271, - "key": "variable", - "commandKey": "curl" + "key": "variable" }, { "name": "VLAN Priority", @@ -2994,8 +2716,7 @@ "longFlag": "--vlan-priority", "keyValueSeparator": " ", "sortOrder": 272, - "key": "vlan-priority", - "commandKey": "curl" + "key": "vlan-priority" }, { "name": "Write Out", @@ -3006,8 +2727,7 @@ "longFlag": "--write-out", "keyValueSeparator": " ", "sortOrder": 273, - "key": "write-out", - "commandKey": "curl" + "key": "write-out" }, { "name": "Xattr", @@ -3016,8 +2736,7 @@ "dataType": "Boolean", "longFlag": "--xattr", "sortOrder": 274, - "key": "xattr", - "commandKey": "curl" + "key": "xattr" } ] } diff --git a/public/tools-collection/dnsx.json b/public/tools-collection/dnsx.json index c9bd643..2515a97 100644 --- a/public/tools-collection/dnsx.json +++ b/public/tools-collection/dnsx.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "dnsx", + "binaryName": "dnsx", "displayName": "DNSX", "info": { "description": "A fast and multi-purpose DNS toolkit designed for running DNS queries.", "version": "1.2.3", "url": "https://github.com/projectdiscovery/dnsx" }, - "commands": [ - { - "name": "dnsx", - "description": "Root command for dnsx CLI.", - "sortOrder": 1, - "key": "dnsx" - } - ], + "commands": [], "parameters": [ { "name": "List", @@ -23,8 +16,7 @@ "dataType": "String", "shortFlag": "-l", "longFlag": "-list", - "key": "list", - "commandKey": "dnsx" + "key": "list" }, { "name": "Domain", @@ -33,8 +25,7 @@ "dataType": "String", "shortFlag": "-d", "longFlag": "-domain", - "key": "domain", - "commandKey": "dnsx" + "key": "domain" }, { "name": "Wordlist", @@ -43,8 +34,7 @@ "dataType": "String", "shortFlag": "-w", "longFlag": "-wordlist", - "key": "wordlist", - "commandKey": "dnsx" + "key": "wordlist" }, { "name": "A", @@ -52,8 +42,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-a", - "key": "a", - "commandKey": "dnsx" + "key": "a" }, { "name": "AAAA", @@ -61,8 +50,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-aaaa", - "key": "aaaa", - "commandKey": "dnsx" + "key": "aaaa" }, { "name": "CNAME", @@ -70,8 +58,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-cname", - "key": "cname", - "commandKey": "dnsx" + "key": "cname" }, { "name": "NS", @@ -79,8 +66,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-ns", - "key": "ns", - "commandKey": "dnsx" + "key": "ns" }, { "name": "TXT", @@ -88,8 +74,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-txt", - "key": "txt", - "commandKey": "dnsx" + "key": "txt" }, { "name": "SRV", @@ -97,8 +82,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-srv", - "key": "srv", - "commandKey": "dnsx" + "key": "srv" }, { "name": "PTR", @@ -106,8 +90,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-ptr", - "key": "ptr", - "commandKey": "dnsx" + "key": "ptr" }, { "name": "MX", @@ -115,8 +98,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-mx", - "key": "mx", - "commandKey": "dnsx" + "key": "mx" }, { "name": "SOA", @@ -124,8 +106,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-soa", - "key": "soa", - "commandKey": "dnsx" + "key": "soa" }, { "name": "ANY", @@ -133,8 +114,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-any", - "key": "any", - "commandKey": "dnsx" + "key": "any" }, { "name": "AXFR", @@ -142,8 +122,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-axfr", - "key": "axfr", - "commandKey": "dnsx" + "key": "axfr" }, { "name": "CAA", @@ -151,8 +130,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-caa", - "key": "caa", - "commandKey": "dnsx" + "key": "caa" }, { "name": "Recon", @@ -160,8 +138,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-recon", - "key": "recon", - "commandKey": "dnsx" + "key": "recon" }, { "name": "Exclude Type", @@ -171,7 +148,6 @@ "shortFlag": "-e", "longFlag": "-exclude-type", "key": "exclude-type", - "commandKey": "dnsx", "enum": { "allowMultiple": true, "values": [ @@ -229,8 +205,7 @@ "dataType": "Boolean", "shortFlag": "-re", "longFlag": "-resp", - "key": "resp", - "commandKey": "dnsx" + "key": "resp" }, { "name": "Response Only", @@ -239,8 +214,7 @@ "dataType": "Boolean", "shortFlag": "-ro", "longFlag": "-resp-only", - "key": "resp-only", - "commandKey": "dnsx" + "key": "resp-only" }, { "name": "Response Code", @@ -249,8 +223,7 @@ "dataType": "String", "shortFlag": "-rc", "longFlag": "-rcode", - "key": "rcode", - "commandKey": "dnsx" + "key": "rcode" }, { "name": "CDN", @@ -258,8 +231,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-cdn", - "key": "cdn", - "commandKey": "dnsx" + "key": "cdn" }, { "name": "ASN", @@ -267,8 +239,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-asn", - "key": "asn", - "commandKey": "dnsx" + "key": "asn" }, { "name": "Threads", @@ -277,8 +248,7 @@ "dataType": "Number", "shortFlag": "-t", "longFlag": "-threads", - "key": "threads", - "commandKey": "dnsx" + "key": "threads" }, { "name": "Rate Limit", @@ -287,8 +257,7 @@ "dataType": "Number", "shortFlag": "-rl", "longFlag": "-rate-limit", - "key": "rate-limit", - "commandKey": "dnsx" + "key": "rate-limit" }, { "name": "Update", @@ -297,8 +266,7 @@ "dataType": "Boolean", "shortFlag": "-up", "longFlag": "-update", - "key": "update", - "commandKey": "dnsx" + "key": "update" }, { "name": "Disable Update Check", @@ -307,8 +275,7 @@ "dataType": "Boolean", "shortFlag": "-duc", "longFlag": "-disable-update-check", - "key": "disable-update-check", - "commandKey": "dnsx" + "key": "disable-update-check" }, { "name": "Output", @@ -317,8 +284,7 @@ "dataType": "String", "shortFlag": "-o", "longFlag": "-output", - "key": "output", - "commandKey": "dnsx" + "key": "output" }, { "name": "JSON Output", @@ -327,8 +293,7 @@ "dataType": "Boolean", "shortFlag": "-j", "longFlag": "-json", - "key": "json", - "commandKey": "dnsx" + "key": "json" }, { "name": "Omit Raw", @@ -337,8 +302,7 @@ "dataType": "Boolean", "shortFlag": "-or", "longFlag": "-omit-raw", - "key": "or", - "commandKey": "dnsx" + "key": "or" }, { "name": "Health Check", @@ -346,8 +310,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-health-check", - "key": "health-check", - "commandKey": "dnsx" + "key": "health-check" }, { "name": "Verbose", @@ -356,8 +319,7 @@ "dataType": "Boolean", "shortFlag": "-v", "longFlag": "-verbose", - "key": "verbose", - "commandKey": "dnsx" + "key": "verbose" }, { "name": "No Color", @@ -365,8 +327,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-no-color", - "key": "no-color", - "commandKey": "dnsx" + "key": "no-color" }, { "name": "Silent", @@ -374,8 +335,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-silent", - "key": "silent", - "commandKey": "dnsx" + "key": "silent" }, { "name": "Version", @@ -383,8 +343,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-version", - "key": "v", - "commandKey": "dnsx" + "key": "v" }, { "name": "Show Config", @@ -392,8 +351,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-sc", - "key": "sc", - "commandKey": "dnsx" + "key": "sc" } ] } diff --git a/public/tools-collection/gospider.json b/public/tools-collection/gospider.json index 70404d0..a980c00 100644 --- a/public/tools-collection/gospider.json +++ b/public/tools-collection/gospider.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "gospider", + "binaryName": "gospider", "displayName": "GoSpider", "info": { "description": "Fast web spider written in Go.", "version": "1.1.6", "url": "https://github.com/jaeles-project/gospider" }, - "commands": [ - { - "name": "gospider", - "description": "Fast web spider written in Go.", - "sortOrder": 1, - "key": "gospider" - } - ], + "commands": [], "parameters": [ { "name": "Site", @@ -24,8 +17,7 @@ "shortFlag": "-s", "longFlag": "--site", "sortOrder": 1, - "key": "site", - "commandKey": "gospider" + "key": "site" }, { "name": "Sites", @@ -35,8 +27,7 @@ "shortFlag": "-S", "longFlag": "--sites", "sortOrder": 2, - "key": "sites", - "commandKey": "gospider" + "key": "sites" }, { "name": "Proxy", @@ -46,8 +37,7 @@ "shortFlag": "-p", "longFlag": "--proxy", "sortOrder": 3, - "key": "proxy", - "commandKey": "gospider" + "key": "proxy" }, { "name": "Output", @@ -57,8 +47,7 @@ "shortFlag": "-o", "longFlag": "--output", "sortOrder": 4, - "key": "output", - "commandKey": "gospider" + "key": "output" }, { "name": "User Agent", @@ -68,8 +57,7 @@ "shortFlag": "-u", "longFlag": "--user-agent", "sortOrder": 5, - "key": "user-agent", - "commandKey": "gospider" + "key": "user-agent" }, { "name": "Cookie", @@ -78,8 +66,7 @@ "dataType": "String", "longFlag": "--cookie", "sortOrder": 6, - "key": "cookie", - "commandKey": "gospider" + "key": "cookie" }, { "name": "Header", @@ -90,8 +77,7 @@ "shortFlag": "-H", "longFlag": "--header", "sortOrder": 7, - "key": "header", - "commandKey": "gospider" + "key": "header" }, { "name": "Burp", @@ -100,8 +86,7 @@ "dataType": "String", "longFlag": "--burp", "sortOrder": 8, - "key": "burp", - "commandKey": "gospider" + "key": "burp" }, { "name": "Blacklist", @@ -110,8 +95,7 @@ "dataType": "String", "longFlag": "--blacklist", "sortOrder": 9, - "key": "blacklist", - "commandKey": "gospider" + "key": "blacklist" }, { "name": "Whitelist", @@ -120,8 +104,7 @@ "dataType": "String", "longFlag": "--whitelist", "sortOrder": 10, - "key": "whitelist", - "commandKey": "gospider" + "key": "whitelist" }, { "name": "Whitelist Domain", @@ -130,8 +113,7 @@ "dataType": "String", "longFlag": "--whitelist-domain", "sortOrder": 11, - "key": "whitelist-domain", - "commandKey": "gospider" + "key": "whitelist-domain" }, { "name": "Threads", @@ -141,8 +123,7 @@ "shortFlag": "-t", "longFlag": "--threads", "sortOrder": 12, - "key": "threads", - "commandKey": "gospider" + "key": "threads" }, { "name": "Concurrent", @@ -152,8 +133,7 @@ "shortFlag": "-c", "longFlag": "--concurrent", "sortOrder": 13, - "key": "concurrent", - "commandKey": "gospider" + "key": "concurrent" }, { "name": "Depth", @@ -163,8 +143,7 @@ "shortFlag": "-d", "longFlag": "--depth", "sortOrder": 14, - "key": "depth", - "commandKey": "gospider" + "key": "depth" }, { "name": "Delay", @@ -174,8 +153,7 @@ "shortFlag": "-k", "longFlag": "--delay", "sortOrder": 15, - "key": "delay", - "commandKey": "gospider" + "key": "delay" }, { "name": "Random Delay", @@ -185,8 +163,7 @@ "shortFlag": "-K", "longFlag": "--random-delay", "sortOrder": 16, - "key": "random-delay", - "commandKey": "gospider" + "key": "random-delay" }, { "name": "Timeout", @@ -196,8 +173,7 @@ "shortFlag": "-m", "longFlag": "--timeout", "sortOrder": 17, - "key": "timeout", - "commandKey": "gospider" + "key": "timeout" }, { "name": "Base", @@ -206,8 +182,7 @@ "dataType": "Boolean", "longFlag": "--base", "sortOrder": 18, - "key": "base", - "commandKey": "gospider" + "key": "base" }, { "name": "JavaScript", @@ -216,8 +191,7 @@ "dataType": "Boolean", "longFlag": "--js", "sortOrder": 19, - "key": "javascript", - "commandKey": "gospider" + "key": "javascript" }, { "name": "Include Subdomains", @@ -226,8 +200,7 @@ "dataType": "Boolean", "longFlag": "--subs", "sortOrder": 20, - "key": "include-subdomains", - "commandKey": "gospider" + "key": "include-subdomains" }, { "name": "Sitemap", @@ -236,8 +209,7 @@ "dataType": "Boolean", "longFlag": "--sitemap", "sortOrder": 21, - "key": "sitemap", - "commandKey": "gospider" + "key": "sitemap" }, { "name": "Robots", @@ -246,8 +218,7 @@ "dataType": "Boolean", "longFlag": "--robots", "sortOrder": 22, - "key": "robots", - "commandKey": "gospider" + "key": "robots" }, { "name": "Other Source", @@ -257,8 +228,7 @@ "shortFlag": "-a", "longFlag": "--other-source", "sortOrder": 23, - "key": "other-source", - "commandKey": "gospider" + "key": "other-source" }, { "name": "Include Third-Party Subdomains", @@ -268,8 +238,7 @@ "shortFlag": "-w", "longFlag": "--include-subs", "sortOrder": 24, - "key": "include-subdomains-from-third-party", - "commandKey": "gospider" + "key": "include-subdomains-from-third-party" }, { "name": "Include Other Source URLs", @@ -279,8 +248,7 @@ "shortFlag": "-r", "longFlag": "--include-other-source", "sortOrder": 25, - "key": "include-other-source-urls", - "commandKey": "gospider" + "key": "include-other-source-urls" }, { "name": "Debug", @@ -289,8 +257,7 @@ "dataType": "Boolean", "longFlag": "--debug", "sortOrder": 26, - "key": "debug", - "commandKey": "gospider" + "key": "debug" }, { "name": "JSON Output", @@ -299,8 +266,7 @@ "dataType": "Boolean", "longFlag": "--json", "sortOrder": 27, - "key": "json-output", - "commandKey": "gospider" + "key": "json-output" }, { "name": "Verbose", @@ -310,8 +276,7 @@ "shortFlag": "-v", "longFlag": "--verbose", "sortOrder": 28, - "key": "verbose", - "commandKey": "gospider" + "key": "verbose" }, { "name": "Length", @@ -321,8 +286,7 @@ "shortFlag": "-l", "longFlag": "--length", "sortOrder": 29, - "key": "length", - "commandKey": "gospider" + "key": "length" }, { "name": "Filter Length", @@ -332,8 +296,7 @@ "shortFlag": "-L", "longFlag": "--filter-length", "sortOrder": 30, - "key": "filter-length", - "commandKey": "gospider" + "key": "filter-length" }, { "name": "Raw", @@ -343,8 +306,7 @@ "shortFlag": "-R", "longFlag": "--raw", "sortOrder": 31, - "key": "raw", - "commandKey": "gospider" + "key": "raw" }, { "name": "Quiet", @@ -354,8 +316,7 @@ "shortFlag": "-q", "longFlag": "--quiet", "sortOrder": 32, - "key": "quiet", - "commandKey": "gospider" + "key": "quiet" }, { "name": "No Redirect", @@ -364,8 +325,7 @@ "dataType": "Boolean", "longFlag": "--no-redirect", "sortOrder": 33, - "key": "no-redirect", - "commandKey": "gospider" + "key": "no-redirect" }, { "name": "Version", @@ -374,8 +334,7 @@ "dataType": "Boolean", "longFlag": "--version", "sortOrder": 34, - "key": "version", - "commandKey": "gospider" + "key": "version" }, { "name": "Help", @@ -385,8 +344,7 @@ "shortFlag": "-h", "longFlag": "--help", "sortOrder": 35, - "key": "help", - "commandKey": "gospider" + "key": "help" } ] } diff --git a/public/tools-collection/httpx.json b/public/tools-collection/httpx.json index a2083c5..e5624ad 100644 --- a/public/tools-collection/httpx.json +++ b/public/tools-collection/httpx.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "httpx", + "binaryName": "httpx", "displayName": "Httpx", "info": { "description": "Httpx is a fast and multi-purpose HTTP toolkit that allows running multiple probes using the retryablehttp library.", "version": "1.9.0", "url": "https://github.com/projectdiscovery/httpx" }, - "commands": [ - { - "name": "httpx", - "description": "Run httpx with the specified parameters.", - "sortOrder": 1, - "key": "httpx" - } - ], + "commands": [], "parameters": [ { "name": "List", @@ -24,8 +17,7 @@ "shortFlag": "-l", "longFlag": "-list", "sortOrder": 1, - "key": "list", - "commandKey": "httpx" + "key": "list" }, { "name": "Request", @@ -35,8 +27,7 @@ "shortFlag": "-rr", "longFlag": "-request", "sortOrder": 2, - "key": "request", - "commandKey": "httpx" + "key": "request" }, { "name": "Target", @@ -47,8 +38,7 @@ "shortFlag": "-u", "longFlag": "-target", "sortOrder": 3, - "key": "target", - "commandKey": "httpx" + "key": "target" }, { "name": "Input Mode", @@ -58,8 +48,7 @@ "shortFlag": "-im", "longFlag": "-input-mode", "sortOrder": 4, - "key": "input-mode", - "commandKey": "httpx" + "key": "input-mode" }, { "name": "Status Code", @@ -69,8 +58,7 @@ "shortFlag": "-sc", "longFlag": "-status-code", "sortOrder": 5, - "key": "status-code", - "commandKey": "httpx" + "key": "status-code" }, { "name": "Content Length", @@ -80,8 +68,7 @@ "shortFlag": "-cl", "longFlag": "-content-length", "sortOrder": 6, - "key": "content-length", - "commandKey": "httpx" + "key": "content-length" }, { "name": "Content Type", @@ -91,8 +78,7 @@ "shortFlag": "-ct", "longFlag": "-content-type", "sortOrder": 7, - "key": "content-type", - "commandKey": "httpx" + "key": "content-type" }, { "name": "Location", @@ -101,8 +87,7 @@ "dataType": "Boolean", "longFlag": "-location", "sortOrder": 8, - "key": "location", - "commandKey": "httpx" + "key": "location" }, { "name": "Favicon", @@ -111,8 +96,7 @@ "dataType": "Boolean", "longFlag": "-favicon", "sortOrder": 9, - "key": "favicon", - "commandKey": "httpx" + "key": "favicon" }, { "name": "Hash", @@ -122,7 +106,6 @@ "longFlag": "-hash", "sortOrder": 10, "key": "hash", - "commandKey": "httpx", "enum": { "values": [ { @@ -159,8 +142,7 @@ "dataType": "Boolean", "longFlag": "-jarm", "sortOrder": 11, - "key": "jarm", - "commandKey": "httpx" + "key": "jarm" }, { "name": "Response Time", @@ -170,8 +152,7 @@ "shortFlag": "-rt", "longFlag": "-response-time", "sortOrder": 12, - "key": "response-time", - "commandKey": "httpx" + "key": "response-time" }, { "name": "Line Count", @@ -181,8 +162,7 @@ "shortFlag": "-lc", "longFlag": "-line-count", "sortOrder": 13, - "key": "line-count", - "commandKey": "httpx" + "key": "line-count" }, { "name": "Word Count", @@ -192,8 +172,7 @@ "shortFlag": "-wc", "longFlag": "-word-count", "sortOrder": 14, - "key": "word-count", - "commandKey": "httpx" + "key": "word-count" }, { "name": "Title", @@ -202,8 +181,7 @@ "dataType": "Boolean", "longFlag": "-title", "sortOrder": 15, - "key": "title", - "commandKey": "httpx" + "key": "title" }, { "name": "Body Preview", @@ -213,8 +191,7 @@ "shortFlag": "-bp", "longFlag": "-body-preview", "sortOrder": 16, - "key": "body-preview", - "commandKey": "httpx" + "key": "body-preview" }, { "name": "Server", @@ -224,8 +201,7 @@ "shortFlag": "-server", "longFlag": "-web-server", "sortOrder": 17, - "key": "web-server", - "commandKey": "httpx" + "key": "web-server" }, { "name": "Tech Detect", @@ -235,8 +211,7 @@ "shortFlag": "-td", "longFlag": "-tech-detect", "sortOrder": 18, - "key": "tech-detect", - "commandKey": "httpx" + "key": "tech-detect" }, { "name": "Custom Fingerprint File", @@ -246,8 +221,7 @@ "shortFlag": "-cff", "longFlag": "-custom-fingerprint-file", "sortOrder": 19, - "key": "custom-fingerprint-file", - "commandKey": "httpx" + "key": "custom-fingerprint-file" }, { "name": "CPE", @@ -256,8 +230,7 @@ "dataType": "Boolean", "longFlag": "-cpe", "sortOrder": 20, - "key": "cpe", - "commandKey": "httpx" + "key": "cpe" }, { "name": "Wordpress", @@ -267,8 +240,7 @@ "shortFlag": "-wp", "longFlag": "-wordpress", "sortOrder": 21, - "key": "wordpress", - "commandKey": "httpx" + "key": "wordpress" }, { "name": "Method", @@ -277,8 +249,7 @@ "dataType": "Boolean", "longFlag": "-method", "sortOrder": 22, - "key": "method", - "commandKey": "httpx" + "key": "method" }, { "name": "WebSocket", @@ -288,8 +259,7 @@ "shortFlag": "-ws", "longFlag": "-websocket", "sortOrder": 23, - "key": "websocket", - "commandKey": "httpx" + "key": "websocket" }, { "name": "IP", @@ -298,8 +268,7 @@ "dataType": "Boolean", "longFlag": "-ip", "sortOrder": 24, - "key": "ip", - "commandKey": "httpx" + "key": "ip" }, { "name": "CNAME", @@ -308,8 +277,7 @@ "dataType": "Boolean", "longFlag": "-cname", "sortOrder": 25, - "key": "cname", - "commandKey": "httpx" + "key": "cname" }, { "name": "Extract FQDN", @@ -319,8 +287,7 @@ "shortFlag": "-efqdn", "longFlag": "-extract-fqdn", "sortOrder": 26, - "key": "extract-fqdn", - "commandKey": "httpx" + "key": "extract-fqdn" }, { "name": "ASN", @@ -329,8 +296,7 @@ "dataType": "Boolean", "longFlag": "-asn", "sortOrder": 27, - "key": "asn", - "commandKey": "httpx" + "key": "asn" }, { "name": "CDN", @@ -339,8 +305,7 @@ "dataType": "Boolean", "longFlag": "-cdn", "sortOrder": 28, - "key": "cdn", - "commandKey": "httpx" + "key": "cdn" }, { "name": "Probe", @@ -349,8 +314,7 @@ "dataType": "Boolean", "longFlag": "-probe", "sortOrder": 29, - "key": "probe", - "commandKey": "httpx" + "key": "probe" }, { "name": "Screenshot", @@ -360,8 +324,7 @@ "shortFlag": "-ss", "longFlag": "-screenshot", "sortOrder": 30, - "key": "screenshot", - "commandKey": "httpx" + "key": "screenshot" }, { "name": "System Chrome", @@ -370,8 +333,7 @@ "dataType": "Boolean", "longFlag": "-system-chrome", "sortOrder": 31, - "key": "system-chrome", - "commandKey": "httpx" + "key": "system-chrome" }, { "name": "Headless Options", @@ -382,8 +344,7 @@ "shortFlag": "-ho", "longFlag": "-headless-options", "sortOrder": 32, - "key": "headless-options", - "commandKey": "httpx" + "key": "headless-options" }, { "name": "Exclude Screenshot Bytes", @@ -393,8 +354,7 @@ "shortFlag": "-esb", "longFlag": "-exclude-screenshot-bytes", "sortOrder": 33, - "key": "exclude-screenshot-bytes", - "commandKey": "httpx" + "key": "exclude-screenshot-bytes" }, { "name": "No Screenshot Full Page", @@ -403,8 +363,7 @@ "dataType": "Boolean", "longFlag": "-no-screenshot-full-page", "sortOrder": 34, - "key": "no-screenshot-full-page", - "commandKey": "httpx" + "key": "no-screenshot-full-page" }, { "name": "Exclude Headless Body", @@ -414,8 +373,7 @@ "shortFlag": "-ehb", "longFlag": "-exclude-headless-body", "sortOrder": 35, - "key": "exclude-headless-body", - "commandKey": "httpx" + "key": "exclude-headless-body" }, { "name": "Screenshot Timeout", @@ -425,8 +383,7 @@ "shortFlag": "-st", "longFlag": "-screenshot-timeout", "sortOrder": 36, - "key": "screenshot-timeout", - "commandKey": "httpx" + "key": "screenshot-timeout" }, { "name": "Screenshot Idle", @@ -436,8 +393,7 @@ "shortFlag": "-sid", "longFlag": "-screenshot-idle", "sortOrder": 37, - "key": "screenshot-idle", - "commandKey": "httpx" + "key": "screenshot-idle" }, { "name": "JavaScript Code", @@ -448,8 +404,7 @@ "shortFlag": "-jsc", "longFlag": "-javascript-code", "sortOrder": 38, - "key": "javascript-code", - "commandKey": "httpx" + "key": "javascript-code" }, { "name": "Match Code", @@ -459,8 +414,7 @@ "shortFlag": "-mc", "longFlag": "-match-code", "sortOrder": 39, - "key": "match-code", - "commandKey": "httpx" + "key": "match-code" }, { "name": "Match Length", @@ -470,8 +424,7 @@ "shortFlag": "-ml", "longFlag": "-match-length", "sortOrder": 40, - "key": "match-length", - "commandKey": "httpx" + "key": "match-length" }, { "name": "Match Line Count", @@ -481,8 +434,7 @@ "shortFlag": "-mlc", "longFlag": "-match-line-count", "sortOrder": 41, - "key": "match-line-count", - "commandKey": "httpx" + "key": "match-line-count" }, { "name": "Match Word Count", @@ -492,8 +444,7 @@ "shortFlag": "-mwc", "longFlag": "-match-word-count", "sortOrder": 42, - "key": "match-word-count", - "commandKey": "httpx" + "key": "match-word-count" }, { "name": "Match Favicon", @@ -504,8 +455,7 @@ "shortFlag": "-mfc", "longFlag": "-match-favicon", "sortOrder": 43, - "key": "match-favicon", - "commandKey": "httpx" + "key": "match-favicon" }, { "name": "Match String", @@ -516,8 +466,7 @@ "shortFlag": "-ms", "longFlag": "-match-string", "sortOrder": 44, - "key": "match-string", - "commandKey": "httpx" + "key": "match-string" }, { "name": "Match Regex", @@ -528,8 +477,7 @@ "shortFlag": "-mr", "longFlag": "-match-regex", "sortOrder": 45, - "key": "match-regex", - "commandKey": "httpx" + "key": "match-regex" }, { "name": "Match CDN", @@ -541,7 +489,6 @@ "longFlag": "-match-cdn", "sortOrder": 46, "key": "match-cdn", - "commandKey": "httpx", "enum": { "allowMultiple": true, "values": [ @@ -620,8 +567,7 @@ "shortFlag": "-mrt", "longFlag": "-match-response-time", "sortOrder": 47, - "key": "match-response-time", - "commandKey": "httpx" + "key": "match-response-time" }, { "name": "Match Condition", @@ -631,8 +577,7 @@ "shortFlag": "-mdc", "longFlag": "-match-condition", "sortOrder": 48, - "key": "match-condition", - "commandKey": "httpx" + "key": "match-condition" }, { "name": "Extract Regex", @@ -643,8 +588,7 @@ "shortFlag": "-er", "longFlag": "-extract-regex", "sortOrder": 49, - "key": "extract-regex", - "commandKey": "httpx" + "key": "extract-regex" }, { "name": "Extract Preset", @@ -656,7 +600,6 @@ "longFlag": "-extract-preset", "sortOrder": 50, "key": "extract-preset", - "commandKey": "httpx", "enum": { "values": [ { @@ -682,8 +625,7 @@ "shortFlag": "-fc", "longFlag": "-filter-code", "sortOrder": 51, - "key": "filter-code", - "commandKey": "httpx" + "key": "filter-code" }, { "name": "Filter Page Type", @@ -695,7 +637,6 @@ "longFlag": "-filter-page-type", "sortOrder": 52, "key": "filter-page-type", - "commandKey": "httpx", "enum": { "values": [ { @@ -789,8 +730,7 @@ "shortFlag": "-fep", "longFlag": "-filter-error-page", "sortOrder": 53, - "key": "filter-error-page", - "commandKey": "httpx" + "key": "filter-error-page" }, { "name": "Filter Duplicates", @@ -800,8 +740,7 @@ "shortFlag": "-fd", "longFlag": "-filter-duplicates", "sortOrder": 54, - "key": "filter-duplicates", - "commandKey": "httpx" + "key": "filter-duplicates" }, { "name": "Filter Length", @@ -811,8 +750,7 @@ "shortFlag": "-fl", "longFlag": "-filter-length", "sortOrder": 55, - "key": "filter-length", - "commandKey": "httpx" + "key": "filter-length" }, { "name": "Filter Line Count", @@ -822,8 +760,7 @@ "shortFlag": "-flc", "longFlag": "-filter-line-count", "sortOrder": 56, - "key": "filter-line-count", - "commandKey": "httpx" + "key": "filter-line-count" }, { "name": "Filter Word Count", @@ -833,8 +770,7 @@ "shortFlag": "-fwc", "longFlag": "-filter-word-count", "sortOrder": 57, - "key": "filter-word-count", - "commandKey": "httpx" + "key": "filter-word-count" }, { "name": "Filter Favicon", @@ -845,8 +781,7 @@ "shortFlag": "-ffc", "longFlag": "-filter-favicon", "sortOrder": 58, - "key": "filter-favicon", - "commandKey": "httpx" + "key": "filter-favicon" }, { "name": "Filter String", @@ -857,8 +792,7 @@ "shortFlag": "-fs", "longFlag": "-filter-string", "sortOrder": 59, - "key": "filter-string", - "commandKey": "httpx" + "key": "filter-string" }, { "name": "Filter Regex", @@ -869,8 +803,7 @@ "shortFlag": "-fe", "longFlag": "-filter-regex", "sortOrder": 60, - "key": "filter-regex", - "commandKey": "httpx" + "key": "filter-regex" }, { "name": "Filter CDN", @@ -882,7 +815,6 @@ "longFlag": "-filter-cdn", "sortOrder": 61, "key": "filter-cdn", - "commandKey": "httpx", "enum": { "allowMultiple": true, "values": [ @@ -961,8 +893,7 @@ "shortFlag": "-frt", "longFlag": "-filter-response-time", "sortOrder": 62, - "key": "filter-response-time", - "commandKey": "httpx" + "key": "filter-response-time" }, { "name": "Filter Condition", @@ -972,8 +903,7 @@ "shortFlag": "-fdc", "longFlag": "-filter-condition", "sortOrder": 63, - "key": "filter-condition", - "commandKey": "httpx" + "key": "filter-condition" }, { "name": "Strip", @@ -982,8 +912,7 @@ "dataType": "Boolean", "longFlag": "-strip", "sortOrder": 64, - "key": "strip", - "commandKey": "httpx" + "key": "strip" }, { "name": "List Output Fields", @@ -993,8 +922,7 @@ "shortFlag": "-lof", "longFlag": "-list-output-fields", "sortOrder": 65, - "key": "list-output-fields", - "commandKey": "httpx" + "key": "list-output-fields" }, { "name": "Exclude Output Fields", @@ -1005,8 +933,7 @@ "shortFlag": "-eof", "longFlag": "-exclude-output-fields", "sortOrder": 66, - "key": "exclude-output-fields", - "commandKey": "httpx" + "key": "exclude-output-fields" }, { "name": "Threads", @@ -1016,8 +943,7 @@ "shortFlag": "-t", "longFlag": "-threads", "sortOrder": 67, - "key": "threads", - "commandKey": "httpx" + "key": "threads" }, { "name": "Rate Limit", @@ -1027,8 +953,7 @@ "shortFlag": "-rl", "longFlag": "-rate-limit", "sortOrder": 68, - "key": "rate-limit", - "commandKey": "httpx" + "key": "rate-limit" }, { "name": "Rate Limit Minute", @@ -1038,8 +963,7 @@ "shortFlag": "-rlm", "longFlag": "-rate-limit-minute", "sortOrder": 69, - "key": "rate-limit-minute", - "commandKey": "httpx" + "key": "rate-limit-minute" }, { "name": "Probe All IPs", @@ -1049,8 +973,7 @@ "shortFlag": "-pa", "longFlag": "-probe-all-ips", "sortOrder": 70, - "key": "probe-all-ips", - "commandKey": "httpx" + "key": "probe-all-ips" }, { "name": "Ports", @@ -1061,8 +984,7 @@ "shortFlag": "-p", "longFlag": "-ports", "sortOrder": 71, - "key": "ports", - "commandKey": "httpx" + "key": "ports" }, { "name": "Path", @@ -1071,8 +993,7 @@ "dataType": "String", "longFlag": "-path", "sortOrder": 72, - "key": "path", - "commandKey": "httpx" + "key": "path" }, { "name": "TLS Probe", @@ -1081,8 +1002,7 @@ "dataType": "Boolean", "longFlag": "-tls-probe", "sortOrder": 73, - "key": "tls-probe", - "commandKey": "httpx" + "key": "tls-probe" }, { "name": "CSP Probe", @@ -1091,8 +1011,7 @@ "dataType": "Boolean", "longFlag": "-csp-probe", "sortOrder": 74, - "key": "csp-probe", - "commandKey": "httpx" + "key": "csp-probe" }, { "name": "TLS Grab", @@ -1101,8 +1020,7 @@ "dataType": "Boolean", "longFlag": "-tls-grab", "sortOrder": 75, - "key": "tls-grab", - "commandKey": "httpx" + "key": "tls-grab" }, { "name": "Pipeline", @@ -1111,8 +1029,7 @@ "dataType": "Boolean", "longFlag": "-pipeline", "sortOrder": 76, - "key": "pipeline", - "commandKey": "httpx" + "key": "pipeline" }, { "name": "HTTP/2", @@ -1121,8 +1038,7 @@ "dataType": "Boolean", "longFlag": "-http2", "sortOrder": 77, - "key": "http2", - "commandKey": "httpx" + "key": "http2" }, { "name": "Vhost", @@ -1131,8 +1047,7 @@ "dataType": "Boolean", "longFlag": "-vhost", "sortOrder": 78, - "key": "vhost", - "commandKey": "httpx" + "key": "vhost" }, { "name": "List DSL Variables", @@ -1142,8 +1057,7 @@ "shortFlag": "-ldv", "longFlag": "-list-dsl-variables", "sortOrder": 79, - "key": "list-dsl-variables", - "commandKey": "httpx" + "key": "list-dsl-variables" }, { "name": "Update", @@ -1153,8 +1067,7 @@ "shortFlag": "-up", "longFlag": "-update", "sortOrder": 80, - "key": "update", - "commandKey": "httpx" + "key": "update" }, { "name": "Disable Update Check", @@ -1164,8 +1077,7 @@ "shortFlag": "-duc", "longFlag": "-disable-update-check", "sortOrder": 81, - "key": "disable-update-check", - "commandKey": "httpx" + "key": "disable-update-check" }, { "name": "Output", @@ -1175,8 +1087,7 @@ "shortFlag": "-o", "longFlag": "-output", "sortOrder": 82, - "key": "output", - "commandKey": "httpx" + "key": "output" }, { "name": "Output All", @@ -1186,8 +1097,7 @@ "shortFlag": "-oa", "longFlag": "-output-all", "sortOrder": 83, - "key": "output-all", - "commandKey": "httpx" + "key": "output-all" }, { "name": "Store Response", @@ -1197,8 +1107,7 @@ "shortFlag": "-sr", "longFlag": "-store-response", "sortOrder": 84, - "key": "store-response", - "commandKey": "httpx" + "key": "store-response" }, { "name": "Store Response Dir", @@ -1208,8 +1117,7 @@ "shortFlag": "-srd", "longFlag": "-store-response-dir", "sortOrder": 85, - "key": "store-response-dir", - "commandKey": "httpx" + "key": "store-response-dir" }, { "name": "Omit Body", @@ -1219,8 +1127,7 @@ "shortFlag": "-ob", "longFlag": "-omit-body", "sortOrder": 86, - "key": "omit-body", - "commandKey": "httpx" + "key": "omit-body" }, { "name": "CSV", @@ -1229,8 +1136,7 @@ "dataType": "Boolean", "longFlag": "-csv", "sortOrder": 87, - "key": "csv", - "commandKey": "httpx" + "key": "csv" }, { "name": "CSV Output Encoding", @@ -1240,8 +1146,7 @@ "shortFlag": "-csvo", "longFlag": "-csv-output-encoding", "sortOrder": 88, - "key": "csv-output-encoding", - "commandKey": "httpx" + "key": "csv-output-encoding" }, { "name": "JSON", @@ -1251,8 +1156,7 @@ "shortFlag": "-j", "longFlag": "-json", "sortOrder": 89, - "key": "json", - "commandKey": "httpx" + "key": "json" }, { "name": "Markdown", @@ -1262,8 +1166,7 @@ "shortFlag": "-md", "longFlag": "-markdown", "sortOrder": 90, - "key": "markdown", - "commandKey": "httpx" + "key": "markdown" }, { "name": "Include Response Header", @@ -1273,8 +1176,7 @@ "shortFlag": "-irh", "longFlag": "-include-response-header", "sortOrder": 91, - "key": "include-response-header", - "commandKey": "httpx" + "key": "include-response-header" }, { "name": "Include Response", @@ -1284,8 +1186,7 @@ "shortFlag": "-irr", "longFlag": "-include-response", "sortOrder": 92, - "key": "include-response", - "commandKey": "httpx" + "key": "include-response" }, { "name": "Include Response Base64", @@ -1295,8 +1196,7 @@ "shortFlag": "-irrb", "longFlag": "-include-response-base64", "sortOrder": 93, - "key": "include-response-base64", - "commandKey": "httpx" + "key": "include-response-base64" }, { "name": "Include Chain", @@ -1305,8 +1205,7 @@ "dataType": "Boolean", "longFlag": "-include-chain", "sortOrder": 94, - "key": "include-chain", - "commandKey": "httpx" + "key": "include-chain" }, { "name": "Store Chain", @@ -1315,8 +1214,7 @@ "dataType": "Boolean", "longFlag": "-store-chain", "sortOrder": 95, - "key": "store-chain", - "commandKey": "httpx" + "key": "store-chain" }, { "name": "Store Vision Recon Cluster", @@ -1326,8 +1224,7 @@ "shortFlag": "-svrc", "longFlag": "-store-vision-recon-cluster", "sortOrder": 96, - "key": "store-vision-recon-cluster", - "commandKey": "httpx" + "key": "store-vision-recon-cluster" }, { "name": "Protocol", @@ -1338,7 +1235,6 @@ "longFlag": "-protocol", "sortOrder": 97, "key": "protocol", - "commandKey": "httpx", "enum": { "values": [ { @@ -1368,8 +1264,7 @@ "shortFlag": "-fepp", "longFlag": "-filter-error-page-path", "sortOrder": 98, - "key": "filter-error-page-path", - "commandKey": "httpx" + "key": "filter-error-page-path" }, { "name": "Result DB", @@ -1379,8 +1274,7 @@ "shortFlag": "-rdb", "longFlag": "-result-db", "sortOrder": 99, - "key": "result-db", - "commandKey": "httpx" + "key": "result-db" }, { "name": "Result DB Config", @@ -1390,8 +1284,7 @@ "shortFlag": "-rdbc", "longFlag": "-result-db-config", "sortOrder": 100, - "key": "result-db-config", - "commandKey": "httpx" + "key": "result-db-config" }, { "name": "Result DB Type", @@ -1402,7 +1295,6 @@ "longFlag": "-result-db-type", "sortOrder": 101, "key": "result-db-type", - "commandKey": "httpx", "enum": { "values": [ { @@ -1428,8 +1320,7 @@ "shortFlag": "-rdbcs", "longFlag": "-result-db-conn", "sortOrder": 102, - "key": "result-db-conn", - "commandKey": "httpx" + "key": "result-db-conn" }, { "name": "Result DB Name", @@ -1439,8 +1330,7 @@ "shortFlag": "-rdbn", "longFlag": "-result-db-name", "sortOrder": 103, - "key": "result-db-name", - "commandKey": "httpx" + "key": "result-db-name" }, { "name": "Result DB Table", @@ -1450,8 +1340,7 @@ "shortFlag": "-rdbtb", "longFlag": "-result-db-table", "sortOrder": 104, - "key": "result-db-table", - "commandKey": "httpx" + "key": "result-db-table" }, { "name": "Result DB Batch Size", @@ -1461,8 +1350,7 @@ "shortFlag": "-rdbbs", "longFlag": "-result-db-batch-size", "sortOrder": 105, - "key": "result-db-batch-size", - "commandKey": "httpx" + "key": "result-db-batch-size" }, { "name": "Result DB Omit Raw", @@ -1472,8 +1360,7 @@ "shortFlag": "-rdbor", "longFlag": "-result-db-omit-raw", "sortOrder": 106, - "key": "result-db-omit-raw", - "commandKey": "httpx" + "key": "result-db-omit-raw" }, { "name": "Config", @@ -1482,8 +1369,7 @@ "dataType": "String", "longFlag": "-config", "sortOrder": 107, - "key": "config", - "commandKey": "httpx" + "key": "config" }, { "name": "Resolvers", @@ -1494,8 +1380,7 @@ "shortFlag": "-r", "longFlag": "-resolvers", "sortOrder": 108, - "key": "resolvers", - "commandKey": "httpx" + "key": "resolvers" }, { "name": "Allow", @@ -1505,8 +1390,7 @@ "isRepeatable": true, "longFlag": "-allow", "sortOrder": 109, - "key": "allow", - "commandKey": "httpx" + "key": "allow" }, { "name": "Deny", @@ -1516,8 +1400,7 @@ "isRepeatable": true, "longFlag": "-deny", "sortOrder": 110, - "key": "deny", - "commandKey": "httpx" + "key": "deny" }, { "name": "SNI Name", @@ -1527,8 +1410,7 @@ "shortFlag": "-sni", "longFlag": "-sni-name", "sortOrder": 111, - "key": "sni-name", - "commandKey": "httpx" + "key": "sni-name" }, { "name": "Random Agent", @@ -1537,8 +1419,7 @@ "dataType": "Boolean", "longFlag": "-random-agent", "sortOrder": 112, - "key": "random-agent", - "commandKey": "httpx" + "key": "random-agent" }, { "name": "Auto Referer", @@ -1547,8 +1428,7 @@ "dataType": "Boolean", "longFlag": "-auto-referer", "sortOrder": 113, - "key": "auto-referer", - "commandKey": "httpx" + "key": "auto-referer" }, { "name": "Header", @@ -1559,8 +1439,7 @@ "shortFlag": "-H", "longFlag": "-header", "sortOrder": 114, - "key": "header", - "commandKey": "httpx" + "key": "header" }, { "name": "Proxy", @@ -1570,8 +1449,7 @@ "shortFlag": "-http-proxy", "longFlag": "-proxy", "sortOrder": 115, - "key": "proxy", - "commandKey": "httpx" + "key": "proxy" }, { "name": "Unsafe", @@ -1580,8 +1458,7 @@ "dataType": "Boolean", "longFlag": "-unsafe", "sortOrder": 116, - "key": "unsafe", - "commandKey": "httpx" + "key": "unsafe" }, { "name": "Resume", @@ -1590,8 +1467,7 @@ "dataType": "Boolean", "longFlag": "-resume", "sortOrder": 117, - "key": "resume", - "commandKey": "httpx" + "key": "resume" }, { "name": "Follow Redirects", @@ -1601,8 +1477,7 @@ "shortFlag": "-fr", "longFlag": "-follow-redirects", "sortOrder": 118, - "key": "follow-redirects", - "commandKey": "httpx" + "key": "follow-redirects" }, { "name": "Max Redirects", @@ -1612,8 +1487,7 @@ "shortFlag": "-maxr", "longFlag": "-max-redirects", "sortOrder": 119, - "key": "max-redirects", - "commandKey": "httpx" + "key": "max-redirects" }, { "name": "Follow Host Redirects", @@ -1623,8 +1497,7 @@ "shortFlag": "-fhr", "longFlag": "-follow-host-redirects", "sortOrder": 120, - "key": "follow-host-redirects", - "commandKey": "httpx" + "key": "follow-host-redirects" }, { "name": "Respect HSTS", @@ -1634,8 +1507,7 @@ "shortFlag": "-rhsts", "longFlag": "-respect-hsts", "sortOrder": 121, - "key": "respect-hsts", - "commandKey": "httpx" + "key": "respect-hsts" }, { "name": "Vhost Input", @@ -1644,8 +1516,7 @@ "dataType": "Boolean", "longFlag": "-vhost-input", "sortOrder": 122, - "key": "vhost-input", - "commandKey": "httpx" + "key": "vhost-input" }, { "name": "X", @@ -1654,8 +1525,7 @@ "dataType": "String", "longFlag": "-x", "sortOrder": 123, - "key": "x", - "commandKey": "httpx" + "key": "x" }, { "name": "Body", @@ -1664,8 +1534,7 @@ "dataType": "String", "longFlag": "-body", "sortOrder": 124, - "key": "body", - "commandKey": "httpx" + "key": "body" }, { "name": "Stream", @@ -1675,8 +1544,7 @@ "shortFlag": "-s", "longFlag": "-stream", "sortOrder": 125, - "key": "stream", - "commandKey": "httpx" + "key": "stream" }, { "name": "Skip Dedupe", @@ -1686,8 +1554,7 @@ "shortFlag": "-sd", "longFlag": "-skip-dedupe", "sortOrder": 126, - "key": "skip-dedupe", - "commandKey": "httpx" + "key": "skip-dedupe" }, { "name": "Leave Default Ports", @@ -1697,8 +1564,7 @@ "shortFlag": "-ldp", "longFlag": "-leave-default-ports", "sortOrder": 127, - "key": "leave-default-ports", - "commandKey": "httpx" + "key": "leave-default-ports" }, { "name": "Ztls", @@ -1707,8 +1573,7 @@ "dataType": "Boolean", "longFlag": "-ztls", "sortOrder": 128, - "key": "ztls", - "commandKey": "httpx" + "key": "ztls" }, { "name": "No Decode", @@ -1717,8 +1582,7 @@ "dataType": "Boolean", "longFlag": "-no-decode", "sortOrder": 129, - "key": "no-decode", - "commandKey": "httpx" + "key": "no-decode" }, { "name": "TLS Impersonate", @@ -1728,8 +1592,7 @@ "shortFlag": "-tlsi", "longFlag": "-tls-impersonate", "sortOrder": 130, - "key": "tls-impersonate", - "commandKey": "httpx" + "key": "tls-impersonate" }, { "name": "No Stdin", @@ -1738,8 +1601,7 @@ "dataType": "Boolean", "longFlag": "-no-stdin", "sortOrder": 131, - "key": "no-stdin", - "commandKey": "httpx" + "key": "no-stdin" }, { "name": "HTTP API Endpoint", @@ -1749,8 +1611,7 @@ "shortFlag": "-hae", "longFlag": "-http-api-endpoint", "sortOrder": 132, - "key": "http-api-endpoint", - "commandKey": "httpx" + "key": "http-api-endpoint" }, { "name": "Secret File", @@ -1760,8 +1621,7 @@ "shortFlag": "-sf", "longFlag": "-secret-file", "sortOrder": 133, - "key": "secret-file", - "commandKey": "httpx" + "key": "secret-file" }, { "name": "Health Check", @@ -1771,8 +1631,7 @@ "shortFlag": "-hc", "longFlag": "-health-check", "sortOrder": 134, - "key": "health-check", - "commandKey": "httpx" + "key": "health-check" }, { "name": "Debug", @@ -1781,8 +1640,7 @@ "dataType": "Boolean", "longFlag": "-debug", "sortOrder": 135, - "key": "debug", - "commandKey": "httpx" + "key": "debug" }, { "name": "Debug Request", @@ -1791,8 +1649,7 @@ "dataType": "Boolean", "longFlag": "-debug-req", "sortOrder": 136, - "key": "debug-req", - "commandKey": "httpx" + "key": "debug-req" }, { "name": "Debug Response", @@ -1801,8 +1658,7 @@ "dataType": "Boolean", "longFlag": "-debug-resp", "sortOrder": 137, - "key": "debug-resp", - "commandKey": "httpx" + "key": "debug-resp" }, { "name": "Version", @@ -1811,8 +1667,7 @@ "dataType": "Boolean", "longFlag": "-version", "sortOrder": 138, - "key": "version", - "commandKey": "httpx" + "key": "version" }, { "name": "Stats", @@ -1821,8 +1676,7 @@ "dataType": "Boolean", "longFlag": "-stats", "sortOrder": 139, - "key": "stats", - "commandKey": "httpx" + "key": "stats" }, { "name": "Profile Mem", @@ -1831,8 +1685,7 @@ "dataType": "String", "longFlag": "-profile-mem", "sortOrder": 140, - "key": "profile-mem", - "commandKey": "httpx" + "key": "profile-mem" }, { "name": "Silent", @@ -1841,8 +1694,7 @@ "dataType": "Boolean", "longFlag": "-silent", "sortOrder": 141, - "key": "silent", - "commandKey": "httpx" + "key": "silent" }, { "name": "Verbose", @@ -1852,8 +1704,7 @@ "shortFlag": "-v", "longFlag": "-verbose", "sortOrder": 142, - "key": "verbose", - "commandKey": "httpx" + "key": "verbose" }, { "name": "Stats Interval", @@ -1863,8 +1714,7 @@ "shortFlag": "-si", "longFlag": "-stats-interval", "sortOrder": 143, - "key": "stats-interval", - "commandKey": "httpx" + "key": "stats-interval" }, { "name": "No Color", @@ -1874,8 +1724,7 @@ "shortFlag": "-nc", "longFlag": "-no-color", "sortOrder": 144, - "key": "no-color", - "commandKey": "httpx" + "key": "no-color" }, { "name": "Trace", @@ -1885,8 +1734,7 @@ "shortFlag": "-tr", "longFlag": "-trace", "sortOrder": 145, - "key": "trace", - "commandKey": "httpx" + "key": "trace" }, { "name": "No Fallback", @@ -1896,8 +1744,7 @@ "shortFlag": "-nf", "longFlag": "-no-fallback", "sortOrder": 146, - "key": "no-fallback", - "commandKey": "httpx" + "key": "no-fallback" }, { "name": "No Fallback Scheme", @@ -1907,8 +1754,7 @@ "shortFlag": "-nfs", "longFlag": "-no-fallback-scheme", "sortOrder": 147, - "key": "no-fallback-scheme", - "commandKey": "httpx" + "key": "no-fallback-scheme" }, { "name": "Max Host Error", @@ -1918,8 +1764,7 @@ "shortFlag": "-maxhr", "longFlag": "-max-host-error", "sortOrder": 148, - "key": "max-host-error", - "commandKey": "httpx" + "key": "max-host-error" }, { "name": "Exclude", @@ -1931,7 +1776,6 @@ "longFlag": "-exclude", "sortOrder": 149, "key": "exclude", - "commandKey": "httpx", "enum": { "values": [ { @@ -1964,8 +1808,7 @@ "dataType": "Number", "longFlag": "-retries", "sortOrder": 150, - "key": "retries", - "commandKey": "httpx" + "key": "retries" }, { "name": "Timeout", @@ -1974,8 +1817,7 @@ "dataType": "Number", "longFlag": "-timeout", "sortOrder": 151, - "key": "timeout", - "commandKey": "httpx" + "key": "timeout" }, { "name": "Delay", @@ -1984,8 +1826,7 @@ "dataType": "String", "longFlag": "-delay", "sortOrder": 152, - "key": "delay", - "commandKey": "httpx" + "key": "delay" }, { "name": "Response Size to Save", @@ -1995,8 +1836,7 @@ "shortFlag": "-rsts", "longFlag": "-response-size-to-save", "sortOrder": 153, - "key": "response-size-to-save", - "commandKey": "httpx" + "key": "response-size-to-save" }, { "name": "Response Size to Read", @@ -2006,8 +1846,7 @@ "shortFlag": "-rstr", "longFlag": "-response-size-to-read", "sortOrder": 154, - "key": "response-size-to-read", - "commandKey": "httpx" + "key": "response-size-to-read" }, { "name": "Auth", @@ -2016,8 +1855,7 @@ "dataType": "Boolean", "longFlag": "-auth", "sortOrder": 155, - "key": "auth", - "commandKey": "httpx" + "key": "auth" }, { "name": "Auth Config", @@ -2027,8 +1865,7 @@ "shortFlag": "-ac", "longFlag": "-auth-config", "sortOrder": 156, - "key": "auth-config", - "commandKey": "httpx" + "key": "auth-config" }, { "name": "Dashboard", @@ -2038,8 +1875,7 @@ "shortFlag": "-pd", "longFlag": "-dashboard", "sortOrder": 157, - "key": "dashboard", - "commandKey": "httpx" + "key": "dashboard" }, { "name": "Team ID", @@ -2049,8 +1885,7 @@ "shortFlag": "-tid", "longFlag": "-team-id", "sortOrder": 158, - "key": "team-id", - "commandKey": "httpx" + "key": "team-id" }, { "name": "Asset ID", @@ -2060,8 +1895,7 @@ "shortFlag": "-aid", "longFlag": "-asset-id", "sortOrder": 159, - "key": "asset-id", - "commandKey": "httpx" + "key": "asset-id" }, { "name": "Asset Name", @@ -2071,8 +1905,7 @@ "shortFlag": "-aname", "longFlag": "-asset-name", "sortOrder": 160, - "key": "asset-name", - "commandKey": "httpx" + "key": "asset-name" }, { "name": "Dashboard Upload", @@ -2082,8 +1915,7 @@ "shortFlag": "-pdu", "longFlag": "-dashboard-upload", "sortOrder": 161, - "key": "dashboard-upload", - "commandKey": "httpx" + "key": "dashboard-upload" } ] } diff --git a/public/tools-collection/katana.json b/public/tools-collection/katana.json index c8803d2..acb7609 100644 --- a/public/tools-collection/katana.json +++ b/public/tools-collection/katana.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "katana", + "binaryName": "katana", "displayName": "Katana", "info": { "description": "Katana is a fast crawler focused on execution in automation pipelines offering both headless and non-headless crawling.", "version": "1.5.0", "url": "https://github.com/projectdiscovery/katana" }, - "commands": [ - { - "name": "katana", - "description": "Primary command for katana crawler.", - "sortOrder": 0, - "key": "katana" - } - ], + "commands": [], "parameters": [ { "name": "List", @@ -24,8 +17,7 @@ "isRepeatable": true, "shortFlag": "-u", "longFlag": "-list", - "key": "list", - "commandKey": "katana" + "key": "list" }, { "name": "Resume", @@ -33,8 +25,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-resume", - "key": "resume", - "commandKey": "katana" + "key": "resume" }, { "name": "Exclude", @@ -44,8 +35,7 @@ "isRepeatable": true, "shortFlag": "-e", "longFlag": "-exclude", - "key": "exclude", - "commandKey": "katana" + "key": "exclude" }, { "name": "Resolvers", @@ -55,8 +45,7 @@ "isRepeatable": true, "shortFlag": "-r", "longFlag": "-resolvers", - "key": "resolvers", - "commandKey": "katana" + "key": "resolvers" }, { "name": "Depth", @@ -65,8 +54,7 @@ "dataType": "Number", "shortFlag": "-d", "longFlag": "-depth", - "key": "depth", - "commandKey": "katana" + "key": "depth" }, { "name": "JS Crawl", @@ -75,8 +63,7 @@ "dataType": "Boolean", "shortFlag": "-jc", "longFlag": "-js-crawl", - "key": "js-crawl", - "commandKey": "katana" + "key": "js-crawl" }, { "name": "JS Luice", @@ -85,8 +72,7 @@ "dataType": "Boolean", "shortFlag": "-jsl", "longFlag": "-jsluice", - "key": "jsluice", - "commandKey": "katana" + "key": "jsluice" }, { "name": "Crawl Duration", @@ -95,8 +81,7 @@ "dataType": "String", "shortFlag": "-ct", "longFlag": "-crawl-duration", - "key": "crawl-duration", - "commandKey": "katana" + "key": "crawl-duration" }, { "name": "Known Files", @@ -106,7 +91,6 @@ "shortFlag": "-kf", "longFlag": "-known-files", "key": "known-files", - "commandKey": "katana", "enum": { "values": [ { @@ -131,8 +115,7 @@ "dataType": "Number", "shortFlag": "-mrs", "longFlag": "-max-response-size", - "key": "max-response-size", - "commandKey": "katana" + "key": "max-response-size" }, { "name": "Timeout", @@ -140,8 +123,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-timeout", - "key": "timeout", - "commandKey": "katana" + "key": "timeout" }, { "name": "Time Stable", @@ -149,8 +131,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-time-stable", - "key": "time-stable", - "commandKey": "katana" + "key": "time-stable" }, { "name": "Automatic Form Fill", @@ -159,8 +140,7 @@ "dataType": "Boolean", "shortFlag": "-aff", "longFlag": "-automatic-form-fill", - "key": "automatic-form-fill", - "commandKey": "katana" + "key": "automatic-form-fill" }, { "name": "Form Extraction", @@ -169,8 +149,7 @@ "dataType": "Boolean", "shortFlag": "-fx", "longFlag": "-form-extraction", - "key": "form-extraction", - "commandKey": "katana" + "key": "form-extraction" }, { "name": "Retry", @@ -178,8 +157,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-retry", - "key": "retry", - "commandKey": "katana" + "key": "retry" }, { "name": "Proxy", @@ -187,8 +165,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-proxy", - "key": "proxy", - "commandKey": "katana" + "key": "proxy" }, { "name": "Tech Detect", @@ -197,8 +174,7 @@ "dataType": "Boolean", "shortFlag": "-td", "longFlag": "-tech-detect", - "key": "tech-detect", - "commandKey": "katana" + "key": "tech-detect" }, { "name": "Headers", @@ -208,8 +184,7 @@ "isRepeatable": true, "shortFlag": "-H", "longFlag": "-headers", - "key": "headers", - "commandKey": "katana" + "key": "headers" }, { "name": "Config", @@ -217,8 +192,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-config", - "key": "config", - "commandKey": "katana" + "key": "config" }, { "name": "Form Config", @@ -227,8 +201,7 @@ "dataType": "String", "shortFlag": "-fc", "longFlag": "-form-config", - "key": "form-config", - "commandKey": "katana" + "key": "form-config" }, { "name": "Field Config", @@ -237,8 +210,7 @@ "dataType": "String", "shortFlag": "-flc", "longFlag": "-field-config", - "key": "field-config", - "commandKey": "katana" + "key": "field-config" }, { "name": "Strategy", @@ -248,7 +220,6 @@ "shortFlag": "-s", "longFlag": "-strategy", "key": "strategy", - "commandKey": "katana", "enum": { "values": [ { @@ -269,8 +240,7 @@ "dataType": "Boolean", "shortFlag": "-iqp", "longFlag": "-ignore-query-params", - "key": "ignore-query-params", - "commandKey": "katana" + "key": "ignore-query-params" }, { "name": "Filter Similar", @@ -279,8 +249,7 @@ "dataType": "Boolean", "shortFlag": "-fsu", "longFlag": "-filter-similar", - "key": "filter-similar", - "commandKey": "katana" + "key": "filter-similar" }, { "name": "Filter Similar Threshold", @@ -289,8 +258,7 @@ "dataType": "Number", "shortFlag": "-fst", "longFlag": "-filter-similar-threshold", - "key": "filter-similar-threshold", - "commandKey": "katana" + "key": "filter-similar-threshold" }, { "name": "TLS Impersonate", @@ -299,8 +267,7 @@ "dataType": "Boolean", "shortFlag": "-tlsi", "longFlag": "-tls-impersonate", - "key": "tls-impersonate", - "commandKey": "katana" + "key": "tls-impersonate" }, { "name": "Disable Redirects", @@ -309,8 +276,7 @@ "dataType": "Boolean", "shortFlag": "-dr", "longFlag": "-disable-redirects", - "key": "disable-redirects", - "commandKey": "katana" + "key": "disable-redirects" }, { "name": "Path Climb", @@ -319,8 +285,7 @@ "dataType": "Boolean", "shortFlag": "-pc", "longFlag": "-path-climb", - "key": "path-climb", - "commandKey": "katana" + "key": "path-climb" }, { "name": "Knowledge Base", @@ -329,8 +294,7 @@ "dataType": "Boolean", "shortFlag": "-kb", "longFlag": "-knowledge-base", - "key": "knowledge-base", - "commandKey": "katana" + "key": "knowledge-base" }, { "name": "Health Check", @@ -339,8 +303,7 @@ "dataType": "Boolean", "shortFlag": "-hc", "longFlag": "-health-check", - "key": "health-check", - "commandKey": "katana" + "key": "health-check" }, { "name": "Error Log", @@ -349,8 +312,7 @@ "dataType": "String", "shortFlag": "-elog", "longFlag": "-error-log", - "key": "error-log", - "commandKey": "katana" + "key": "error-log" }, { "name": "Pprof Server", @@ -358,8 +320,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-pprof-server", - "key": "pprof-server", - "commandKey": "katana" + "key": "pprof-server" }, { "name": "Headless", @@ -368,8 +329,7 @@ "dataType": "Boolean", "shortFlag": "-hl", "longFlag": "-headless", - "key": "headless", - "commandKey": "katana" + "key": "headless" }, { "name": "Hybrid", @@ -378,8 +338,7 @@ "dataType": "Boolean", "shortFlag": "-hh", "longFlag": "-hybrid", - "key": "hybrid", - "commandKey": "katana" + "key": "hybrid" }, { "name": "System Chrome", @@ -388,8 +347,7 @@ "dataType": "Boolean", "shortFlag": "-sc", "longFlag": "-system-chrome", - "key": "system-chrome", - "commandKey": "katana" + "key": "system-chrome" }, { "name": "Show Browser", @@ -398,8 +356,7 @@ "dataType": "Boolean", "shortFlag": "-sb", "longFlag": "-show-browser", - "key": "show-browser", - "commandKey": "katana" + "key": "show-browser" }, { "name": "Headless Options", @@ -409,8 +366,7 @@ "isRepeatable": true, "shortFlag": "-ho", "longFlag": "-headless-options", - "key": "headless-options", - "commandKey": "katana" + "key": "headless-options" }, { "name": "No Sandbox", @@ -419,8 +375,7 @@ "dataType": "Boolean", "shortFlag": "-nos", "longFlag": "-no-sandbox", - "key": "no-sandbox", - "commandKey": "katana" + "key": "no-sandbox" }, { "name": "Chrome Data Dir", @@ -429,8 +384,7 @@ "dataType": "String", "shortFlag": "-cdd", "longFlag": "-chrome-data-dir", - "key": "chrome-data-dir", - "commandKey": "katana" + "key": "chrome-data-dir" }, { "name": "System Chrome Path", @@ -439,8 +393,7 @@ "dataType": "String", "shortFlag": "-scp", "longFlag": "-system-chrome-path", - "key": "system-chrome-path", - "commandKey": "katana" + "key": "system-chrome-path" }, { "name": "No Incognito", @@ -449,8 +402,7 @@ "dataType": "Boolean", "shortFlag": "-noi", "longFlag": "-no-incognito", - "key": "no-incognito", - "commandKey": "katana" + "key": "no-incognito" }, { "name": "Chrome WS URL", @@ -459,8 +411,7 @@ "dataType": "String", "shortFlag": "-cwu", "longFlag": "-chrome-ws-url", - "key": "chrome-ws-url", - "commandKey": "katana" + "key": "chrome-ws-url" }, { "name": "XHR Extraction", @@ -469,8 +420,7 @@ "dataType": "Boolean", "shortFlag": "-xhr", "longFlag": "-xhr-extraction", - "key": "xhr-extraction", - "commandKey": "katana" + "key": "xhr-extraction" }, { "name": "Max Failure Count", @@ -479,8 +429,7 @@ "dataType": "Number", "shortFlag": "-mfc", "longFlag": "-max-failure-count", - "key": "max-failure-count", - "commandKey": "katana" + "key": "max-failure-count" }, { "name": "Enable Diagnostics", @@ -489,8 +438,7 @@ "dataType": "Boolean", "shortFlag": "-ed", "longFlag": "-enable-diagnostics", - "key": "enable-diagnostics", - "commandKey": "katana" + "key": "enable-diagnostics" }, { "name": "Captcha Solver Provider", @@ -499,8 +447,7 @@ "dataType": "String", "shortFlag": "-csp", "longFlag": "-captcha-solver-provider", - "key": "captcha-solver-provider", - "commandKey": "katana" + "key": "captcha-solver-provider" }, { "name": "Captcha Solver Key", @@ -509,8 +456,7 @@ "dataType": "String", "shortFlag": "-csk", "longFlag": "-captcha-solver-key", - "key": "captcha-solver-key", - "commandKey": "katana" + "key": "captcha-solver-key" }, { "name": "Crawl Scope", @@ -520,8 +466,7 @@ "isRepeatable": true, "shortFlag": "-cs", "longFlag": "-crawl-scope", - "key": "crawl-scope", - "commandKey": "katana" + "key": "crawl-scope" }, { "name": "Crawl Out Scope", @@ -531,8 +476,7 @@ "isRepeatable": true, "shortFlag": "-cos", "longFlag": "-crawl-out-scope", - "key": "crawl-out-scope", - "commandKey": "katana" + "key": "crawl-out-scope" }, { "name": "Field Scope", @@ -541,8 +485,7 @@ "dataType": "String", "shortFlag": "-fs", "longFlag": "-field-scope", - "key": "field-scope", - "commandKey": "katana" + "key": "field-scope" }, { "name": "No Scope", @@ -551,8 +494,7 @@ "dataType": "Boolean", "shortFlag": "-ns", "longFlag": "-no-scope", - "key": "no-scope", - "commandKey": "katana" + "key": "no-scope" }, { "name": "Display Out Scope", @@ -561,8 +503,7 @@ "dataType": "Boolean", "shortFlag": "-do", "longFlag": "-display-out-scope", - "key": "display-out-scope", - "commandKey": "katana" + "key": "display-out-scope" }, { "name": "Match Regex", @@ -572,8 +513,7 @@ "isRepeatable": true, "shortFlag": "-mr", "longFlag": "-match-regex", - "key": "match-regex", - "commandKey": "katana" + "key": "match-regex" }, { "name": "Filter Regex", @@ -583,8 +523,7 @@ "isRepeatable": true, "shortFlag": "-fr", "longFlag": "-filter-regex", - "key": "filter-regex", - "commandKey": "katana" + "key": "filter-regex" }, { "name": "Field", @@ -594,7 +533,6 @@ "shortFlag": "-f", "longFlag": "-field", "key": "field", - "commandKey": "katana", "enum": { "values": [ { @@ -664,7 +602,6 @@ "shortFlag": "-sf", "longFlag": "-store-field", "key": "store-field", - "commandKey": "katana", "enum": { "values": [ { @@ -734,8 +671,7 @@ "isRepeatable": true, "shortFlag": "-em", "longFlag": "-extension-match", - "key": "extension-match", - "commandKey": "katana" + "key": "extension-match" }, { "name": "Extension Filter", @@ -745,8 +681,7 @@ "isRepeatable": true, "shortFlag": "-ef", "longFlag": "-extension-filter", - "key": "extension-filter", - "commandKey": "katana" + "key": "extension-filter" }, { "name": "No Default Ext Filter", @@ -755,8 +690,7 @@ "dataType": "Boolean", "shortFlag": "-ndef", "longFlag": "-no-default-ext-filter", - "key": "no-default-ext-filter", - "commandKey": "katana" + "key": "no-default-ext-filter" }, { "name": "Match Condition", @@ -765,8 +699,7 @@ "dataType": "String", "shortFlag": "-mdc", "longFlag": "-match-condition", - "key": "match-condition", - "commandKey": "katana" + "key": "match-condition" }, { "name": "Filter Condition", @@ -775,8 +708,7 @@ "dataType": "String", "shortFlag": "-fdc", "longFlag": "-filter-condition", - "key": "filter-condition", - "commandKey": "katana" + "key": "filter-condition" }, { "name": "Disable Unique Filter", @@ -785,8 +717,7 @@ "dataType": "Boolean", "shortFlag": "-duf", "longFlag": "-disable-unique-filter", - "key": "disable-unique-filter", - "commandKey": "katana" + "key": "disable-unique-filter" }, { "name": "Filter Page Type", @@ -797,7 +728,6 @@ "shortFlag": "-fpt", "longFlag": "-filter-page-type", "key": "filter-page-type", - "commandKey": "katana", "enum": { "allowMultiple": true, "values": [ @@ -891,8 +821,7 @@ "dataType": "Number", "shortFlag": "-c", "longFlag": "-concurrency", - "key": "concurrency", - "commandKey": "katana" + "key": "concurrency" }, { "name": "Parallelism", @@ -901,8 +830,7 @@ "dataType": "Number", "shortFlag": "-p", "longFlag": "-parallelism", - "key": "parallelism", - "commandKey": "katana" + "key": "parallelism" }, { "name": "Delay", @@ -911,8 +839,7 @@ "dataType": "Number", "shortFlag": "-rd", "longFlag": "-delay", - "key": "delay", - "commandKey": "katana" + "key": "delay" }, { "name": "Rate Limit", @@ -921,8 +848,7 @@ "dataType": "Number", "shortFlag": "-rl", "longFlag": "-rate-limit", - "key": "rate-limit", - "commandKey": "katana" + "key": "rate-limit" }, { "name": "Rate Limit Minute", @@ -931,8 +857,7 @@ "dataType": "Number", "shortFlag": "-rlm", "longFlag": "-rate-limit-minute", - "key": "rate-limit-minute", - "commandKey": "katana" + "key": "rate-limit-minute" }, { "name": "Update", @@ -941,8 +866,7 @@ "dataType": "Boolean", "shortFlag": "-up", "longFlag": "-update", - "key": "update", - "commandKey": "katana" + "key": "update" }, { "name": "Disable Update Check", @@ -951,8 +875,7 @@ "dataType": "Boolean", "shortFlag": "-duc", "longFlag": "-disable-update-check", - "key": "disable-update-check", - "commandKey": "katana" + "key": "disable-update-check" }, { "name": "Output", @@ -961,8 +884,7 @@ "dataType": "String", "shortFlag": "-o", "longFlag": "-output", - "key": "output", - "commandKey": "katana" + "key": "output" }, { "name": "Output Template", @@ -971,8 +893,7 @@ "dataType": "String", "shortFlag": "-ot", "longFlag": "-output-template", - "key": "output-template", - "commandKey": "katana" + "key": "output-template" }, { "name": "Store Response", @@ -981,8 +902,7 @@ "dataType": "Boolean", "shortFlag": "-sr", "longFlag": "-store-response", - "key": "store-response", - "commandKey": "katana" + "key": "store-response" }, { "name": "Store Response Dir", @@ -991,8 +911,7 @@ "dataType": "String", "shortFlag": "-srd", "longFlag": "-store-response-dir", - "key": "store-response-dir", - "commandKey": "katana" + "key": "store-response-dir" }, { "name": "No Clobber", @@ -1001,8 +920,7 @@ "dataType": "Boolean", "shortFlag": "-ncb", "longFlag": "-no-clobber", - "key": "no-clobber", - "commandKey": "katana" + "key": "no-clobber" }, { "name": "Store Field Dir", @@ -1011,8 +929,7 @@ "dataType": "String", "shortFlag": "-sfd", "longFlag": "-store-field-dir", - "key": "store-field-dir", - "commandKey": "katana" + "key": "store-field-dir" }, { "name": "Omit Raw", @@ -1021,8 +938,7 @@ "dataType": "Boolean", "shortFlag": "-or", "longFlag": "-omit-raw", - "key": "omit-raw", - "commandKey": "katana" + "key": "omit-raw" }, { "name": "Omit Body", @@ -1031,8 +947,7 @@ "dataType": "Boolean", "shortFlag": "-ob", "longFlag": "-omit-body", - "key": "omit-body", - "commandKey": "katana" + "key": "omit-body" }, { "name": "List Output Fields", @@ -1041,8 +956,7 @@ "dataType": "Boolean", "shortFlag": "-lof", "longFlag": "-list-output-fields", - "key": "list-output-fields", - "commandKey": "katana" + "key": "list-output-fields" }, { "name": "Exclude Output Fields", @@ -1052,8 +966,7 @@ "isRepeatable": true, "shortFlag": "-eof", "longFlag": "-exclude-output-fields", - "key": "exclude-output-fields", - "commandKey": "katana" + "key": "exclude-output-fields" }, { "name": "JSONL Output", @@ -1062,8 +975,7 @@ "dataType": "Boolean", "shortFlag": "-j", "longFlag": "-jsonl", - "key": "jsonl", - "commandKey": "katana" + "key": "jsonl" }, { "name": "No Color", @@ -1072,8 +984,7 @@ "dataType": "Boolean", "shortFlag": "-nc", "longFlag": "-no-color", - "key": "no-color", - "commandKey": "katana" + "key": "no-color" }, { "name": "Silent", @@ -1081,8 +992,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-silent", - "key": "silent", - "commandKey": "katana" + "key": "silent" }, { "name": "Verbose", @@ -1091,8 +1001,7 @@ "dataType": "Boolean", "shortFlag": "-v", "longFlag": "-verbose", - "key": "verbose", - "commandKey": "katana" + "key": "verbose" }, { "name": "Debug", @@ -1100,8 +1009,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-debug", - "key": "debug", - "commandKey": "katana" + "key": "debug" }, { "name": "Version", @@ -1109,8 +1017,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-version", - "key": "version", - "commandKey": "katana" + "key": "version" } ] } diff --git a/public/tools-collection/mapcidr.json b/public/tools-collection/mapcidr.json index 4438fa2..d48fe31 100644 --- a/public/tools-collection/mapcidr.json +++ b/public/tools-collection/mapcidr.json @@ -1,19 +1,12 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "mapcidr", + "binaryName": "mapcidr", "displayName": "Mapcidr", - "commands": [ - { - "key": "mapcidr", - "name": "mapcidr", - "description": "Perform multiple operations on CIDR and IP ranges." - } - ], + "commands": [], "parameters": [ { "key": "cidr", "name": "CIDR input", - "commandKey": "mapcidr", "description": "CIDR, IP, or file input to process.", "group": "Input", "parameterType": "Option", @@ -26,7 +19,6 @@ { "key": "slice-by-cidr-count", "name": "Slice by CIDR count", - "commandKey": "mapcidr", "description": "Split CIDRs into the requested number of subnets.", "group": "Process", "parameterType": "Option", @@ -37,7 +29,6 @@ { "key": "slice-by-host-count", "name": "Slice by host count", - "commandKey": "mapcidr", "description": "Split CIDRs into subnets with the requested host count.", "group": "Process", "parameterType": "Option", @@ -48,7 +39,6 @@ { "key": "aggregate", "name": "Aggregate", - "commandKey": "mapcidr", "description": "Merge IPs and CIDRs into the smallest subnet.", "group": "Process", "parameterType": "Flag", @@ -58,7 +48,6 @@ { "key": "approximate-aggregate", "name": "Approximate aggregate", - "commandKey": "mapcidr", "description": "Approximate sparse IPv4 ranges into the smallest subnet block.", "group": "Process", "parameterType": "Flag", @@ -68,7 +57,6 @@ { "key": "count", "name": "Count", - "commandKey": "mapcidr", "description": "Count the number of IPs in the input CIDR.", "group": "Process", "parameterType": "Flag", @@ -78,7 +66,6 @@ { "key": "convert-to-ipv4", "name": "Convert to IPv4", - "commandKey": "mapcidr", "description": "Convert input IPs to IPv4 format.", "group": "Process", "parameterType": "Flag", @@ -88,7 +75,6 @@ { "key": "convert-to-ipv6", "name": "Convert to IPv6", - "commandKey": "mapcidr", "description": "Convert input IPs to IPv6 format.", "group": "Process", "parameterType": "Flag", @@ -98,7 +84,6 @@ { "key": "ip-format", "name": "IP format", - "commandKey": "mapcidr", "description": "Output IPs in one or more format indices.", "group": "Process", "parameterType": "Option", @@ -163,7 +148,6 @@ { "key": "zero-pad-n", "name": "Zero pad count", - "commandKey": "mapcidr", "description": "Number of padded zeros to use.", "group": "Process", "parameterType": "Option", @@ -174,7 +158,6 @@ { "key": "zero-pad-permute", "name": "Zero pad permutations", - "commandKey": "mapcidr", "description": "Enable permutations from zero to the pad count for each octet.", "group": "Process", "parameterType": "Flag", @@ -185,7 +168,6 @@ { "key": "filter-ipv4", "name": "Filter IPv4", - "commandKey": "mapcidr", "description": "Keep only IPv4 addresses from the input.", "group": "Filter", "parameterType": "Flag", @@ -196,7 +178,6 @@ { "key": "filter-ipv6", "name": "Filter IPv6", - "commandKey": "mapcidr", "description": "Keep only IPv6 addresses from the input.", "group": "Filter", "parameterType": "Flag", @@ -207,7 +188,6 @@ { "key": "skip-base", "name": "Skip base IPs", - "commandKey": "mapcidr", "description": "Skip base IPs ending in .0.", "group": "Filter", "parameterType": "Flag", @@ -217,7 +197,6 @@ { "key": "skip-broadcast", "name": "Skip broadcast IPs", - "commandKey": "mapcidr", "description": "Skip broadcast IPs ending in .255.", "group": "Filter", "parameterType": "Flag", @@ -227,7 +206,6 @@ { "key": "match-ip", "name": "Match IPs", - "commandKey": "mapcidr", "description": "Match IPs or CIDRs from the given list or file.", "group": "Filter", "parameterType": "Option", @@ -240,7 +218,6 @@ { "key": "filter-ip", "name": "Filter IPs", - "commandKey": "mapcidr", "description": "Filter IPs or CIDRs from the given list or file.", "group": "Filter", "parameterType": "Option", @@ -253,7 +230,6 @@ { "key": "sort", "name": "Sort ascending", - "commandKey": "mapcidr", "description": "Sort input IPs and CIDRs in ascending order.", "group": "Miscellaneous", "parameterType": "Flag", @@ -264,7 +240,6 @@ { "key": "sort-reverse", "name": "Sort descending", - "commandKey": "mapcidr", "description": "Sort input IPs and CIDRs in descending order.", "group": "Miscellaneous", "parameterType": "Flag", @@ -275,7 +250,6 @@ { "key": "shuffle-ip", "name": "Shuffle IPs", - "commandKey": "mapcidr", "description": "Shuffle input IPs randomly.", "group": "Miscellaneous", "parameterType": "Flag", @@ -286,7 +260,6 @@ { "key": "shuffle-port", "name": "Shuffle ports", - "commandKey": "mapcidr", "description": "Shuffle input IP:Port values randomly.", "group": "Miscellaneous", "parameterType": "Option", @@ -297,7 +270,6 @@ { "key": "update", "name": "Update", - "commandKey": "mapcidr", "description": "Update mapcidr to the latest version.", "group": "Update", "parameterType": "Flag", @@ -308,7 +280,6 @@ { "key": "disable-update-check", "name": "Disable update check", - "commandKey": "mapcidr", "description": "Disable automatic update checks.", "group": "Update", "parameterType": "Flag", @@ -319,7 +290,6 @@ { "key": "verbose", "name": "Verbose", - "commandKey": "mapcidr", "description": "Enable verbose output.", "group": "Output", "parameterType": "Flag", @@ -329,7 +299,6 @@ { "key": "output", "name": "Output file", - "commandKey": "mapcidr", "description": "Write output to a file.", "group": "Output", "parameterType": "Option", @@ -340,7 +309,6 @@ { "key": "silent", "name": "Silent", - "commandKey": "mapcidr", "description": "Suppress non-output logs.", "group": "Output", "parameterType": "Flag", @@ -350,7 +318,6 @@ { "key": "version", "name": "Version", - "commandKey": "mapcidr", "description": "Show the project version.", "group": "Output", "parameterType": "Flag", diff --git a/public/tools-collection/naabu.json b/public/tools-collection/naabu.json index 8dcbd0f..9d477ba 100644 --- a/public/tools-collection/naabu.json +++ b/public/tools-collection/naabu.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "naabu", + "binaryName": "naabu", "displayName": "Naabu", "info": { "description": "Fast port scanner for discovering open ports on hosts.", "version": "2.5.0", "url": "https://github.com/projectdiscovery/naabu" }, - "commands": [ - { - "name": "naabu", - "description": "Run naabu with flags.", - "sortOrder": 1, - "key": "naabu" - } - ], + "commands": [], "parameters": [ { "name": "Host", @@ -23,8 +16,7 @@ "dataType": "String", "longFlag": "-host", "sortOrder": 1, - "key": "host", - "commandKey": "naabu" + "key": "host" }, { "name": "List", @@ -34,8 +26,7 @@ "shortFlag": "-l", "longFlag": "-list", "sortOrder": 2, - "key": "list", - "commandKey": "naabu" + "key": "list" }, { "name": "Exclude Hosts", @@ -45,8 +36,7 @@ "shortFlag": "-eh", "longFlag": "-exclude-hosts", "sortOrder": 3, - "key": "exclude-hosts", - "commandKey": "naabu" + "key": "exclude-hosts" }, { "name": "Exclude File", @@ -56,8 +46,7 @@ "shortFlag": "-ef", "longFlag": "-exclude-file", "sortOrder": 4, - "key": "exclude-file", - "commandKey": "naabu" + "key": "exclude-file" }, { "name": "Port", @@ -67,8 +56,7 @@ "shortFlag": "-p", "longFlag": "-port", "sortOrder": 5, - "key": "port", - "commandKey": "naabu" + "key": "port" }, { "name": "Top Ports", @@ -103,8 +91,7 @@ } ] }, - "key": "top-ports", - "commandKey": "naabu" + "key": "top-ports" }, { "name": "Exclude Ports", @@ -114,8 +101,7 @@ "shortFlag": "-ep", "longFlag": "-exclude-ports", "sortOrder": 7, - "key": "exclude-ports", - "commandKey": "naabu" + "key": "exclude-ports" }, { "name": "Ports File", @@ -125,8 +111,7 @@ "shortFlag": "-pf", "longFlag": "-ports-file", "sortOrder": 8, - "key": "ports-file", - "commandKey": "naabu" + "key": "ports-file" }, { "name": "Port Threshold", @@ -136,8 +121,7 @@ "shortFlag": "-pts", "longFlag": "-port-threshold", "sortOrder": 9, - "key": "port-threshold", - "commandKey": "naabu" + "key": "port-threshold" }, { "name": "Exclude CDN", @@ -147,8 +131,7 @@ "shortFlag": "-ec", "longFlag": "-exclude-cdn", "sortOrder": 10, - "key": "exclude-cdn", - "commandKey": "naabu" + "key": "exclude-cdn" }, { "name": "Display CDN", @@ -158,8 +141,7 @@ "shortFlag": "-cdn", "longFlag": "-display-cdn", "sortOrder": 11, - "key": "display-cdn", - "commandKey": "naabu" + "key": "display-cdn" }, { "name": "Threads", @@ -168,8 +150,7 @@ "dataType": "Number", "longFlag": "-c", "sortOrder": 12, - "key": "threads", - "commandKey": "naabu" + "key": "threads" }, { "name": "Rate", @@ -178,8 +159,7 @@ "dataType": "Number", "longFlag": "-rate", "sortOrder": 13, - "key": "rate", - "commandKey": "naabu" + "key": "rate" }, { "name": "Update", @@ -189,8 +169,7 @@ "shortFlag": "-up", "longFlag": "-update", "sortOrder": 14, - "key": "update", - "commandKey": "naabu" + "key": "update" }, { "name": "Disable Update Check", @@ -200,8 +179,7 @@ "shortFlag": "-duc", "longFlag": "-disable-update-check", "sortOrder": 15, - "key": "disable-update-check", - "commandKey": "naabu" + "key": "disable-update-check" }, { "name": "Output", @@ -211,8 +189,7 @@ "shortFlag": "-o", "longFlag": "-output", "sortOrder": 16, - "key": "output", - "commandKey": "naabu" + "key": "output" }, { "name": "JSON", @@ -222,8 +199,7 @@ "shortFlag": "-j", "longFlag": "-json", "sortOrder": 17, - "key": "json", - "commandKey": "naabu" + "key": "json" }, { "name": "CSV", @@ -232,8 +208,7 @@ "dataType": "Boolean", "longFlag": "-csv", "sortOrder": 18, - "key": "csv", - "commandKey": "naabu" + "key": "csv" }, { "name": "Config", @@ -242,8 +217,7 @@ "dataType": "String", "longFlag": "-config", "sortOrder": 19, - "key": "config", - "commandKey": "naabu" + "key": "config" }, { "name": "Scan All IPs", @@ -253,8 +227,7 @@ "shortFlag": "-sa", "longFlag": "-scan-all-ips", "sortOrder": 20, - "key": "scan-all-ips", - "commandKey": "naabu" + "key": "scan-all-ips" }, { "name": "IP Version", @@ -282,8 +255,7 @@ } ] }, - "key": "ip-version", - "commandKey": "naabu" + "key": "ip-version" }, { "name": "Scan Type", @@ -311,8 +283,7 @@ } ] }, - "key": "scan-type", - "commandKey": "naabu" + "key": "scan-type" }, { "name": "Source IP", @@ -321,8 +292,7 @@ "dataType": "String", "longFlag": "-source-ip", "sortOrder": 23, - "key": "source-ip", - "commandKey": "naabu" + "key": "source-ip" }, { "name": "Interface List", @@ -332,8 +302,7 @@ "shortFlag": "-il", "longFlag": "-interface-list", "sortOrder": 24, - "key": "interface-list", - "commandKey": "naabu" + "key": "interface-list" }, { "name": "Interface", @@ -343,8 +312,7 @@ "shortFlag": "-i", "longFlag": "-interface", "sortOrder": 25, - "key": "interface", - "commandKey": "naabu" + "key": "interface" }, { "name": "Nmap", @@ -353,8 +321,7 @@ "dataType": "Boolean", "longFlag": "-nmap", "sortOrder": 26, - "key": "nmap", - "commandKey": "naabu" + "key": "nmap" }, { "name": "Nmap CLI", @@ -363,8 +330,7 @@ "dataType": "String", "longFlag": "-nmap-cli", "sortOrder": 27, - "key": "nmap-cli", - "commandKey": "naabu" + "key": "nmap-cli" }, { "name": "Resolvers", @@ -373,8 +339,7 @@ "dataType": "String", "longFlag": "-r", "sortOrder": 28, - "key": "resolvers", - "commandKey": "naabu" + "key": "resolvers" }, { "name": "Proxy", @@ -383,8 +348,7 @@ "dataType": "String", "longFlag": "-proxy", "sortOrder": 29, - "key": "proxy", - "commandKey": "naabu" + "key": "proxy" }, { "name": "Proxy Auth", @@ -393,8 +357,7 @@ "dataType": "String", "longFlag": "-proxy-auth", "sortOrder": 30, - "key": "proxy-auth", - "commandKey": "naabu" + "key": "proxy-auth" }, { "name": "Resume", @@ -403,8 +366,7 @@ "dataType": "Boolean", "longFlag": "-resume", "sortOrder": 31, - "key": "resume", - "commandKey": "naabu" + "key": "resume" }, { "name": "Stream", @@ -413,8 +375,7 @@ "dataType": "Boolean", "longFlag": "-stream", "sortOrder": 32, - "key": "stream", - "commandKey": "naabu" + "key": "stream" }, { "name": "Passive", @@ -423,8 +384,7 @@ "dataType": "Boolean", "longFlag": "-passive", "sortOrder": 33, - "key": "passive", - "commandKey": "naabu" + "key": "passive" }, { "name": "Input Read Timeout", @@ -434,8 +394,7 @@ "shortFlag": "-irt", "longFlag": "-input-read-timeout", "sortOrder": 34, - "key": "input-read-timeout", - "commandKey": "naabu" + "key": "input-read-timeout" }, { "name": "No Stdin", @@ -444,8 +403,7 @@ "dataType": "Boolean", "longFlag": "-no-stdin", "sortOrder": 35, - "key": "no-stdin", - "commandKey": "naabu" + "key": "no-stdin" }, { "name": "Host Discovery Only", @@ -455,8 +413,7 @@ "shortFlag": "-sn", "longFlag": "-host-discovery", "sortOrder": 36, - "key": "host-discovery-only", - "commandKey": "naabu" + "key": "host-discovery-only" }, { "name": "Skip Host Discovery", @@ -466,8 +423,7 @@ "shortFlag": "-Pn", "longFlag": "-skip-host-discovery", "sortOrder": 37, - "key": "skip-host-discovery", - "commandKey": "naabu" + "key": "skip-host-discovery" }, { "name": "With Host Discovery", @@ -477,8 +433,7 @@ "shortFlag": "-wn", "longFlag": "-with-host-discovery", "sortOrder": 38, - "key": "with-host-discovery", - "commandKey": "naabu" + "key": "with-host-discovery" }, { "name": "Probe TCP SYN", @@ -488,8 +443,7 @@ "shortFlag": "-ps", "longFlag": "-probe-tcp-syn", "sortOrder": 39, - "key": "probe-tcp-syn", - "commandKey": "naabu" + "key": "probe-tcp-syn" }, { "name": "Probe TCP ACK", @@ -499,8 +453,7 @@ "shortFlag": "-pa", "longFlag": "-probe-tcp-ack", "sortOrder": 40, - "key": "probe-tcp-ack", - "commandKey": "naabu" + "key": "probe-tcp-ack" }, { "name": "Probe ICMP Echo", @@ -510,8 +463,7 @@ "shortFlag": "-pe", "longFlag": "-probe-icmp-echo", "sortOrder": 41, - "key": "probe-icmp-echo", - "commandKey": "naabu" + "key": "probe-icmp-echo" }, { "name": "Probe ICMP Timestamp", @@ -521,8 +473,7 @@ "shortFlag": "-pp", "longFlag": "-probe-icmp-timestamp", "sortOrder": 42, - "key": "probe-icmp-timestamp", - "commandKey": "naabu" + "key": "probe-icmp-timestamp" }, { "name": "Probe ICMP Address Mask", @@ -532,8 +483,7 @@ "shortFlag": "-pm", "longFlag": "-probe-icmp-address-mask", "sortOrder": 43, - "key": "probe-icmp-address-mask", - "commandKey": "naabu" + "key": "probe-icmp-address-mask" }, { "name": "ARP Ping", @@ -543,8 +493,7 @@ "shortFlag": "-arp", "longFlag": "-arp-ping", "sortOrder": 44, - "key": "arp-ping", - "commandKey": "naabu" + "key": "arp-ping" }, { "name": "ND Ping", @@ -554,8 +503,7 @@ "shortFlag": "-nd", "longFlag": "-nd-ping", "sortOrder": 45, - "key": "nd-ping", - "commandKey": "naabu" + "key": "nd-ping" }, { "name": "Rev PTR", @@ -564,8 +512,7 @@ "dataType": "Boolean", "longFlag": "-rev-ptr", "sortOrder": 46, - "key": "rev-ptr", - "commandKey": "naabu" + "key": "rev-ptr" }, { "name": "Retries", @@ -574,8 +521,7 @@ "dataType": "Number", "longFlag": "-retries", "sortOrder": 47, - "key": "retries", - "commandKey": "naabu" + "key": "retries" }, { "name": "Timeout", @@ -584,8 +530,7 @@ "dataType": "Number", "longFlag": "-timeout", "sortOrder": 48, - "key": "timeout", - "commandKey": "naabu" + "key": "timeout" }, { "name": "Warm Up Time", @@ -594,8 +539,7 @@ "dataType": "Number", "longFlag": "-warm-up-time", "sortOrder": 49, - "key": "warm-up-time", - "commandKey": "naabu" + "key": "warm-up-time" }, { "name": "Ping", @@ -604,8 +548,7 @@ "dataType": "Boolean", "longFlag": "-ping", "sortOrder": 50, - "key": "ping", - "commandKey": "naabu" + "key": "ping" }, { "name": "Verify", @@ -614,8 +557,7 @@ "dataType": "Boolean", "longFlag": "-verify", "sortOrder": 51, - "key": "verify", - "commandKey": "naabu" + "key": "verify" }, { "name": "Health Check", @@ -625,8 +567,7 @@ "shortFlag": "-hc", "longFlag": "-health-check", "sortOrder": 52, - "key": "health-check", - "commandKey": "naabu" + "key": "health-check" }, { "name": "Debug", @@ -635,8 +576,7 @@ "dataType": "Boolean", "longFlag": "-debug", "sortOrder": 53, - "key": "debug", - "commandKey": "naabu" + "key": "debug" }, { "name": "Verbose", @@ -646,8 +586,7 @@ "shortFlag": "-v", "longFlag": "-verbose", "sortOrder": 54, - "key": "verbose", - "commandKey": "naabu" + "key": "verbose" }, { "name": "No Color", @@ -657,8 +596,7 @@ "shortFlag": "-nc", "longFlag": "-no-color", "sortOrder": 55, - "key": "no-color", - "commandKey": "naabu" + "key": "no-color" }, { "name": "Silent", @@ -667,8 +605,7 @@ "dataType": "Boolean", "longFlag": "-silent", "sortOrder": 56, - "key": "silent", - "commandKey": "naabu" + "key": "silent" }, { "name": "Version", @@ -677,8 +614,7 @@ "dataType": "Boolean", "longFlag": "-version", "sortOrder": 57, - "key": "version", - "commandKey": "naabu" + "key": "version" }, { "name": "Stats", @@ -687,8 +623,7 @@ "dataType": "Boolean", "longFlag": "-stats", "sortOrder": 58, - "key": "stats", - "commandKey": "naabu" + "key": "stats" }, { "name": "Stats Interval", @@ -698,8 +633,7 @@ "shortFlag": "-si", "longFlag": "-stats-interval", "sortOrder": 59, - "key": "stats-interval", - "commandKey": "naabu" + "key": "stats-interval" }, { "name": "Metrics Port", @@ -709,8 +643,7 @@ "shortFlag": "-mp", "longFlag": "-metrics-port", "sortOrder": 60, - "key": "metrics-port", - "commandKey": "naabu" + "key": "metrics-port" }, { "name": "Auth", @@ -719,8 +652,7 @@ "dataType": "Boolean", "longFlag": "-auth", "sortOrder": 61, - "key": "auth", - "commandKey": "naabu" + "key": "auth" }, { "name": "Auth Config", @@ -730,8 +662,7 @@ "shortFlag": "-ac", "longFlag": "-auth-config", "sortOrder": 62, - "key": "auth-config", - "commandKey": "naabu" + "key": "auth-config" }, { "name": "Dashboard", @@ -741,8 +672,7 @@ "shortFlag": "-pd", "longFlag": "-dashboard", "sortOrder": 63, - "key": "dashboard", - "commandKey": "naabu" + "key": "dashboard" }, { "name": "Team ID", @@ -752,8 +682,7 @@ "shortFlag": "-tid", "longFlag": "-team-id", "sortOrder": 64, - "key": "team-id", - "commandKey": "naabu" + "key": "team-id" }, { "name": "Asset ID", @@ -763,8 +692,7 @@ "shortFlag": "-aid", "longFlag": "-asset-id", "sortOrder": 65, - "key": "asset-id", - "commandKey": "naabu" + "key": "asset-id" }, { "name": "Asset Name", @@ -774,8 +702,7 @@ "shortFlag": "-aname", "longFlag": "-asset-name", "sortOrder": 66, - "key": "asset-name", - "commandKey": "naabu" + "key": "asset-name" }, { "name": "Dashboard Upload", @@ -785,8 +712,7 @@ "shortFlag": "-pdu", "longFlag": "-dashboard-upload", "sortOrder": 67, - "key": "dashboard-upload", - "commandKey": "naabu" + "key": "dashboard-upload" }, { "name": "List Output Fields", @@ -796,8 +722,7 @@ "shortFlag": "-lof", "longFlag": "-list-output-fields", "sortOrder": 68, - "key": "list-output-fields", - "commandKey": "naabu" + "key": "list-output-fields" }, { "name": "Exclude Output Fields", @@ -807,8 +732,7 @@ "shortFlag": "-eof", "longFlag": "-exclude-output-fields", "sortOrder": 69, - "key": "exclude-output-fields", - "commandKey": "naabu" + "key": "exclude-output-fields" }, { "name": "Connect Payload", @@ -818,8 +742,7 @@ "shortFlag": "-cp", "longFlag": "-connect-payload", "sortOrder": 70, - "key": "connect-payload", - "commandKey": "naabu" + "key": "connect-payload" }, { "name": "Service Discovery", @@ -829,8 +752,7 @@ "shortFlag": "-sD", "longFlag": "-service-discovery", "sortOrder": 71, - "key": "service-discovery", - "commandKey": "naabu" + "key": "service-discovery" }, { "name": "Service Version", @@ -840,8 +762,7 @@ "shortFlag": "-sV", "longFlag": "-service-version", "sortOrder": 72, - "key": "service-version", - "commandKey": "naabu" + "key": "service-version" } ] -} \ No newline at end of file +} diff --git a/public/tools-collection/notify.json b/public/tools-collection/notify.json index 556fefc..e39f932 100644 --- a/public/tools-collection/notify.json +++ b/public/tools-collection/notify.json @@ -1,21 +1,12 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "notify", + "binaryName": "notify", "displayName": "Notify", - "commands": [ - { - "key": "notify", - "name": "Notify", - "description": "Stream output from files or pipes to notification providers.", - "isDefault": true, - "sortOrder": 0 - } - ], + "commands": [], "parameters": [ { "key": "bulk", "name": "Bulk", - "commandKey": "notify", "description": "Enable bulk processing.", "parameterType": "Flag", "dataType": "Boolean", @@ -24,7 +15,6 @@ { "key": "char-limit", "name": "Character Limit", - "commandKey": "notify", "description": "Maximum character limit per message.", "parameterType": "Option", "dataType": "Number", @@ -43,7 +33,6 @@ { "key": "config", "name": "Config", - "commandKey": "notify", "description": "Notify configuration file path.", "parameterType": "Option", "dataType": "String", @@ -52,7 +41,6 @@ { "key": "data", "name": "Data", - "commandKey": "notify", "description": "Input file to send to Notify.", "parameterType": "Option", "dataType": "String", @@ -62,7 +50,6 @@ { "key": "delay", "name": "Delay", - "commandKey": "notify", "description": "Delay in seconds between notifications.", "parameterType": "Option", "dataType": "Number", @@ -81,7 +68,6 @@ { "key": "id", "name": "ID", - "commandKey": "notify", "description": "Comma-separated notification IDs.", "parameterType": "Option", "dataType": "String", @@ -91,7 +77,6 @@ { "key": "msg-format", "name": "Message Format", - "commandKey": "notify", "description": "Custom message format.", "parameterType": "Option", "dataType": "String", @@ -101,7 +86,6 @@ { "key": "no-color", "name": "No Color", - "commandKey": "notify", "description": "Disable colored output.", "parameterType": "Flag", "dataType": "Boolean", @@ -111,7 +95,6 @@ { "key": "provider-config", "name": "Provider Config", - "commandKey": "notify", "description": "Provider config file path.", "parameterType": "Option", "dataType": "String", @@ -121,7 +104,6 @@ { "key": "provider", "name": "Provider", - "commandKey": "notify", "description": "Comma-separated providers to send notifications to.", "parameterType": "Option", "dataType": "Enum", @@ -182,7 +164,6 @@ { "key": "proxy", "name": "Proxy", - "commandKey": "notify", "description": "HTTP or SOCKSv5 proxy to use.", "parameterType": "Option", "dataType": "String", @@ -191,7 +172,6 @@ { "key": "rate-limit", "name": "Rate Limit", - "commandKey": "notify", "description": "Maximum HTTP requests per second.", "parameterType": "Option", "dataType": "Number", @@ -210,7 +190,6 @@ { "key": "silent", "name": "Silent", - "commandKey": "notify", "description": "Enable silent mode.", "parameterType": "Flag", "dataType": "Boolean", @@ -219,7 +198,6 @@ { "key": "verbose", "name": "Verbose", - "commandKey": "notify", "description": "Enable verbose mode.", "parameterType": "Flag", "dataType": "Boolean", @@ -228,7 +206,6 @@ { "key": "version", "name": "Version", - "commandKey": "notify", "description": "Display the version.", "parameterType": "Flag", "dataType": "Boolean", @@ -237,7 +214,6 @@ { "key": "update", "name": "Update", - "commandKey": "notify", "description": "Update to the latest version.", "parameterType": "Flag", "dataType": "Boolean", @@ -246,7 +222,6 @@ { "key": "disable-update-check", "name": "Disable Update Check", - "commandKey": "notify", "description": "Disable automatic update checks.", "parameterType": "Flag", "dataType": "Boolean", diff --git a/public/tools-collection/nuclei.json b/public/tools-collection/nuclei.json index 44b73ab..cff6f66 100644 --- a/public/tools-collection/nuclei.json +++ b/public/tools-collection/nuclei.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "nuclei", + "binaryName": "nuclei", "displayName": "Nuclei", "info": { "description": "Nuclei is a fast, template based vulnerability scanner focusing on extensive configurability, massive extensibility and ease of use.", "version": "3.7.1", "url": "https://github.com/projectdiscovery/nuclei" }, - "commands": [ - { - "name": "nuclei", - "description": "Run nuclei vulnerability scanner.", - "sortOrder": 1, - "key": "nuclei" - } - ], + "commands": [], "parameters": [ { "name": "Target", @@ -24,8 +17,7 @@ "isRepeatable": true, "shortFlag": "-u", "longFlag": "-target", - "key": "target", - "commandKey": "nuclei" + "key": "target" }, { "name": "List", @@ -34,8 +26,7 @@ "dataType": "String", "shortFlag": "-l", "longFlag": "-list", - "key": "list", - "commandKey": "nuclei" + "key": "list" }, { "name": "Exclude Hosts", @@ -45,8 +36,7 @@ "isRepeatable": true, "shortFlag": "-eh", "longFlag": "-exclude-hosts", - "key": "exclude-hosts", - "commandKey": "nuclei" + "key": "exclude-hosts" }, { "name": "Resume", @@ -54,8 +44,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-resume", - "key": "resume", - "commandKey": "nuclei" + "key": "resume" }, { "name": "Scan All IPs", @@ -64,8 +53,7 @@ "dataType": "Boolean", "shortFlag": "-sa", "longFlag": "-scan-all-ips", - "key": "scan-all-ips", - "commandKey": "nuclei" + "key": "scan-all-ips" }, { "name": "IP Version", @@ -93,8 +81,7 @@ } ] }, - "key": "ip-version", - "commandKey": "nuclei" + "key": "ip-version" }, { "name": "Input Mode", @@ -149,8 +136,7 @@ } ] }, - "key": "input-mode", - "commandKey": "nuclei" + "key": "input-mode" }, { "name": "Required Only", @@ -159,8 +145,7 @@ "dataType": "Boolean", "shortFlag": "-ro", "longFlag": "-required-only", - "key": "required-only", - "commandKey": "nuclei" + "key": "required-only" }, { "name": "Skip Format Validation", @@ -169,8 +154,7 @@ "dataType": "Boolean", "shortFlag": "-sfv", "longFlag": "-skip-format-validation", - "key": "skip-format-validation", - "commandKey": "nuclei" + "key": "skip-format-validation" }, { "name": "New Templates", @@ -179,8 +163,7 @@ "dataType": "Boolean", "shortFlag": "-nt", "longFlag": "-new-templates", - "key": "new-templates", - "commandKey": "nuclei" + "key": "new-templates" }, { "name": "New Templates Version", @@ -190,8 +173,7 @@ "isRepeatable": true, "shortFlag": "-ntv", "longFlag": "-new-templates-version", - "key": "new-templates-version", - "commandKey": "nuclei" + "key": "new-templates-version" }, { "name": "Automatic Scan", @@ -200,8 +182,7 @@ "dataType": "Boolean", "shortFlag": "-as", "longFlag": "-automatic-scan", - "key": "automatic-scan", - "commandKey": "nuclei" + "key": "automatic-scan" }, { "name": "Templates", @@ -211,8 +192,7 @@ "isRepeatable": true, "shortFlag": "-t", "longFlag": "-templates", - "key": "templates", - "commandKey": "nuclei" + "key": "templates" }, { "name": "Template URL", @@ -222,8 +202,7 @@ "isRepeatable": true, "shortFlag": "-turl", "longFlag": "-template-url", - "key": "template-url", - "commandKey": "nuclei" + "key": "template-url" }, { "name": "Prompt", @@ -232,8 +211,7 @@ "dataType": "String", "shortFlag": "-ai", "longFlag": "-prompt", - "key": "prompt", - "commandKey": "nuclei" + "key": "prompt" }, { "name": "Workflows", @@ -243,8 +221,7 @@ "isRepeatable": true, "shortFlag": "-w", "longFlag": "-workflows", - "key": "workflows", - "commandKey": "nuclei" + "key": "workflows" }, { "name": "Workflow URL", @@ -254,8 +231,7 @@ "isRepeatable": true, "shortFlag": "-wurl", "longFlag": "-workflow-url", - "key": "workflow-url", - "commandKey": "nuclei" + "key": "workflow-url" }, { "name": "Validate", @@ -263,8 +239,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-validate", - "key": "validate", - "commandKey": "nuclei" + "key": "validate" }, { "name": "No Strict Syntax", @@ -273,8 +248,7 @@ "dataType": "Boolean", "shortFlag": "-nss", "longFlag": "-no-strict-syntax", - "key": "no-strict-syntax", - "commandKey": "nuclei" + "key": "no-strict-syntax" }, { "name": "Template Display", @@ -283,8 +257,7 @@ "dataType": "Boolean", "shortFlag": "-td", "longFlag": "-template-display", - "key": "template-display", - "commandKey": "nuclei" + "key": "template-display" }, { "name": "List Templates", @@ -292,8 +265,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-tl", - "key": "list-templates", - "commandKey": "nuclei" + "key": "list-templates" }, { "name": "List Tags", @@ -301,8 +273,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-tgl", - "key": "list-tags", - "commandKey": "nuclei" + "key": "list-tags" }, { "name": "Sign", @@ -310,8 +281,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-sign", - "key": "sign", - "commandKey": "nuclei" + "key": "sign" }, { "name": "Code", @@ -319,8 +289,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-code", - "key": "code", - "commandKey": "nuclei" + "key": "code" }, { "name": "Disable Unsigned Templates", @@ -329,8 +298,7 @@ "dataType": "Boolean", "shortFlag": "-dut", "longFlag": "-disable-unsigned-templates", - "key": "disable-unsigned-templates", - "commandKey": "nuclei" + "key": "disable-unsigned-templates" }, { "name": "Enable Self Contained", @@ -339,8 +307,7 @@ "dataType": "Boolean", "shortFlag": "-esc", "longFlag": "-enable-self-contained", - "key": "enable-self-contained", - "commandKey": "nuclei" + "key": "enable-self-contained" }, { "name": "Enable Global Matchers", @@ -349,8 +316,7 @@ "dataType": "Boolean", "shortFlag": "-egm", "longFlag": "-enable-global-matchers", - "key": "enable-global-matchers", - "commandKey": "nuclei" + "key": "enable-global-matchers" }, { "name": "File", @@ -358,8 +324,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-file", - "key": "file", - "commandKey": "nuclei" + "key": "file" }, { "name": "Author", @@ -369,8 +334,7 @@ "isRepeatable": true, "shortFlag": "-a", "longFlag": "-author", - "key": "author", - "commandKey": "nuclei" + "key": "author" }, { "name": "Tags", @@ -379,8 +343,7 @@ "dataType": "String", "isRepeatable": true, "longFlag": "-tags", - "key": "tags", - "commandKey": "nuclei" + "key": "tags" }, { "name": "Exclude Tags", @@ -390,8 +353,7 @@ "isRepeatable": true, "shortFlag": "-etags", "longFlag": "-exclude-tags", - "key": "exclude-tags", - "commandKey": "nuclei" + "key": "exclude-tags" }, { "name": "Include Tags", @@ -401,8 +363,7 @@ "isRepeatable": true, "shortFlag": "-itags", "longFlag": "-include-tags", - "key": "include-tags", - "commandKey": "nuclei" + "key": "include-tags" }, { "name": "Template ID", @@ -412,8 +373,7 @@ "isRepeatable": true, "shortFlag": "-id", "longFlag": "-template-id", - "key": "template-id", - "commandKey": "nuclei" + "key": "template-id" }, { "name": "Exclude Id", @@ -423,8 +383,7 @@ "isRepeatable": true, "shortFlag": "-eid", "longFlag": "-exclude-id", - "key": "exclude-id", - "commandKey": "nuclei" + "key": "exclude-id" }, { "name": "Include Templates", @@ -434,8 +393,7 @@ "isRepeatable": true, "shortFlag": "-it", "longFlag": "-include-templates", - "key": "include-templates", - "commandKey": "nuclei" + "key": "include-templates" }, { "name": "Exclude Templates", @@ -445,8 +403,7 @@ "isRepeatable": true, "shortFlag": "-et", "longFlag": "-exclude-templates", - "key": "exclude-templates", - "commandKey": "nuclei" + "key": "exclude-templates" }, { "name": "Exclude Matchers", @@ -456,8 +413,7 @@ "isRepeatable": true, "shortFlag": "-em", "longFlag": "-exclude-matchers", - "key": "exclude-matchers", - "commandKey": "nuclei" + "key": "exclude-matchers" }, { "name": "Severity", @@ -513,8 +469,7 @@ } ] }, - "key": "severity", - "commandKey": "nuclei" + "key": "severity" }, { "name": "Exclude Severity", @@ -570,8 +525,7 @@ } ] }, - "key": "exclude-severity", - "commandKey": "nuclei" + "key": "exclude-severity" }, { "name": "Type", @@ -662,8 +616,7 @@ } ] }, - "key": "type", - "commandKey": "nuclei" + "key": "type" }, { "name": "Exclude Type", @@ -754,8 +707,7 @@ } ] }, - "key": "exclude-type", - "commandKey": "nuclei" + "key": "exclude-type" }, { "name": "Template Condition", @@ -765,8 +717,7 @@ "isRepeatable": true, "shortFlag": "-tc", "longFlag": "-template-condition", - "key": "template-condition", - "commandKey": "nuclei" + "key": "template-condition" }, { "name": "Output", @@ -775,8 +726,7 @@ "dataType": "String", "shortFlag": "-o", "longFlag": "-output", - "key": "output", - "commandKey": "nuclei" + "key": "output" }, { "name": "Store Response", @@ -785,8 +735,7 @@ "dataType": "Boolean", "shortFlag": "-sresp", "longFlag": "-store-resp", - "key": "store-resp", - "commandKey": "nuclei" + "key": "store-resp" }, { "name": "Store Response Dir", @@ -795,8 +744,7 @@ "dataType": "String", "shortFlag": "-srd", "longFlag": "-store-resp-dir", - "key": "store-resp-dir", - "commandKey": "nuclei" + "key": "store-resp-dir" }, { "name": "Silent", @@ -804,8 +752,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-silent", - "key": "silent", - "commandKey": "nuclei" + "key": "silent" }, { "name": "No Color", @@ -814,8 +761,7 @@ "dataType": "Boolean", "shortFlag": "-nc", "longFlag": "-no-color", - "key": "no-color", - "commandKey": "nuclei" + "key": "no-color" }, { "name": "JSONL", @@ -824,8 +770,7 @@ "dataType": "Boolean", "shortFlag": "-j", "longFlag": "-jsonl", - "key": "jsonl", - "commandKey": "nuclei" + "key": "jsonl" }, { "name": "Include RR", @@ -834,8 +779,7 @@ "dataType": "Boolean", "shortFlag": "-irr", "longFlag": "-include-rr", - "key": "include-rr", - "commandKey": "nuclei" + "key": "include-rr" }, { "name": "Omit Raw", @@ -844,8 +788,7 @@ "dataType": "Boolean", "shortFlag": "-or", "longFlag": "-omit-raw", - "key": "omit-raw", - "commandKey": "nuclei" + "key": "omit-raw" }, { "name": "Omit Template", @@ -854,8 +797,7 @@ "dataType": "Boolean", "shortFlag": "-ot", "longFlag": "-omit-template", - "key": "omit-template", - "commandKey": "nuclei" + "key": "omit-template" }, { "name": "No Meta", @@ -864,8 +806,7 @@ "dataType": "Boolean", "shortFlag": "-nm", "longFlag": "-no-meta", - "key": "no-meta", - "commandKey": "nuclei" + "key": "no-meta" }, { "name": "Timestamp", @@ -874,8 +815,7 @@ "dataType": "Boolean", "shortFlag": "-ts", "longFlag": "-timestamp", - "key": "timestamp", - "commandKey": "nuclei" + "key": "timestamp" }, { "name": "Report DB", @@ -884,8 +824,7 @@ "dataType": "String", "shortFlag": "-rdb", "longFlag": "-report-db", - "key": "report-db", - "commandKey": "nuclei" + "key": "report-db" }, { "name": "Matcher Status", @@ -894,8 +833,7 @@ "dataType": "Boolean", "shortFlag": "-ms", "longFlag": "-matcher-status", - "key": "matcher-status", - "commandKey": "nuclei" + "key": "matcher-status" }, { "name": "Markdown Export", @@ -904,8 +842,7 @@ "dataType": "String", "shortFlag": "-me", "longFlag": "-markdown-export", - "key": "markdown-export", - "commandKey": "nuclei" + "key": "markdown-export" }, { "name": "Sarif Export", @@ -914,8 +851,7 @@ "dataType": "String", "shortFlag": "-se", "longFlag": "-sarif-export", - "key": "sarif-export", - "commandKey": "nuclei" + "key": "sarif-export" }, { "name": "JSON Export", @@ -924,8 +860,7 @@ "dataType": "String", "shortFlag": "-je", "longFlag": "-json-export", - "key": "json-export", - "commandKey": "nuclei" + "key": "json-export" }, { "name": "JSONL Export", @@ -934,8 +869,7 @@ "dataType": "String", "shortFlag": "-jle", "longFlag": "-jsonl-export", - "key": "jsonl-export", - "commandKey": "nuclei" + "key": "jsonl-export" }, { "name": "Redact", @@ -945,8 +879,7 @@ "isRepeatable": true, "shortFlag": "-rd", "longFlag": "-redact", - "key": "redact", - "commandKey": "nuclei" + "key": "redact" }, { "name": "Config", @@ -954,8 +887,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-config", - "key": "config", - "commandKey": "nuclei" + "key": "config" }, { "name": "Profile", @@ -964,8 +896,7 @@ "dataType": "String", "shortFlag": "-tp", "longFlag": "-profile", - "key": "profile", - "commandKey": "nuclei" + "key": "profile" }, { "name": "Profile List", @@ -974,8 +905,7 @@ "dataType": "Boolean", "shortFlag": "-tpl", "longFlag": "-profile-list", - "key": "profile-list", - "commandKey": "nuclei" + "key": "profile-list" }, { "name": "Follow Redirects", @@ -984,8 +914,7 @@ "dataType": "Boolean", "shortFlag": "-fr", "longFlag": "-follow-redirects", - "key": "follow-redirects", - "commandKey": "nuclei" + "key": "follow-redirects" }, { "name": "Follow Host Redirects", @@ -994,8 +923,7 @@ "dataType": "Boolean", "shortFlag": "-fhr", "longFlag": "-follow-host-redirects", - "key": "follow-host-redirects", - "commandKey": "nuclei" + "key": "follow-host-redirects" }, { "name": "Max Redirects", @@ -1004,8 +932,7 @@ "dataType": "Number", "shortFlag": "-mr", "longFlag": "-max-redirects", - "key": "max-redirects", - "commandKey": "nuclei" + "key": "max-redirects" }, { "name": "Disable Redirects", @@ -1014,8 +941,7 @@ "dataType": "Boolean", "shortFlag": "-dr", "longFlag": "-disable-redirects", - "key": "disable-redirects", - "commandKey": "nuclei" + "key": "disable-redirects" }, { "name": "Report Config", @@ -1024,8 +950,7 @@ "dataType": "String", "shortFlag": "-rc", "longFlag": "-report-config", - "key": "report-config", - "commandKey": "nuclei" + "key": "report-config" }, { "name": "Header", @@ -1035,8 +960,7 @@ "isRepeatable": true, "shortFlag": "-H", "longFlag": "-header", - "key": "header", - "commandKey": "nuclei" + "key": "header" }, { "name": "Var", @@ -1045,8 +969,7 @@ "dataType": "String", "shortFlag": "-V", "longFlag": "-var", - "key": "var", - "commandKey": "nuclei" + "key": "var" }, { "name": "Resolvers", @@ -1055,8 +978,7 @@ "dataType": "String", "shortFlag": "-r", "longFlag": "-resolvers", - "key": "resolvers", - "commandKey": "nuclei" + "key": "resolvers" }, { "name": "System Resolvers", @@ -1065,8 +987,7 @@ "dataType": "Boolean", "shortFlag": "-sr", "longFlag": "-system-resolvers", - "key": "system-resolvers", - "commandKey": "nuclei" + "key": "system-resolvers" }, { "name": "Disable Clustering", @@ -1075,8 +996,7 @@ "dataType": "Boolean", "shortFlag": "-dc", "longFlag": "-disable-clustering", - "key": "disable-clustering", - "commandKey": "nuclei" + "key": "disable-clustering" }, { "name": "Passive", @@ -1084,8 +1004,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-passive", - "key": "passive", - "commandKey": "nuclei" + "key": "passive" }, { "name": "Force HTTP/2", @@ -1094,8 +1013,7 @@ "dataType": "Boolean", "shortFlag": "-fh2", "longFlag": "-force-http2", - "key": "force-http2", - "commandKey": "nuclei" + "key": "force-http2" }, { "name": "Env Vars", @@ -1104,8 +1022,7 @@ "dataType": "Boolean", "shortFlag": "-ev", "longFlag": "-env-vars", - "key": "env-vars", - "commandKey": "nuclei" + "key": "env-vars" }, { "name": "Client Cert", @@ -1114,8 +1031,7 @@ "dataType": "String", "shortFlag": "-cc", "longFlag": "-client-cert", - "key": "client-cert", - "commandKey": "nuclei" + "key": "client-cert" }, { "name": "Client Key", @@ -1124,8 +1040,7 @@ "dataType": "String", "shortFlag": "-ck", "longFlag": "-client-key", - "key": "client-key", - "commandKey": "nuclei" + "key": "client-key" }, { "name": "Client CA", @@ -1134,8 +1049,7 @@ "dataType": "String", "shortFlag": "-ca", "longFlag": "-client-ca", - "key": "client-ca", - "commandKey": "nuclei" + "key": "client-ca" }, { "name": "Show Match Line", @@ -1144,8 +1058,7 @@ "dataType": "Boolean", "shortFlag": "-sml", "longFlag": "-show-match-line", - "key": "show-match-line", - "commandKey": "nuclei" + "key": "show-match-line" }, { "name": "Ztls", @@ -1153,8 +1066,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-ztls", - "key": "ztls", - "commandKey": "nuclei" + "key": "ztls" }, { "name": "Sni", @@ -1162,8 +1074,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-sni", - "key": "sni", - "commandKey": "nuclei" + "key": "sni" }, { "name": "Dialer Keep Alive", @@ -1172,8 +1083,7 @@ "dataType": "String", "shortFlag": "-dka", "longFlag": "-dialer-keep-alive", - "key": "dialer-keep-alive", - "commandKey": "nuclei" + "key": "dialer-keep-alive" }, { "name": "Allow Local File Access", @@ -1182,8 +1092,7 @@ "dataType": "Boolean", "shortFlag": "-lfa", "longFlag": "-allow-local-file-access", - "key": "allow-local-file-access", - "commandKey": "nuclei" + "key": "allow-local-file-access" }, { "name": "Restrict Local Network Access", @@ -1192,8 +1101,7 @@ "dataType": "Boolean", "shortFlag": "-lna", "longFlag": "-restrict-local-network-access", - "key": "restrict-local-network-access", - "commandKey": "nuclei" + "key": "restrict-local-network-access" }, { "name": "Interface", @@ -1202,8 +1110,7 @@ "dataType": "String", "shortFlag": "-i", "longFlag": "-interface", - "key": "interface", - "commandKey": "nuclei" + "key": "interface" }, { "name": "Attack Type", @@ -1237,8 +1144,7 @@ } ] }, - "key": "attack-type", - "commandKey": "nuclei" + "key": "attack-type" }, { "name": "Source IP", @@ -1247,8 +1153,7 @@ "dataType": "String", "shortFlag": "-sip", "longFlag": "-source-ip", - "key": "source-ip", - "commandKey": "nuclei" + "key": "source-ip" }, { "name": "Response Size Read", @@ -1257,8 +1162,7 @@ "dataType": "Number", "shortFlag": "-rsr", "longFlag": "-response-size-read", - "key": "response-size-read", - "commandKey": "nuclei" + "key": "response-size-read" }, { "name": "Response Size Save", @@ -1267,8 +1171,7 @@ "dataType": "Number", "shortFlag": "-rss", "longFlag": "-response-size-save", - "key": "response-size-save", - "commandKey": "nuclei" + "key": "response-size-save" }, { "name": "Reset", @@ -1276,8 +1179,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-reset", - "key": "reset", - "commandKey": "nuclei" + "key": "reset" }, { "name": "TLS Impersonate", @@ -1286,8 +1188,7 @@ "dataType": "Boolean", "shortFlag": "-tlsi", "longFlag": "-tls-impersonate", - "key": "tls-impersonate", - "commandKey": "nuclei" + "key": "tls-impersonate" }, { "name": "HTTP API Endpoint", @@ -1296,8 +1197,7 @@ "dataType": "String", "shortFlag": "-hae", "longFlag": "-http-api-endpoint", - "key": "http-api-endpoint", - "commandKey": "nuclei" + "key": "http-api-endpoint" }, { "name": "Interactsh Server", @@ -1306,8 +1206,7 @@ "dataType": "String", "shortFlag": "-iserver", "longFlag": "-interactsh-server", - "key": "interactsh-server", - "commandKey": "nuclei" + "key": "interactsh-server" }, { "name": "Interactsh Token", @@ -1316,8 +1215,7 @@ "dataType": "String", "shortFlag": "-itoken", "longFlag": "-interactsh-token", - "key": "interactsh-token", - "commandKey": "nuclei" + "key": "interactsh-token" }, { "name": "Interactions Cache Size", @@ -1325,8 +1223,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-interactions-cache-size", - "key": "interactions-cache-size", - "commandKey": "nuclei" + "key": "interactions-cache-size" }, { "name": "Interactions Eviction", @@ -1334,8 +1231,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-interactions-eviction", - "key": "interactions-eviction", - "commandKey": "nuclei" + "key": "interactions-eviction" }, { "name": "Interactions Poll Duration", @@ -1343,8 +1239,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-interactions-poll-duration", - "key": "interactions-poll-duration", - "commandKey": "nuclei" + "key": "interactions-poll-duration" }, { "name": "Interactions Cooldown Period", @@ -1352,8 +1247,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-interactions-cooldown-period", - "key": "interactions-cooldown-period", - "commandKey": "nuclei" + "key": "interactions-cooldown-period" }, { "name": "No Interactsh", @@ -1362,8 +1256,7 @@ "dataType": "Boolean", "shortFlag": "-ni", "longFlag": "-no-interactsh", - "key": "no-interactsh", - "commandKey": "nuclei" + "key": "no-interactsh" }, { "name": "Fuzzing Type", @@ -1404,8 +1297,7 @@ } ] }, - "key": "fuzzing-type", - "commandKey": "nuclei" + "key": "fuzzing-type" }, { "name": "Fuzzing Mode", @@ -1432,8 +1324,7 @@ } ] }, - "key": "fuzzing-mode", - "commandKey": "nuclei" + "key": "fuzzing-mode" }, { "name": "Fuzz", @@ -1441,8 +1332,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-fuzz", - "key": "fuzz", - "commandKey": "nuclei" + "key": "fuzz" }, { "name": "DAST", @@ -1450,8 +1340,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-dast", - "key": "dast", - "commandKey": "nuclei" + "key": "dast" }, { "name": "DAST Server", @@ -1460,8 +1349,7 @@ "dataType": "Boolean", "shortFlag": "-dts", "longFlag": "-dast-server", - "key": "dast-server", - "commandKey": "nuclei" + "key": "dast-server" }, { "name": "DAST Report", @@ -1470,8 +1358,7 @@ "dataType": "Boolean", "shortFlag": "-dtr", "longFlag": "-dast-report", - "key": "dast-report", - "commandKey": "nuclei" + "key": "dast-report" }, { "name": "DAST Server Token", @@ -1480,8 +1367,7 @@ "dataType": "String", "shortFlag": "-dtst", "longFlag": "-dast-server-token", - "key": "dast-server-token", - "commandKey": "nuclei" + "key": "dast-server-token" }, { "name": "DAST Server Address", @@ -1490,8 +1376,7 @@ "dataType": "String", "shortFlag": "-dtsa", "longFlag": "-dast-server-address", - "key": "dast-server-address", - "commandKey": "nuclei" + "key": "dast-server-address" }, { "name": "Display Fuzz Points", @@ -1499,8 +1384,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-dfp", - "key": "display-fuzz-points", - "commandKey": "nuclei" + "key": "display-fuzz-points" }, { "name": "Fuzz Param Frequency", @@ -1508,8 +1392,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-fuzz-param-frequency", - "key": "fuzz-param-frequency", - "commandKey": "nuclei" + "key": "fuzz-param-frequency" }, { "name": "Fuzz Aggression", @@ -1543,8 +1426,7 @@ } ] }, - "key": "fuzz-aggression", - "commandKey": "nuclei" + "key": "fuzz-aggression" }, { "name": "Fuzz Scope", @@ -1554,8 +1436,7 @@ "isRepeatable": true, "shortFlag": "-cs", "longFlag": "-fuzz-scope", - "key": "fuzz-scope", - "commandKey": "nuclei" + "key": "fuzz-scope" }, { "name": "Fuzz Out Scope", @@ -1565,8 +1446,7 @@ "isRepeatable": true, "shortFlag": "-cos", "longFlag": "-fuzz-out-scope", - "key": "fuzz-out-scope", - "commandKey": "nuclei" + "key": "fuzz-out-scope" }, { "name": "Uncover", @@ -1575,8 +1455,7 @@ "dataType": "Boolean", "shortFlag": "-uc", "longFlag": "-uncover", - "key": "uncover", - "commandKey": "nuclei" + "key": "uncover" }, { "name": "Uncover Query", @@ -1586,8 +1465,7 @@ "isRepeatable": true, "shortFlag": "-uq", "longFlag": "-uncover-query", - "key": "uncover-query", - "commandKey": "nuclei" + "key": "uncover-query" }, { "name": "Uncover Engine", @@ -1597,8 +1475,7 @@ "isRepeatable": true, "shortFlag": "-ue", "longFlag": "-uncover-engine", - "key": "uncover-engine", - "commandKey": "nuclei" + "key": "uncover-engine" }, { "name": "Uncover Field", @@ -1607,8 +1484,7 @@ "dataType": "String", "shortFlag": "-uf", "longFlag": "-uncover-field", - "key": "uncover-field", - "commandKey": "nuclei" + "key": "uncover-field" }, { "name": "Uncover Limit", @@ -1617,8 +1493,7 @@ "dataType": "Number", "shortFlag": "-ul", "longFlag": "-uncover-limit", - "key": "uncover-limit", - "commandKey": "nuclei" + "key": "uncover-limit" }, { "name": "Uncover Rate Limit", @@ -1627,8 +1502,7 @@ "dataType": "Number", "shortFlag": "-ur", "longFlag": "-uncover-ratelimit", - "key": "uncover-ratelimit", - "commandKey": "nuclei" + "key": "uncover-ratelimit" }, { "name": "Rate Limit", @@ -1637,8 +1511,7 @@ "dataType": "Number", "shortFlag": "-rl", "longFlag": "-rate-limit", - "key": "rate-limit", - "commandKey": "nuclei" + "key": "rate-limit" }, { "name": "Rate Limit Duration", @@ -1647,8 +1520,7 @@ "dataType": "String", "shortFlag": "-rld", "longFlag": "-rate-limit-duration", - "key": "rate-limit-duration", - "commandKey": "nuclei" + "key": "rate-limit-duration" }, { "name": "Rate Limit Minute", @@ -1657,8 +1529,7 @@ "dataType": "Number", "shortFlag": "-rlm", "longFlag": "-rate-limit-minute", - "key": "rate-limit-minute", - "commandKey": "nuclei" + "key": "rate-limit-minute" }, { "name": "Bulk Size", @@ -1667,8 +1538,7 @@ "dataType": "Number", "shortFlag": "-bs", "longFlag": "-bulk-size", - "key": "bulk-size", - "commandKey": "nuclei" + "key": "bulk-size" }, { "name": "Concurrency", @@ -1677,8 +1547,7 @@ "dataType": "Number", "shortFlag": "-c", "longFlag": "-concurrency", - "key": "concurrency", - "commandKey": "nuclei" + "key": "concurrency" }, { "name": "Headless Bulk Size", @@ -1687,8 +1556,7 @@ "dataType": "Number", "shortFlag": "-hbs", "longFlag": "-headless-bulk-size", - "key": "headless-bulk-size", - "commandKey": "nuclei" + "key": "headless-bulk-size" }, { "name": "Headless Concurrency", @@ -1696,8 +1564,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-headless-concurrency", - "key": "headless-concurrency", - "commandKey": "nuclei" + "key": "headless-concurrency" }, { "name": "JS Concurrency", @@ -1706,8 +1573,7 @@ "dataType": "Number", "shortFlag": "-jsc", "longFlag": "-js-concurrency", - "key": "js-concurrency", - "commandKey": "nuclei" + "key": "js-concurrency" }, { "name": "Payload Concurrency", @@ -1716,8 +1582,7 @@ "dataType": "Number", "shortFlag": "-pc", "longFlag": "-payload-concurrency", - "key": "payload-concurrency", - "commandKey": "nuclei" + "key": "payload-concurrency" }, { "name": "Probe Concurrency", @@ -1726,8 +1591,7 @@ "dataType": "Number", "shortFlag": "-prc", "longFlag": "-probe-concurrency", - "key": "probe-concurrency", - "commandKey": "nuclei" + "key": "probe-concurrency" }, { "name": "Timeout", @@ -1735,8 +1599,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-timeout", - "key": "timeout", - "commandKey": "nuclei" + "key": "timeout" }, { "name": "Retries", @@ -1744,8 +1607,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-retries", - "key": "retries", - "commandKey": "nuclei" + "key": "retries" }, { "name": "Leave Default Ports", @@ -1754,8 +1616,7 @@ "dataType": "Boolean", "shortFlag": "-ldp", "longFlag": "-leave-default-ports", - "key": "leave-default-ports", - "commandKey": "nuclei" + "key": "leave-default-ports" }, { "name": "Max Host Error", @@ -1764,8 +1625,7 @@ "dataType": "Number", "shortFlag": "-mhe", "longFlag": "-max-host-error", - "key": "max-host-error", - "commandKey": "nuclei" + "key": "max-host-error" }, { "name": "Track Error", @@ -1775,8 +1635,7 @@ "isRepeatable": true, "shortFlag": "-te", "longFlag": "-track-error", - "key": "track-error", - "commandKey": "nuclei" + "key": "track-error" }, { "name": "No MHE", @@ -1785,8 +1644,7 @@ "dataType": "Boolean", "shortFlag": "-nmhe", "longFlag": "-no-mhe", - "key": "no-mhe", - "commandKey": "nuclei" + "key": "no-mhe" }, { "name": "Project", @@ -1794,8 +1652,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-project", - "key": "project", - "commandKey": "nuclei" + "key": "project" }, { "name": "Project Path", @@ -1803,8 +1660,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-project-path", - "key": "project-path", - "commandKey": "nuclei" + "key": "project-path" }, { "name": "Stop At First Match", @@ -1813,8 +1669,7 @@ "dataType": "Boolean", "shortFlag": "-spm", "longFlag": "-stop-at-first-match", - "key": "stop-at-first-match", - "commandKey": "nuclei" + "key": "stop-at-first-match" }, { "name": "Stream", @@ -1822,8 +1677,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-stream", - "key": "stream", - "commandKey": "nuclei" + "key": "stream" }, { "name": "Scan Strategy", @@ -1857,8 +1711,7 @@ } ] }, - "key": "scan-strategy", - "commandKey": "nuclei" + "key": "scan-strategy" }, { "name": "Input Read Timeout", @@ -1867,8 +1720,7 @@ "dataType": "String", "shortFlag": "-irt", "longFlag": "-input-read-timeout", - "key": "input-read-timeout", - "commandKey": "nuclei" + "key": "input-read-timeout" }, { "name": "No HTTPX", @@ -1877,8 +1729,7 @@ "dataType": "Boolean", "shortFlag": "-nh", "longFlag": "-no-httpx", - "key": "no-httpx", - "commandKey": "nuclei" + "key": "no-httpx" }, { "name": "No Stdin", @@ -1886,8 +1737,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-no-stdin", - "key": "no-stdin", - "commandKey": "nuclei" + "key": "no-stdin" }, { "name": "Headless", @@ -1895,8 +1745,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-headless", - "key": "headless", - "commandKey": "nuclei" + "key": "headless" }, { "name": "Page Timeout", @@ -1904,8 +1753,7 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "-page-timeout", - "key": "page-timeout", - "commandKey": "nuclei" + "key": "page-timeout" }, { "name": "Show Browser", @@ -1914,8 +1762,7 @@ "dataType": "Boolean", "shortFlag": "-sb", "longFlag": "-show-browser", - "key": "show-browser", - "commandKey": "nuclei" + "key": "show-browser" }, { "name": "Headless Options", @@ -1925,8 +1772,7 @@ "isRepeatable": true, "shortFlag": "-ho", "longFlag": "-headless-options", - "key": "headless-options", - "commandKey": "nuclei" + "key": "headless-options" }, { "name": "System Chrome", @@ -1935,8 +1781,7 @@ "dataType": "Boolean", "shortFlag": "-sc", "longFlag": "-system-chrome", - "key": "system-chrome", - "commandKey": "nuclei" + "key": "system-chrome" }, { "name": "List Headless Action", @@ -1945,8 +1790,7 @@ "dataType": "Boolean", "shortFlag": "-lha", "longFlag": "-list-headless-action", - "key": "list-headless-action", - "commandKey": "nuclei" + "key": "list-headless-action" }, { "name": "Debug", @@ -1954,8 +1798,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-debug", - "key": "debug", - "commandKey": "nuclei" + "key": "debug" }, { "name": "Debug Request", @@ -1964,8 +1807,7 @@ "dataType": "Boolean", "shortFlag": "-dreq", "longFlag": "-debug-req", - "key": "debug-req", - "commandKey": "nuclei" + "key": "debug-req" }, { "name": "Debug Response", @@ -1974,8 +1816,7 @@ "dataType": "Boolean", "shortFlag": "-dresp", "longFlag": "-debug-resp", - "key": "debug-resp", - "commandKey": "nuclei" + "key": "debug-resp" }, { "name": "Proxy", @@ -1985,8 +1826,7 @@ "isRepeatable": true, "shortFlag": "-p", "longFlag": "-proxy", - "key": "proxy", - "commandKey": "nuclei" + "key": "proxy" }, { "name": "Proxy Internal", @@ -1995,8 +1835,7 @@ "dataType": "Boolean", "shortFlag": "-pi", "longFlag": "-proxy-internal", - "key": "proxy-internal", - "commandKey": "nuclei" + "key": "proxy-internal" }, { "name": "List DSL Function", @@ -2005,8 +1844,7 @@ "dataType": "Boolean", "shortFlag": "-ldf", "longFlag": "-list-dsl-function", - "key": "list-dsl-function", - "commandKey": "nuclei" + "key": "list-dsl-function" }, { "name": "Trace Log", @@ -2015,8 +1853,7 @@ "dataType": "String", "shortFlag": "-tlog", "longFlag": "-trace-log", - "key": "trace-log", - "commandKey": "nuclei" + "key": "trace-log" }, { "name": "Error Log", @@ -2025,8 +1862,7 @@ "dataType": "String", "shortFlag": "-elog", "longFlag": "-error-log", - "key": "error-log", - "commandKey": "nuclei" + "key": "error-log" }, { "name": "Version", @@ -2034,8 +1870,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-version", - "key": "version", - "commandKey": "nuclei" + "key": "version" }, { "name": "Hang Monitor", @@ -2044,8 +1879,7 @@ "dataType": "Boolean", "shortFlag": "-hm", "longFlag": "-hang-monitor", - "key": "hang-monitor", - "commandKey": "nuclei" + "key": "hang-monitor" }, { "name": "Verbose", @@ -2054,8 +1888,7 @@ "dataType": "Boolean", "shortFlag": "-v", "longFlag": "-verbose", - "key": "verbose", - "commandKey": "nuclei" + "key": "verbose" }, { "name": "Profile Mem", @@ -2063,8 +1896,7 @@ "parameterType": "Option", "dataType": "String", "longFlag": "-profile-mem", - "key": "profile-mem", - "commandKey": "nuclei" + "key": "profile-mem" }, { "name": "Vv", @@ -2072,8 +1904,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-vv", - "key": "vv", - "commandKey": "nuclei" + "key": "vv" }, { "name": "Show Var Dump", @@ -2082,8 +1913,7 @@ "dataType": "Boolean", "shortFlag": "-svd", "longFlag": "-show-var-dump", - "key": "show-var-dump", - "commandKey": "nuclei" + "key": "show-var-dump" }, { "name": "Var Dump Limit", @@ -2092,8 +1922,7 @@ "dataType": "Number", "shortFlag": "-vdl", "longFlag": "-var-dump-limit", - "key": "var-dump-limit", - "commandKey": "nuclei" + "key": "var-dump-limit" }, { "name": "Enable Pprof", @@ -2102,8 +1931,7 @@ "dataType": "Boolean", "shortFlag": "-ep", "longFlag": "-enable-pprof", - "key": "enable-pprof", - "commandKey": "nuclei" + "key": "enable-pprof" }, { "name": "Templates Version", @@ -2112,8 +1940,7 @@ "dataType": "Boolean", "shortFlag": "-tv", "longFlag": "-templates-version", - "key": "templates-version", - "commandKey": "nuclei" + "key": "templates-version" }, { "name": "Health Check", @@ -2122,8 +1949,7 @@ "dataType": "Boolean", "shortFlag": "-hc", "longFlag": "-health-check", - "key": "health-check", - "commandKey": "nuclei" + "key": "health-check" }, { "name": "Update", @@ -2132,8 +1958,7 @@ "dataType": "Boolean", "shortFlag": "-up", "longFlag": "-update", - "key": "update", - "commandKey": "nuclei" + "key": "update" }, { "name": "Update Templates", @@ -2142,8 +1967,7 @@ "dataType": "Boolean", "shortFlag": "-ut", "longFlag": "-update-templates", - "key": "update-templates", - "commandKey": "nuclei" + "key": "update-templates" }, { "name": "Update Template Dir", @@ -2152,8 +1976,7 @@ "dataType": "String", "shortFlag": "-ud", "longFlag": "-update-template-dir", - "key": "update-template-dir", - "commandKey": "nuclei" + "key": "update-template-dir" }, { "name": "Disable Update Check", @@ -2162,8 +1985,7 @@ "dataType": "Boolean", "shortFlag": "-duc", "longFlag": "-disable-update-check", - "key": "disable-update-check", - "commandKey": "nuclei" + "key": "disable-update-check" }, { "name": "Stats", @@ -2171,8 +1993,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-stats", - "key": "stats", - "commandKey": "nuclei" + "key": "stats" }, { "name": "Stats Json", @@ -2181,8 +2002,7 @@ "dataType": "Boolean", "shortFlag": "-sj", "longFlag": "-stats-json", - "key": "stats-json", - "commandKey": "nuclei" + "key": "stats-json" }, { "name": "Stats Interval", @@ -2191,8 +2011,7 @@ "dataType": "Number", "shortFlag": "-si", "longFlag": "-stats-interval", - "key": "stats-interval", - "commandKey": "nuclei" + "key": "stats-interval" }, { "name": "Metrics Port", @@ -2201,8 +2020,7 @@ "dataType": "Number", "shortFlag": "-mp", "longFlag": "-metrics-port", - "key": "metrics-port", - "commandKey": "nuclei" + "key": "metrics-port" }, { "name": "Http Stats", @@ -2211,8 +2029,7 @@ "dataType": "Boolean", "shortFlag": "-hps", "longFlag": "-http-stats", - "key": "http-stats", - "commandKey": "nuclei" + "key": "http-stats" }, { "name": "Auth", @@ -2220,8 +2037,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-auth", - "key": "auth", - "commandKey": "nuclei" + "key": "auth" }, { "name": "Team Id", @@ -2230,8 +2046,7 @@ "dataType": "String", "shortFlag": "-tid", "longFlag": "-team-id", - "key": "team-id", - "commandKey": "nuclei" + "key": "team-id" }, { "name": "Cloud Upload", @@ -2240,8 +2055,7 @@ "dataType": "Boolean", "shortFlag": "-cup", "longFlag": "-cloud-upload", - "key": "cloud-upload", - "commandKey": "nuclei" + "key": "cloud-upload" }, { "name": "Scan Id", @@ -2250,8 +2064,7 @@ "dataType": "String", "shortFlag": "-sid", "longFlag": "-scan-id", - "key": "scan-id", - "commandKey": "nuclei" + "key": "scan-id" }, { "name": "Scan Name", @@ -2260,8 +2073,7 @@ "dataType": "String", "shortFlag": "-sname", "longFlag": "-scan-name", - "key": "scan-name", - "commandKey": "nuclei" + "key": "scan-name" }, { "name": "Dashboard", @@ -2270,8 +2082,7 @@ "dataType": "Boolean", "shortFlag": "-pd", "longFlag": "-dashboard", - "key": "dashboard", - "commandKey": "nuclei" + "key": "dashboard" }, { "name": "Dashboard Upload", @@ -2280,8 +2091,7 @@ "dataType": "String", "shortFlag": "-pdu", "longFlag": "-dashboard-upload", - "key": "dashboard-upload", - "commandKey": "nuclei" + "key": "dashboard-upload" }, { "name": "Secret File", @@ -2291,8 +2101,7 @@ "isRepeatable": true, "shortFlag": "-sf", "longFlag": "-secret-file", - "key": "secret-file", - "commandKey": "nuclei" + "key": "secret-file" }, { "name": "Prefetch Secrets", @@ -2301,8 +2110,7 @@ "dataType": "Boolean", "shortFlag": "-ps", "longFlag": "-prefetch-secrets", - "key": "prefetch-secrets", - "commandKey": "nuclei" + "key": "prefetch-secrets" } ] -} \ No newline at end of file +} diff --git a/public/tools-collection/shuffledns.json b/public/tools-collection/shuffledns.json index 917428e..5c2145e 100644 --- a/public/tools-collection/shuffledns.json +++ b/public/tools-collection/shuffledns.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "shuffledns", + "binaryName": "shuffledns", "displayName": "ShuffleDNS", "info": { "description": "MassDNS wrapper written in go to enumerate valid subdomains using active bruteforce as well as resolve subdomains with wildcard filtering and easy input-output support.", "version": "1.2.1", "url": "https://github.com/projectdiscovery/shuffledns" }, - "commands": [ - { - "name": "shuffledns", - "description": "shuffleDNS is a wrapper around massdns written in go that allows you to enumerate valid subdomains using active bruteforce as well as resolve subdomains with wildcard handling and easy input-output support.", - "sortOrder": 1, - "key": "shuffledns" - } - ], + "commands": [], "parameters": [ { "name": "Domain", @@ -25,8 +18,7 @@ "shortFlag": "-d", "longFlag": "-domain", "sortOrder": 1, - "key": "domain", - "commandKey": "shuffledns" + "key": "domain" }, { "name": "List", @@ -36,8 +28,7 @@ "shortFlag": "-l", "longFlag": "-list", "sortOrder": 2, - "key": "list", - "commandKey": "shuffledns" + "key": "list" }, { "name": "Wordlist", @@ -47,8 +38,7 @@ "shortFlag": "-w", "longFlag": "-wordlist", "sortOrder": 3, - "key": "wordlist", - "commandKey": "shuffledns" + "key": "wordlist" }, { "name": "Resolver", @@ -58,8 +48,7 @@ "shortFlag": "-r", "longFlag": "-resolver", "sortOrder": 4, - "key": "resolver", - "commandKey": "shuffledns" + "key": "resolver" }, { "name": "Trusted Resolver", @@ -69,8 +58,7 @@ "shortFlag": "-tr", "longFlag": "-trusted-resolver", "sortOrder": 5, - "key": "trusted-resolver", - "commandKey": "shuffledns" + "key": "trusted-resolver" }, { "name": "Raw Input", @@ -80,8 +68,7 @@ "shortFlag": "-ri", "longFlag": "-raw-input", "sortOrder": 6, - "key": "raw-input", - "commandKey": "shuffledns" + "key": "raw-input" }, { "name": "Mode", @@ -91,7 +78,6 @@ "longFlag": "-mode", "sortOrder": 7, "key": "mode", - "commandKey": "shuffledns", "enum": { "values": [ { @@ -117,8 +103,7 @@ "shortFlag": "-t", "longFlag": "-t", "sortOrder": 8, - "key": "t", - "commandKey": "shuffledns" + "key": "t" }, { "name": "Update", @@ -128,8 +113,7 @@ "shortFlag": "-up", "longFlag": "-update", "sortOrder": 9, - "key": "update", - "commandKey": "shuffledns" + "key": "update" }, { "name": "Disable Update Check", @@ -139,8 +123,7 @@ "shortFlag": "-duc", "longFlag": "-disable-update-check", "sortOrder": 10, - "key": "disable-update-check", - "commandKey": "shuffledns" + "key": "disable-update-check" }, { "name": "Output", @@ -150,8 +133,7 @@ "shortFlag": "-o", "longFlag": "-output", "sortOrder": 11, - "key": "output", - "commandKey": "shuffledns" + "key": "output" }, { "name": "JSON Output", @@ -161,8 +143,7 @@ "shortFlag": "-j", "longFlag": "-json", "sortOrder": 12, - "key": "json", - "commandKey": "shuffledns" + "key": "json" }, { "name": "Wildcard Output", @@ -172,8 +153,7 @@ "shortFlag": "-wo", "longFlag": "-wildcard-output", "sortOrder": 13, - "key": "wildcard-output", - "commandKey": "shuffledns" + "key": "wildcard-output" }, { "name": "Massdns", @@ -183,8 +163,7 @@ "shortFlag": "-m", "longFlag": "-massdns", "sortOrder": 14, - "key": "massdns", - "commandKey": "shuffledns" + "key": "massdns" }, { "name": "Massdns Cmd", @@ -194,8 +173,7 @@ "shortFlag": "-mcmd", "longFlag": "-massdns-cmd", "sortOrder": 15, - "key": "massdns-cmd", - "commandKey": "shuffledns" + "key": "massdns-cmd" }, { "name": "Directory", @@ -204,8 +182,7 @@ "dataType": "String", "longFlag": "-directory", "sortOrder": 16, - "key": "directory", - "commandKey": "shuffledns" + "key": "directory" }, { "name": "Retries", @@ -214,8 +191,7 @@ "dataType": "Number", "longFlag": "-retries", "sortOrder": 17, - "key": "retries", - "commandKey": "shuffledns" + "key": "retries" }, { "name": "Strict Wildcard", @@ -225,8 +201,7 @@ "shortFlag": "-sw", "longFlag": "-strict-wildcard", "sortOrder": 18, - "key": "strict-wildcard", - "commandKey": "shuffledns" + "key": "strict-wildcard" }, { "name": "Wildcard Threads", @@ -235,8 +210,7 @@ "dataType": "Number", "longFlag": "-wt", "sortOrder": 19, - "key": "wt", - "commandKey": "shuffledns" + "key": "wt" }, { "name": "Silent", @@ -245,8 +219,7 @@ "dataType": "Boolean", "longFlag": "-silent", "sortOrder": 20, - "key": "silent", - "commandKey": "shuffledns" + "key": "silent" }, { "name": "Version", @@ -255,8 +228,7 @@ "dataType": "Boolean", "longFlag": "-version", "sortOrder": 21, - "key": "version", - "commandKey": "shuffledns" + "key": "version" }, { "name": "Verbose", @@ -265,8 +237,7 @@ "dataType": "Boolean", "longFlag": "-v", "sortOrder": 22, - "key": "v", - "commandKey": "shuffledns" + "key": "v" }, { "name": "No Color", @@ -276,8 +247,7 @@ "shortFlag": "-nc", "longFlag": "-no-color", "sortOrder": 23, - "key": "no-color", - "commandKey": "shuffledns" + "key": "no-color" } ] } diff --git a/public/tools-collection/subfinder.json b/public/tools-collection/subfinder.json index 26cd4f8..244db11 100644 --- a/public/tools-collection/subfinder.json +++ b/public/tools-collection/subfinder.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "subfinder", + "binaryName": "subfinder", "displayName": "Subfinder", "info": { "description": "Subfinder is a subdomain discovery tool that discovers subdomains for websites by using passive online sources.", "version": "2.13.0", "url": "https://github.com/projectdiscovery/subfinder" }, - "commands": [ - { - "name": "subfinder", - "description": "Subfinder is a subdomain discovery tool that discovers subdomains for websites by using passive online sources.", - "sortOrder": 1, - "key": "subfinder" - } - ], + "commands": [], "parameters": [ { "name": "Target Domain", @@ -25,8 +18,7 @@ "dataType": "String", "arraySeparator": ",", "isRepeatable": true, - "key": "domain", - "commandKey": "subfinder" + "key": "domain" }, { "name": "Sources", @@ -37,8 +29,7 @@ "dataType": "String", "arraySeparator": ",", "isRepeatable": true, - "key": "sources", - "commandKey": "subfinder" + "key": "sources" }, { "name": "Recursive", @@ -46,8 +37,7 @@ "longFlag": "-recursive", "parameterType": "Flag", "dataType": "Boolean", - "key": "recursive", - "commandKey": "subfinder" + "key": "recursive" }, { "name": "Target Domains List", @@ -56,8 +46,7 @@ "shortFlag": "-dL", "parameterType": "Option", "dataType": "String", - "key": "list", - "commandKey": "subfinder" + "key": "list" }, { "name": "Output Directory", @@ -66,8 +55,7 @@ "shortFlag": "-oD", "parameterType": "Option", "dataType": "String", - "key": "output-dir", - "commandKey": "subfinder" + "key": "output-dir" }, { "name": "Report Statistics", @@ -75,8 +63,7 @@ "longFlag": "-stats", "parameterType": "Flag", "dataType": "Boolean", - "key": "stats", - "commandKey": "subfinder" + "key": "stats" }, { "name": "Timeout", @@ -84,8 +71,7 @@ "longFlag": "-timeout", "parameterType": "Option", "dataType": "Number", - "key": "timeout", - "commandKey": "subfinder" + "key": "timeout" }, { "name": "Resolvers File", @@ -94,8 +80,7 @@ "shortFlag": "-rL", "parameterType": "Option", "dataType": "String", - "key": "rlist", - "commandKey": "subfinder" + "key": "rlist" }, { "name": "Update", @@ -104,8 +89,7 @@ "shortFlag": "-up", "parameterType": "Flag", "dataType": "Boolean", - "key": "update", - "commandKey": "subfinder" + "key": "update" }, { "name": "Version", @@ -113,8 +97,7 @@ "longFlag": "-version", "parameterType": "Flag", "dataType": "Boolean", - "key": "version", - "commandKey": "subfinder" + "key": "version" }, { "name": "Exclude IPs", @@ -123,8 +106,7 @@ "shortFlag": "-ei", "parameterType": "Flag", "dataType": "Boolean", - "key": "exclude-ip", - "commandKey": "subfinder" + "key": "exclude-ip" }, { "name": "Proxy", @@ -132,8 +114,7 @@ "longFlag": "-proxy", "parameterType": "Option", "dataType": "String", - "key": "proxy", - "commandKey": "subfinder" + "key": "proxy" }, { "name": "No Color", @@ -142,8 +123,7 @@ "shortFlag": "-nc", "parameterType": "Flag", "dataType": "Boolean", - "key": "no-color", - "commandKey": "subfinder" + "key": "no-color" }, { "name": "Match", @@ -154,8 +134,7 @@ "dataType": "String", "arraySeparator": ",", "isRepeatable": true, - "key": "match", - "commandKey": "subfinder" + "key": "match" }, { "name": "Include IP", @@ -164,8 +143,7 @@ "shortFlag": "-oI", "parameterType": "Flag", "dataType": "Boolean", - "key": "ip", - "commandKey": "subfinder" + "key": "ip" }, { "name": "Active Domains Only", @@ -174,8 +152,7 @@ "shortFlag": "-nW", "parameterType": "Flag", "dataType": "Boolean", - "key": "active", - "commandKey": "subfinder" + "key": "active" }, { "name": "Rate Limits", @@ -185,8 +162,7 @@ "parameterType": "Option", "dataType": "String", "keyValueSeparator": "=", - "key": "rate-limits", - "commandKey": "subfinder" + "key": "rate-limits" }, { "name": "Disable Update Check", @@ -195,8 +171,7 @@ "shortFlag": "-duc", "parameterType": "Flag", "dataType": "Boolean", - "key": "disable-update-check", - "commandKey": "subfinder" + "key": "disable-update-check" }, { "name": "Collect Sources", @@ -205,8 +180,7 @@ "shortFlag": "-cs", "parameterType": "Flag", "dataType": "Boolean", - "key": "collect-sources", - "commandKey": "subfinder" + "key": "collect-sources" }, { "name": "Output File", @@ -215,8 +189,7 @@ "shortFlag": "-o", "parameterType": "Option", "dataType": "String", - "key": "output", - "commandKey": "subfinder" + "key": "output" }, { "name": "Verbose Output", @@ -224,8 +197,7 @@ "longFlag": "-v", "parameterType": "Flag", "dataType": "Boolean", - "key": "v", - "commandKey": "subfinder" + "key": "v" }, { "name": "Resolvers", @@ -235,8 +207,7 @@ "dataType": "String", "arraySeparator": ",", "isRepeatable": true, - "key": "r", - "commandKey": "subfinder" + "key": "r" }, { "name": "Json", @@ -245,8 +216,7 @@ "shortFlag": "-oJ", "parameterType": "Flag", "dataType": "Boolean", - "key": "json", - "commandKey": "subfinder" + "key": "json" }, { "name": "Configuration File", @@ -254,8 +224,7 @@ "longFlag": "-config", "parameterType": "Option", "dataType": "String", - "key": "config", - "commandKey": "subfinder" + "key": "config" }, { "name": "Rate Limit", @@ -264,8 +233,7 @@ "shortFlag": "-rl", "parameterType": "Option", "dataType": "Number", - "key": "rate-limit", - "commandKey": "subfinder" + "key": "rate-limit" }, { "name": "Filter", @@ -276,8 +244,7 @@ "dataType": "String", "arraySeparator": ",", "isRepeatable": true, - "key": "filter", - "commandKey": "subfinder" + "key": "filter" }, { "name": "Silent", @@ -285,8 +252,7 @@ "longFlag": "-silent", "parameterType": "Flag", "dataType": "Boolean", - "key": "silent", - "commandKey": "subfinder" + "key": "silent" }, { "name": "All Sources", @@ -294,8 +260,7 @@ "longFlag": "-all", "parameterType": "Flag", "dataType": "Boolean", - "key": "all", - "commandKey": "subfinder" + "key": "all" }, { "name": "List Sources", @@ -304,8 +269,7 @@ "shortFlag": "-ls", "parameterType": "Flag", "dataType": "Boolean", - "key": "list-sources", - "commandKey": "subfinder" + "key": "list-sources" }, { "name": "Exclude Sources", @@ -316,8 +280,7 @@ "dataType": "String", "arraySeparator": ",", "isRepeatable": true, - "key": "exclude-sources", - "commandKey": "subfinder" + "key": "exclude-sources" }, { "name": "Max Time", @@ -325,8 +288,7 @@ "longFlag": "-max-time", "parameterType": "Option", "dataType": "Number", - "key": "max-time", - "commandKey": "subfinder" + "key": "max-time" }, { "name": "Threads", @@ -334,8 +296,7 @@ "longFlag": "-t", "parameterType": "Option", "dataType": "Number", - "key": "t", - "commandKey": "subfinder" + "key": "t" }, { "name": "Provider Configuration File", @@ -344,8 +305,7 @@ "shortFlag": "-pc", "parameterType": "Option", "dataType": "String", - "key": "provider-config", - "commandKey": "subfinder" + "key": "provider-config" } ] } diff --git a/public/tools-collection/urlfinder.json b/public/tools-collection/urlfinder.json index 887958d..5283469 100644 --- a/public/tools-collection/urlfinder.json +++ b/public/tools-collection/urlfinder.json @@ -1,20 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "urlfinder", + "binaryName": "urlfinder", "displayName": "URLFinder", "info": { "description": "A streamlined tool for discovering associated URLs.", "version": "0.0.3", "url": "https://github.com/projectdiscovery/urlfinder" }, - "commands": [ - { - "name": "urlfinder", - "description": "A streamlined tool for discovering associated URLs.", - "sortOrder": 1, - "key": "urlfinder" - } - ], + "commands": [], "parameters": [ { "name": "Domain", @@ -23,8 +16,7 @@ "dataType": "String", "isRepeatable": true, "longFlag": "-d", - "key": "domain", - "commandKey": "urlfinder" + "key": "domain" }, { "name": "List of Domains", @@ -34,8 +26,7 @@ "isRepeatable": true, "longFlag": "-list", "arraySeparator": ",", - "key": "list", - "commandKey": "urlfinder" + "key": "list" }, { "name": "Sources", @@ -46,8 +37,7 @@ "shortFlag": "-s", "longFlag": "-sources", "arraySeparator": ",", - "key": "sources", - "commandKey": "urlfinder" + "key": "sources" }, { "name": "Exclude Sources", @@ -58,8 +48,7 @@ "shortFlag": "-es", "longFlag": "-exclude-sources", "arraySeparator": ",", - "key": "exclude-sources", - "commandKey": "urlfinder" + "key": "exclude-sources" }, { "name": "All", @@ -67,8 +56,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-all", - "key": "all", - "commandKey": "urlfinder" + "key": "all" }, { "name": "URL Scope", @@ -79,8 +67,7 @@ "shortFlag": "-us", "longFlag": "-url-scope", "arraySeparator": ",", - "key": "url-scope", - "commandKey": "urlfinder" + "key": "url-scope" }, { "name": "URL Out Scope", @@ -91,8 +78,7 @@ "shortFlag": "-uos", "longFlag": "-url-out-scope", "arraySeparator": ",", - "key": "url-out-scope", - "commandKey": "urlfinder" + "key": "url-out-scope" }, { "name": "Field Scope", @@ -101,8 +87,7 @@ "dataType": "String", "shortFlag": "-fs", "longFlag": "-field-scope", - "key": "field-scope", - "commandKey": "urlfinder" + "key": "field-scope" }, { "name": "No Scope", @@ -111,8 +96,7 @@ "dataType": "Boolean", "shortFlag": "-ns", "longFlag": "-no-scope", - "key": "no-scope", - "commandKey": "urlfinder" + "key": "no-scope" }, { "name": "Display Out Scope", @@ -121,8 +105,7 @@ "dataType": "Boolean", "shortFlag": "-do", "longFlag": "-display-out-scope", - "key": "display-out-scope", - "commandKey": "urlfinder" + "key": "display-out-scope" }, { "name": "Match", @@ -133,8 +116,7 @@ "shortFlag": "-m", "longFlag": "-match", "arraySeparator": ",", - "key": "match", - "commandKey": "urlfinder" + "key": "match" }, { "name": "Filter", @@ -145,8 +127,7 @@ "shortFlag": "-f", "longFlag": "-filter", "arraySeparator": ",", - "key": "filter", - "commandKey": "urlfinder" + "key": "filter" }, { "name": "Rate Limit", @@ -155,8 +136,7 @@ "dataType": "Number", "shortFlag": "-rl", "longFlag": "-rate-limit", - "key": "rate-limit", - "commandKey": "urlfinder" + "key": "rate-limit" }, { "name": "Rate Limits", @@ -165,8 +145,7 @@ "dataType": "String", "shortFlag": "-rls", "longFlag": "-rate-limits", - "key": "rate-limits", - "commandKey": "urlfinder" + "key": "rate-limits" }, { "name": "Update", @@ -175,8 +154,7 @@ "dataType": "Boolean", "shortFlag": "-up", "longFlag": "-update", - "key": "update", - "commandKey": "urlfinder" + "key": "update" }, { "name": "Verbose", @@ -185,8 +163,7 @@ "dataType": "Boolean", "shortFlag": "-v", "longFlag": "-verbose", - "key": "verbose", - "commandKey": "urlfinder" + "key": "verbose" }, { "name": "Silent", @@ -195,8 +172,7 @@ "dataType": "Boolean", "shortFlag": "-s", "longFlag": "-silent", - "key": "silent", - "commandKey": "urlfinder" + "key": "silent" }, { "name": "Timeout", @@ -205,8 +181,7 @@ "dataType": "Number", "shortFlag": "-to", "longFlag": "-timeout", - "key": "timeout", - "commandKey": "urlfinder" + "key": "timeout" }, { "name": "JSON Output", @@ -215,8 +190,7 @@ "dataType": "Boolean", "shortFlag": "-j", "longFlag": "-json", - "key": "json", - "commandKey": "urlfinder" + "key": "json" }, { "name": "CSV Output", @@ -224,8 +198,7 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "-csv", - "key": "csv", - "commandKey": "urlfinder" + "key": "csv" }, { "name": "Version", @@ -234,8 +207,7 @@ "dataType": "Boolean", "shortFlag": "-vrs", "longFlag": "-version", - "key": "version", - "commandKey": "urlfinder" + "key": "version" } ] } diff --git a/public/tools-collection/yt-dlp.json b/public/tools-collection/yt-dlp.json index 853d16e..ecdcc43 100644 --- a/public/tools-collection/yt-dlp.json +++ b/public/tools-collection/yt-dlp.json @@ -1,21 +1,13 @@ { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "yt-dlp", + "binaryName": "yt-dlp", "displayName": "yt-dlp", "info": { "description": "yt-dlp is a command-line program to download videos from YouTube and other sites.", "version": "2026.03.17", "url": "https://github.com/yt-dlp/yt-dlp" }, - "commands": [ - { - "name": "yt-dlp", - "description": "yt-dlp is a command-line program to download videos from YouTube and other sites.", - "sortOrder": 1, - "commandKey": "yt-dlp", - "key": "yt-dlp" - } - ], + "commands": [], "parameters": [ { "name": "Help", @@ -23,7 +15,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--help", - "commandKey": "yt-dlp", "key": "help" }, { @@ -32,7 +23,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--version", - "commandKey": "yt-dlp", "key": "version" }, { @@ -41,7 +31,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--update", - "commandKey": "yt-dlp", "key": "update" }, { @@ -50,7 +39,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-update", - "commandKey": "yt-dlp", "key": "no-update" }, { @@ -59,7 +47,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--update-to", - "commandKey": "yt-dlp", "key": "update-to" }, { @@ -68,7 +55,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--ignore-errors", - "commandKey": "yt-dlp", "key": "ignore-errors" }, { @@ -77,7 +63,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-abort-on-error", - "commandKey": "yt-dlp", "key": "no-abort-on-error" }, { @@ -86,7 +71,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--abort-on-error", - "commandKey": "yt-dlp", "key": "abort-on-error" }, { @@ -95,7 +79,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--dump-user-agent", - "commandKey": "yt-dlp", "key": "dump-user-agent" }, { @@ -104,7 +87,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--list-extractors", - "commandKey": "yt-dlp", "key": "list-extractors" }, { @@ -113,7 +95,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--extractor-descriptions", - "commandKey": "yt-dlp", "key": "extractor-descriptions" }, { @@ -122,7 +103,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--use-extractors", - "commandKey": "yt-dlp", "key": "use-extractors" }, { @@ -131,7 +111,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--default-search", - "commandKey": "yt-dlp", "key": "default-search" }, { @@ -140,7 +119,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--ignore-config", - "commandKey": "yt-dlp", "key": "ignore-config" }, { @@ -149,7 +127,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-config-locations", - "commandKey": "yt-dlp", "key": "no-config-locations" }, { @@ -159,7 +136,6 @@ "dataType": "String", "isRepeatable": true, "longFlag": "--config-locations", - "commandKey": "yt-dlp", "key": "config-locations" }, { @@ -169,7 +145,6 @@ "dataType": "String", "isRepeatable": true, "longFlag": "--plugin-dirs", - "commandKey": "yt-dlp", "key": "plugin-dirs" }, { @@ -178,7 +153,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-plugin-dirs", - "commandKey": "yt-dlp", "key": "no-plugin-dirs" }, { @@ -187,7 +161,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--flat-playlist", - "commandKey": "yt-dlp", "key": "flat-playlist" }, { @@ -196,7 +169,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-flat-playlist", - "commandKey": "yt-dlp", "key": "no-flat-playlist" }, { @@ -206,7 +178,6 @@ "dataType": "Enum", "isRepeatable": true, "longFlag": "--preset-alias", - "commandKey": "yt-dlp", "key": "preset-alias", "enum": { "values": [ @@ -239,7 +210,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--proxy", - "commandKey": "yt-dlp", "key": "proxy" }, { @@ -248,7 +218,6 @@ "parameterType": "Option", "dataType": "Number", "longFlag": "--socket-timeout", - "commandKey": "yt-dlp", "key": "socket-timeout" }, { @@ -257,7 +226,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--source-address", - "commandKey": "yt-dlp", "key": "source-address" }, { @@ -266,7 +234,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--impersonate", - "commandKey": "yt-dlp", "key": "impersonate" }, { @@ -275,7 +242,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--list-impersonate-targets", - "commandKey": "yt-dlp", "key": "list-impersonate-targets" }, { @@ -284,7 +250,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--force-ipv4", - "commandKey": "yt-dlp", "key": "force-ipv4" }, { @@ -293,7 +258,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--force-ipv6", - "commandKey": "yt-dlp", "key": "force-ipv6" }, { @@ -302,7 +266,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--enable-file-urls", - "commandKey": "yt-dlp", "key": "enable-file-urls" }, { @@ -311,7 +274,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--geo-verification-proxy", - "commandKey": "yt-dlp", "key": "geo-verification-proxy" }, { @@ -320,7 +282,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--xff", - "commandKey": "yt-dlp", "key": "xff" }, { @@ -330,7 +291,6 @@ "dataType": "String", "longFlag": "--playlist-items", "shortFlag": "-I", - "commandKey": "yt-dlp", "key": "playlist-items" }, { @@ -339,7 +299,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--min-filesize", - "commandKey": "yt-dlp", "key": "min-filesize" }, { @@ -348,7 +307,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--max-filesize", - "commandKey": "yt-dlp", "key": "max-filesize" }, { @@ -357,7 +315,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--date", - "commandKey": "yt-dlp", "key": "date" }, { @@ -366,7 +323,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--datebefore", - "commandKey": "yt-dlp", "key": "datebefore" }, { @@ -375,7 +331,6 @@ "parameterType": "Option", "dataType": "String", "longFlag": "--dateafter", - "commandKey": "yt-dlp", "key": "dateafter" }, { @@ -385,7 +340,6 @@ "dataType": "String", "isRepeatable": true, "longFlag": "--match-filters", - "commandKey": "yt-dlp", "key": "match-filters" }, { @@ -394,7 +348,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-match-filters", - "commandKey": "yt-dlp", "key": "no-match-filters" }, { @@ -404,7 +357,6 @@ "dataType": "String", "isRepeatable": true, "longFlag": "--break-match-filters", - "commandKey": "yt-dlp", "key": "break-match-filters" }, { @@ -413,7 +365,6 @@ "parameterType": "Flag", "dataType": "Boolean", "longFlag": "--no-break-match-filters", - "commandKey": "yt-dlp", "key": "no-break-match-filters" } ] diff --git a/public/tools.json b/public/tools.json index 3cdb231..e748007 100644 --- a/public/tools.json +++ b/public/tools.json @@ -1,6 +1,6 @@ [ { - "name": "asnmap", + "binaryName": "asnmap", "displayName": "ASNMap", "description": "Go CLI and Library for quickly mapping organization network ranges using ASN information.", "info": { @@ -10,7 +10,7 @@ } }, { - "name": "cdncheck", + "binaryName": "cdncheck", "displayName": "CDNCheck", "description": "cdncheck is a tool for identifying the technology associated with dns / ip network addresses.", "info": { @@ -20,7 +20,7 @@ } }, { - "name": "curl", + "binaryName": "curl", "displayName": "Curl", "description": "curl is a command line tool and library for transferring data with URLs.", "info": { @@ -30,7 +30,7 @@ } }, { - "name": "dnsx", + "binaryName": "dnsx", "displayName": "DNSX", "description": "A fast and multi-purpose DNS toolkit designed for running DNS queries.", "info": { @@ -40,7 +40,7 @@ } }, { - "name": "gospider", + "binaryName": "gospider", "displayName": "GoSpider", "description": "Fast web spider written in Go.", "info": { @@ -50,7 +50,7 @@ } }, { - "name": "httpx", + "binaryName": "httpx", "displayName": "Httpx", "description": "Httpx is a fast and multi-purpose HTTP toolkit that allows running multiple probes using the retryablehttp library.", "info": { @@ -60,7 +60,7 @@ } }, { - "name": "katana", + "binaryName": "katana", "displayName": "Katana", "description": "Katana is a fast crawler focused on execution in automation pipelines offering both headless and non-headless crawling.", "info": { @@ -70,7 +70,16 @@ } }, { - "name": "naabu", + "binaryName": "mapcidr", + "displayName": "Mapcidr", + "description": "Perform multiple operations on CIDR and IP ranges.", + "info": { + "description": "Perform multiple operations on CIDR and IP ranges.", + "url": "https://github.com/projectdiscovery/mapcidr" + } + }, + { + "binaryName": "naabu", "displayName": "Naabu", "description": "Fast port scanner for discovering open ports on hosts.", "info": { @@ -80,7 +89,16 @@ } }, { - "name": "nuclei", + "binaryName": "notify", + "displayName": "Notify", + "description": "Stream output from files or pipes to notification providers.", + "info": { + "url": "https://github.com/projectdiscovery/notify", + "description": "Stream output from files or pipes to notification providers." + } + }, + { + "binaryName": "nuclei", "displayName": "Nuclei", "description": "Nuclei is a fast, template based vulnerability scanner focusing on extensive configurability, massive extensibility and ease of use.", "info": { @@ -90,7 +108,7 @@ } }, { - "name": "shuffledns", + "binaryName": "shuffledns", "displayName": "ShuffleDNS", "description": "MassDNS wrapper written in go to enumerate valid subdomains using active bruteforce as well as resolve subdomains with wildcard filtering and easy input-output support.", "info": { @@ -100,7 +118,7 @@ } }, { - "name": "subfinder", + "binaryName": "subfinder", "displayName": "Subfinder", "description": "Subfinder is a subdomain discovery tool that discovers subdomains for websites by using passive online sources.", "info": { @@ -110,7 +128,7 @@ } }, { - "name": "urlfinder", + "binaryName": "urlfinder", "displayName": "URLFinder", "description": "A streamlined tool for discovering associated URLs.", "info": { @@ -120,7 +138,7 @@ } }, { - "name": "yt-dlp", + "binaryName": "yt-dlp", "displayName": "yt-dlp", "description": "yt-dlp is a command-line program to download videos from YouTube and other sites.", "info": { diff --git a/registry/commandly/__tests__/generated-command.test.tsx b/registry/commandly/__tests__/generated-command.test.tsx index 20ad239..f73a8f6 100644 --- a/registry/commandly/__tests__/generated-command.test.tsx +++ b/registry/commandly/__tests__/generated-command.test.tsx @@ -2,7 +2,7 @@ import { GeneratedCommand } from "../generated-command"; import { render, screen } from "@testing-library/react"; const testTool = { - name: "tool", + binaryName: "tool", displayName: "Tool", commands: [{ key: "test-key", name: "test", sortOrder: 0 }], parameters: [], @@ -21,7 +21,7 @@ describe("GeneratedCommand", () => { it("emits repeated flags for a repeatable Option with an array value", () => { const tool = { - name: "curl", + binaryName: "curl", displayName: "Curl", commands: [{ key: "curl", name: "curl", sortOrder: 1 }], parameters: [ @@ -53,7 +53,7 @@ describe("GeneratedCommand", () => { it("emits a single joined token for a repeatable Option with arraySeparator", () => { const tool = { - name: "mytool", + binaryName: "mytool", displayName: "My Tool", commands: [{ key: "mytool", name: "mytool", sortOrder: 1 }], parameters: [ @@ -82,7 +82,7 @@ describe("GeneratedCommand", () => { it("does not emit a flag when all repeatable values are empty strings", () => { const tool = { - name: "curl", + binaryName: "curl", displayName: "Curl", commands: [{ key: "curl", name: "curl", sortOrder: 1 }], parameters: [ @@ -109,7 +109,7 @@ describe("GeneratedCommand", () => { it("emits a repeatable Flag multiple times based on numeric value", () => { const tool = { - name: "ssh", + binaryName: "ssh", displayName: "SSH", commands: [{ key: "ssh", name: "ssh", sortOrder: 1 }], parameters: [ @@ -137,7 +137,7 @@ describe("GeneratedCommand", () => { it("does not duplicate argument-type parameters in the generated command", () => { const tool = { - name: "curl", + binaryName: "curl", displayName: "Curl", commands: [{ key: "curl", name: "curl", sortOrder: 1 }], parameters: [ @@ -164,4 +164,133 @@ describe("GeneratedCommand", () => { const output = screen.getByText(/curl/); expect(output.textContent).toBe("curl https://example.com"); }); + + it("generates command for a root-only tool (no commands)", () => { + const tool = { + binaryName: "httpx", + displayName: "Httpx", + commands: [], + parameters: [ + { + key: "list", + name: "List", + parameterType: "Option" as const, + dataType: "String" as const, + shortFlag: "-l", + longFlag: "-list", + sortOrder: 1, + }, + { + key: "target", + name: "Target", + parameterType: "Option" as const, + dataType: "String" as const, + shortFlag: "-u", + longFlag: "-target", + sortOrder: 2, + }, + ], + }; + render( + , + ); + const output = screen.getByText(/httpx/); + expect(output.textContent).toBe("httpx -l urls.txt -u example.com"); + }); + + it("generates command with only tool name when root-only tool has no values set", () => { + const tool = { + binaryName: "httpx", + displayName: "Httpx", + commands: [], + parameters: [ + { + key: "list", + name: "List", + parameterType: "Option" as const, + dataType: "String" as const, + longFlag: "-list", + sortOrder: 1, + }, + ], + }; + render( + , + ); + const output = screen.getByText(/httpx/); + expect(output.textContent).toBe("httpx"); + }); + + it("includes root parameters in generated command when tool has commands but selectedCommand is null", () => { + const tool = { + binaryName: "mycli", + displayName: "My CLI", + commands: [{ key: "sub", name: "sub", sortOrder: 0 }], + parameters: [ + { + key: "verbose", + name: "Verbose", + parameterType: "Flag" as const, + dataType: "Boolean" as const, + longFlag: "--verbose", + sortOrder: 1, + }, + { + key: "output", + name: "Output", + parameterType: "Option" as const, + dataType: "String" as const, + longFlag: "--output", + commandKey: "sub", + sortOrder: 2, + }, + ], + }; + render( + , + ); + const output = screen.getByText(/mycli/); + expect(output.textContent).toBe("mycli --verbose"); + }); + + it("includes parent command path for nested subcommands", () => { + const tool = { + binaryName: "mycli", + displayName: "My CLI", + commands: [ + { key: "config", name: "config", sortOrder: 0 }, + { key: "get", name: "get", parentCommandKey: "config", sortOrder: 0 }, + ], + parameters: [ + { + key: "key-param", + name: "Key", + parameterType: "Argument" as const, + dataType: "String" as const, + commandKey: "get", + position: 1, + sortOrder: 1, + }, + ], + }; + render( + , + ); + const output = screen.getByText(/mycli/); + expect(output.textContent).toBe("mycli config get app.name"); + }); }); diff --git a/registry/commandly/__tests__/json-output.test.tsx b/registry/commandly/__tests__/json-output.test.tsx index 53404bf..4172f0e 100644 --- a/registry/commandly/__tests__/json-output.test.tsx +++ b/registry/commandly/__tests__/json-output.test.tsx @@ -81,7 +81,7 @@ describe("exportToStructuredJSON", () => { describe("convertToNestedStructure", () => { it("omits validations and dependencies when empty", () => { const result = toJSON(convertToNestedStructure(defaultTool())); - result.globalParameters.forEach((param) => { + result.rootParameters.forEach((param) => { expect(param).not.toHaveProperty("validations"); expect(param).not.toHaveProperty("dependencies"); }); @@ -103,7 +103,7 @@ describe("convertToNestedStructure", () => { }, ]; const result = convertToNestedStructure(tool); - expect(result.globalParameters[0].validations).toHaveLength(1); + expect(result.rootParameters[0].validations).toHaveLength(1); }); it("includes exclusionGroups when non-empty", () => { diff --git a/registry/commandly/__tests__/tool-renderer.test.tsx b/registry/commandly/__tests__/tool-renderer.test.tsx index 565afa2..0d70af5 100644 --- a/registry/commandly/__tests__/tool-renderer.test.tsx +++ b/registry/commandly/__tests__/tool-renderer.test.tsx @@ -3,7 +3,6 @@ import { ParameterRendererEntry } from "@/components/commandly/types/renderer"; import { createNewParameter } from "@/components/commandly/utils/flat"; import { defaultTool } from "@/lib/utils"; import { render, screen } from "@testing-library/react"; - const baseCommand = { key: "my-tool", name: "my-tool", sortOrder: 0 }; const baseTool = { ...defaultTool(), commands: [baseCommand] }; @@ -218,4 +217,157 @@ describe("ToolRenderer", () => { expect(screen.getByTestId("custom-flag")).toBeInTheDocument(); expect(screen.queryByRole("switch")).not.toBeInTheDocument(); }); + + it("renders root parameters for a tool with no commands", () => { + const rootTool = { + binaryName: "httpx", + displayName: "Httpx", + commands: [], + parameters: [ + { + key: "list", + name: "List", + parameterType: "Option" as const, + dataType: "String" as const, + longFlag: "-list", + }, + { + key: "verbose", + name: "Verbose", + parameterType: "Flag" as const, + dataType: "Boolean" as const, + longFlag: "--verbose", + }, + ], + }; + render( + {}} + />, + ); + expect(screen.getByText("List")).toBeInTheDocument(); + expect(screen.getByText("Verbose")).toBeInTheDocument(); + }); + + it("shows no parameters message for root-only tool with empty parameters", () => { + const emptyRootTool = { + binaryName: "httpx", + displayName: "Httpx", + commands: [], + parameters: [], + }; + render( + {}} + />, + ); + expect(screen.getByText(/No parameters available/)).toBeInTheDocument(); + }); + + it("renders root parameters when tool has commands but selectedCommand is null", () => { + const tool = { + binaryName: "mycli", + displayName: "My CLI", + commands: [{ key: "sub", name: "sub", sortOrder: 0 }], + parameters: [ + { + key: "verbose", + name: "Verbose", + parameterType: "Flag" as const, + dataType: "Boolean" as const, + longFlag: "--verbose", + }, + { + key: "output", + name: "Output", + parameterType: "Option" as const, + dataType: "String" as const, + longFlag: "--output", + commandKey: "sub", + }, + ], + }; + render( + {}} + />, + ); + expect(screen.getByText("Verbose")).toBeInTheDocument(); + expect(screen.queryByText("Output")).not.toBeInTheDocument(); + }); + + it("renders global parameters when root is selected (selectedCommand is null)", () => { + const tool = { + binaryName: "mycli", + displayName: "My CLI", + commands: [{ key: "sub", name: "sub", sortOrder: 0 }], + parameters: [ + { + key: "global-flag", + name: "GlobalFlag", + parameterType: "Flag" as const, + dataType: "Boolean" as const, + longFlag: "--global", + isGlobal: true, + }, + { + key: "output", + name: "Output", + parameterType: "Option" as const, + dataType: "String" as const, + longFlag: "--output", + commandKey: "sub", + }, + ], + }; + render( + {}} + />, + ); + expect(screen.getByText("GlobalFlag")).toBeInTheDocument(); + expect(screen.queryByText("Output")).not.toBeInTheDocument(); + }); + + it("does not render info icon when description is empty or absent", () => { + const paramNoDesc = { + ...createNewParameter(false, "my-tool"), + key: "flag-no-desc", + name: "NoDesc", + parameterType: "Flag" as const, + dataType: "Boolean" as const, + description: undefined, + }; + const paramEmptyDesc = { + ...createNewParameter(false, "my-tool"), + key: "flag-empty-desc", + name: "EmptyDesc", + parameterType: "Flag" as const, + dataType: "Boolean" as const, + description: "", + }; + render( + {}} + />, + ); + expect(screen.queryAllByRole("button")).toHaveLength(0); + }); }); diff --git a/registry/commandly/generated-command.tsx b/registry/commandly/generated-command.tsx index e4617d3..d2850db 100644 --- a/registry/commandly/generated-command.tsx +++ b/registry/commandly/generated-command.tsx @@ -7,32 +7,44 @@ import { toast } from "sonner"; interface GeneratedCommandProps { tool: Tool; - selectedCommand?: Command; + selectedCommand?: Command | null; parameterValues: Record; onSaveCommand?: (command: string) => void; } export function GeneratedCommand({ tool, - selectedCommand, + selectedCommand: providedCommand, parameterValues, onSaveCommand, }: GeneratedCommandProps) { - selectedCommand = selectedCommand || tool.commands[0]; + const selectedCommand = providedCommand === undefined ? tool.commands[0] : providedCommand; + const hasCommands = tool.commands.length > 0; const [generatedCommand, setGeneratedCommand] = useState(""); const globalParameters = useMemo(() => { return tool.parameters?.filter((p) => p.isGlobal) || []; }, [tool]); + const rootParameters = useMemo(() => { + if (hasCommands && selectedCommand) return []; + return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || []; + }, [tool, hasCommands, selectedCommand]); + const currentParameters = useMemo(() => { + if (!selectedCommand) return []; return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || []; }, [tool, selectedCommand]); const generateCommand = useCallback(() => { - if (!selectedCommand) return; - const commandPath = getCommandPath(selectedCommand, tool); - let command = tool.name == commandPath ? tool.name : `${tool.name} ${commandPath}`; + let command = tool.binaryName; + + if (hasCommands && selectedCommand) { + const commandPath = getCommandPath(selectedCommand, tool); + if (tool.binaryName !== commandPath) { + command = `${tool.binaryName} ${commandPath}`; + } + } const parametersWithValues: Array<{ param: Parameter; @@ -46,6 +58,13 @@ export function GeneratedCommand({ } }); + rootParameters.forEach((param) => { + const value = parameterValues[param.key]; + if (value !== undefined && value !== "" && value !== false) { + parametersWithValues.push({ param, value }); + } + }); + currentParameters.forEach((param) => { const value = parameterValues[param.key]; if (value !== undefined && value !== "" && value !== false && !param.isGlobal) { @@ -95,7 +114,15 @@ export function GeneratedCommand({ }); setGeneratedCommand(command); - }, [tool, parameterValues, selectedCommand, globalParameters, currentParameters]); + }, [ + tool, + parameterValues, + selectedCommand, + hasCommands, + globalParameters, + rootParameters, + currentParameters, + ]); useEffect(() => { generateCommand(); @@ -108,12 +135,7 @@ export function GeneratedCommand({ return (
- {tool.commands.length === 0 ? ( -
- -

No commands available for this tool.

-
- ) : generatedCommand ? ( + {generatedCommand ? (
{generatedCommand}
diff --git a/registry/commandly/tool-renderer.tsx b/registry/commandly/tool-renderer.tsx index 364f9a9..abb4d61 100644 --- a/registry/commandly/tool-renderer.tsx +++ b/registry/commandly/tool-renderer.tsx @@ -22,11 +22,11 @@ import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { CheckIcon, ChevronsUpDownIcon, InfoIcon, PlusIcon, XIcon } from "lucide-react"; -import React from "react"; +import React, { useMemo } from "react"; const findDefaultCommand = (tool: Tool): Command | null => { const nameMatchCommand = tool.commands.find( - (command) => command.name.toLowerCase() === tool.name.toLowerCase(), + (command) => command.name.toLowerCase() === tool.binaryName.toLowerCase(), ); if (nameMatchCommand) return nameMatchCommand; @@ -63,14 +63,16 @@ function ParameterLabel({ )} {isRequired && *} - - - - - - {description} - - + {description?.trim() && ( + + + + + + {description} + + + )} {children} {isGlobal && ( 0; + + const visibleParameters = useMemo(() => { + if (!hasCommands || !selectedCommand) { + return tool.parameters.filter((p) => !p.commandKey || p.isGlobal); + } + return tool.parameters.filter( + (param) => param.commandKey === selectedCommand?.key || param.isGlobal, + ); + }, [tool, hasCommands, selectedCommand]); return ( - {selectedCommand && tool.commands.length === 0 ? ( -

No commands available for this tool.

- ) : ( -
- {tool.parameters.length > 0 ? ( - tool.parameters - .filter((param) => param.commandKey === selectedCommand?.key || param.isGlobal) - .map((parameter) => { - const value = parameterValues[parameter.key] ?? ""; - const onUpdate = (val: ParameterValue) => updateParameterValue(parameter.key, val); - const entry = catalog.find((e) => e.condition(parameter)); - if (!entry) return null; - return ( - - {parameter.isRepeatable ? ( - - ) : ( - entry.component({ parameter, value, onUpdate }) - )} - - ); - }) - ) : ( -

- No parameters available for this command. -

- )} -
- )} +
+ {visibleParameters.length > 0 ? ( + visibleParameters.map((parameter) => { + const value = parameterValues[parameter.key] ?? ""; + const onUpdate = (val: ParameterValue) => updateParameterValue(parameter.key, val); + const entry = catalog.find((e) => e.condition(parameter)); + if (!entry) return null; + return ( + + {parameter.isRepeatable ? ( + + ) : ( + entry.component({ parameter, value, onUpdate }) + )} + + ); + }) + ) : ( +

No parameters available for this command.

+ )} +
); } diff --git a/registry/commandly/types/flat.ts b/registry/commandly/types/flat.ts index 5728c12..810c39b 100644 --- a/registry/commandly/types/flat.ts +++ b/registry/commandly/types/flat.ts @@ -151,8 +151,8 @@ export interface ExclusionGroup { } export interface Tool { - /** Unique machine-readable identifier for the tool (e.g. "httpx"). */ - name: string; + /** Unique binary name for the tool that it can be invoked from the command line (e.g. "httpx"). */ + binaryName: string; /** Human-readable display name for the tool (e.g. "HTTPx"). */ displayName: string; /** General information about the tool such as description, version, and URL. */ diff --git a/registry/commandly/types/nested.ts b/registry/commandly/types/nested.ts index 8ece0cd..3997a87 100644 --- a/registry/commandly/types/nested.ts +++ b/registry/commandly/types/nested.ts @@ -79,8 +79,9 @@ export interface NestedCommand { /** Parameters that belong directly to this command. */ parameters: NestedParameter[]; /** Nested subcommands of this command. */ - subcommands: NestedCommand[]; /** Groups of parameters with mutual exclusion or required-one-of constraints scoped to this command. */ - exclusionGroups?: NestedExclusionGroup[];} + subcommands: NestedCommand[] /** Groups of parameters with mutual exclusion or required-one-of constraints scoped to this command. */; + exclusionGroups?: NestedExclusionGroup[]; +} export interface NestedExclusionGroup { /** Human-readable name for this exclusion group. */ @@ -92,16 +93,17 @@ export interface NestedExclusionGroup { } export interface NestedTool { - /** Optional JSON schema URI for validation. */ $schema?: string; - /** Unique machine-readable identifier for the tool (e.g. "httpx"). */ - name: string; + /** Unique binary name for the tool that it can be invoked from the command line (e.g. "httpx"). */ + binaryName: string; /** Human-readable display name for the tool (e.g. "HTTPx"). */ displayName: string; /** General information about the tool such as description, version, and URL. */ info?: ToolInfo; /** The homepage or documentation URL for the tool. */ url?: string; + /** Parameters that belong to the root invocation when no commands exist. */ + rootParameters: NestedParameter[]; /** Parameters that apply to all commands globally. */ globalParameters: NestedParameter[]; /** Hierarchical list of commands and their nested subcommands. */ diff --git a/registry/commandly/utils/flat.ts b/registry/commandly/utils/flat.ts index 1ee93bd..5eb8dfd 100644 --- a/registry/commandly/utils/flat.ts +++ b/registry/commandly/utils/flat.ts @@ -13,6 +13,7 @@ export const slugify = (text: string): string => { }; export const getCommandPath = (command: Command, tool: Tool): string => { + const allCommands = tool.commands; const findCommandPath = ( targetKey: string, commands: Command[], @@ -23,7 +24,7 @@ export const getCommandPath = (command: Command, tool: Tool): string => { return [...path, cmd.name]; } - const childCommands = commands.filter((c) => c.parentCommandKey === cmd.key); + const childCommands = allCommands.filter((c) => c.parentCommandKey === cmd.key); if (childCommands.length > 0) { const subPath = findCommandPath(targetKey, childCommands, [...path, cmd.name]); if (subPath) { @@ -73,7 +74,7 @@ export const sanitizeToolJSON = (tool: Tool) => { export const exportToStructuredJSON = (tool: Tool) => { return { $schema: SCHEMA_URL, - name: tool.name, + name: tool.binaryName, displayName: tool.displayName, info: tool.info, commands: tool.commands.map((cmd) => ({ ...cmd })), diff --git a/registry/commandly/utils/nested.ts b/registry/commandly/utils/nested.ts index a27e110..9b86a3d 100644 --- a/registry/commandly/utils/nested.ts +++ b/registry/commandly/utils/nested.ts @@ -73,13 +73,17 @@ export const convertToNestedStructure = (tool: Tool): NestedTool => { }), })); + const rootParameters = + tool.commands.length === 0 ? tool.parameters.filter((p) => !p.commandKey && !p.isGlobal) : []; + return { $schema: "https://commandly.divyeshio.in/specification/nested.json", - name: tool.name, + binaryName: tool.binaryName, url: tool.info?.url, displayName: tool.displayName, info: tool.info, metadata: tool.metadata, + rootParameters: rootParameters.map(convertParameter), globalParameters: globalParameters.map(convertParameter), commands: buildNestedCommands(tool.commands), exclusionGroups: nestedExclusionGroups, diff --git a/scripts/generate-tools-json.ts b/scripts/generate-tools-json.ts index 71d0ec9..a4c3fea 100644 --- a/scripts/generate-tools-json.ts +++ b/scripts/generate-tools-json.ts @@ -10,8 +10,8 @@ const tools = files.sort().map((file) => { const content = readFileSync(join(collectionDir, file), "utf-8"); const tool = JSON.parse(content) as Tool; return { - name: tool.name, - displayName: tool.displayName || tool.name, + binaryName: tool.binaryName, + displayName: tool.displayName || tool.binaryName, description: tool.info?.description, info: tool.info, }; diff --git a/scripts/validate-tool-collection.ts b/scripts/validate-tool-collection.ts index 9eb4138..fb94afe 100644 --- a/scripts/validate-tool-collection.ts +++ b/scripts/validate-tool-collection.ts @@ -47,18 +47,32 @@ for (const file of files) { continue; } - if (tool.name !== fileName) { + if (tool.binaryName !== fileName) { errors.push( - `❌ \`${file}\`: \`name\` field (\`${tool.name}\`) does not match filename (\`${fileName}\`).`, + `❌ \`${file}\`: \`name\` field (\`${tool.binaryName}\`) does not match filename (\`${fileName}\`).`, ); continue; } - if (!Array.isArray(tool.commands) || tool.commands.length === 0) { - errors.push(`❌ \`${file}\`: \`commands\` must be a non-empty array.`); + if (!Array.isArray(tool.commands)) { + errors.push(`❌ \`${file}\`: \`commands\` must be an array.`); continue; } + const hasCommands = tool.commands.length > 0; + for (const param of tool.parameters) { + if (!hasCommands && (param.commandKey || param.isGlobal)) { + errors.push( + `❌ \`${file}\`: Parameter \`${param.key}\` must not have \`commandKey\` or \`isGlobal\` when there are no commands.`, + ); + } + if (hasCommands && !param.commandKey && !param.isGlobal) { + errors.push( + `❌ \`${file}\`: Parameter \`${param.key}\` must have \`commandKey\` or \`isGlobal\` when commands exist.`, + ); + } + } + const sanitized = sanitizeToolJSON(tool); const output = JSON.stringify(sanitized, null, 2); diff --git a/src/components/docs/demos/generated-command-demo.tsx b/src/components/docs/demos/generated-command-demo.tsx index 2653895..0315351 100644 --- a/src/components/docs/demos/generated-command-demo.tsx +++ b/src/components/docs/demos/generated-command-demo.tsx @@ -3,7 +3,7 @@ import type { Tool } from "@/components/commandly/types/flat"; import { useState } from "react"; const sampleTool: Tool = { - name: "curl", + binaryName: "curl", displayName: "curl", info: { description: "Transfer data to or from a server", diff --git a/src/components/docs/demos/json-output-demo.tsx b/src/components/docs/demos/json-output-demo.tsx index 2a9a003..86d56ae 100644 --- a/src/components/docs/demos/json-output-demo.tsx +++ b/src/components/docs/demos/json-output-demo.tsx @@ -2,7 +2,7 @@ import { JsonOutput } from "@/components/commandly/json-output"; import type { Tool } from "@/components/commandly/types/flat"; const sampleTool: Tool = { - name: "curl", + binaryName: "curl", displayName: "curl", info: { description: "Transfer data to or from a server", diff --git a/src/components/docs/demos/tool-renderer-demo.tsx b/src/components/docs/demos/tool-renderer-demo.tsx index bb9c74f..c1bfc16 100644 --- a/src/components/docs/demos/tool-renderer-demo.tsx +++ b/src/components/docs/demos/tool-renderer-demo.tsx @@ -3,7 +3,7 @@ import type { ParameterValue, Tool } from "@/components/commandly/types/flat"; import { useState } from "react"; const sampleTool: Tool = { - name: "curl", + binaryName: "curl", displayName: "curl", info: { description: "Transfer data with URLs", diff --git a/src/components/tool-card.tsx b/src/components/tool-card.tsx index 4fc4567..11553b2 100644 --- a/src/components/tool-card.tsx +++ b/src/components/tool-card.tsx @@ -25,7 +25,7 @@ export function ToolCard({ @@ -33,7 +33,7 @@ export function ToolCard({ {tool.displayName} @@ -69,7 +69,7 @@ export function ToolCard({ > @@ -94,8 +94,8 @@ export function ToolCard({ diff --git a/src/components/tool-editor/ai-chat-store.ts b/src/components/tool-editor/ai-chat-store.ts index e6d5a9f..0848832 100644 --- a/src/components/tool-editor/ai-chat-store.ts +++ b/src/components/tool-editor/ai-chat-store.ts @@ -97,8 +97,8 @@ export class ChatStore { updateTool(tool: Tool): void { this.currentTool = tool; - if (tool.name !== this.toolName) { - this.toolName = tool.name; + if (tool.binaryName !== this.toolName) { + this.toolName = tool.binaryName; this.refreshChats(); } } diff --git a/src/components/tool-editor/ai-chat.tsx b/src/components/tool-editor/ai-chat.tsx index ae3cbb5..7e053c5 100644 --- a/src/components/tool-editor/ai-chat.tsx +++ b/src/components/tool-editor/ai-chat.tsx @@ -207,7 +207,7 @@ function useAIChat( onGeneratingChange?: (isGenerating: boolean) => void, ) { const { contextSelection } = useToolBuilder(); - const [store] = useState(() => new ChatStore(currentTool.name, currentTool)); + const [store] = useState(() => new ChatStore(currentTool.binaryName, currentTool)); const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot); store.updateTool(currentTool); diff --git a/src/components/tool-editor/command-tree.tsx b/src/components/tool-editor/command-tree.tsx index 4dfcc1c..b273c0f 100644 --- a/src/components/tool-editor/command-tree.tsx +++ b/src/components/tool-editor/command-tree.tsx @@ -1,143 +1,60 @@ import { CommandDialog } from "../tool-editor/dialogs/command-dialog"; import { useToolBuilder } from "./tool-editor.context"; import { Command } from "@/components/commandly/types/flat"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tree, Folder, File } from "@/components/ui/file-tree"; +import { + Sortable, + SortableContent, + SortableItem, + SortableItemHandle, + SortableOverlay, +} from "@/components/ui/sortable"; import { cn } from "@/lib/utils"; -import { ChevronDownIcon, ChevronRightIcon, Edit2Icon, PlusIcon, Trash2Icon } from "lucide-react"; +import { ChevronRightIcon, Edit2Icon, GripVerticalIcon, PlusIcon, TerminalIcon, Trash2Icon } from "lucide-react"; import { useState } from "react"; -interface CommandNodeProps { - command: Command; - level?: number; - allCommands: Command[]; - toolName: string; - selectedCommandKey?: string; - contextCommandKeys: string[]; - isChatOpen: boolean; - expandedCommands: Set; - onToggle: (commandKey: string) => void; - onSelect: (command: Command, e: React.MouseEvent) => void; - onEdit: (command: Command) => void; - onAddSubcommand: (parentKey?: string) => void; - onDelete: (commandKey: string) => void; -} +const ROOT_ID = "__root__"; -function CommandNode({ - command, - level = 0, - allCommands, - toolName, - selectedCommandKey, - contextCommandKeys, - isChatOpen, - expandedCommands, - onToggle, - onSelect, +function CommandActions({ onEdit, - onAddSubcommand, + onAdd, onDelete, -}: CommandNodeProps) { - const isExpanded = expandedCommands.has(command.key); - const subcommands = allCommands.filter((cmd) => cmd.parentCommandKey === command.key); - const hasSubcommands = subcommands.length > 0; - const isSelected = selectedCommandKey === command.key; - const isContextSelected = contextCommandKeys.includes(command.key); - const isRoot = command.name === toolName; - + showHandle = false, +}: { + onEdit?: () => void; + onAdd: () => void; + onDelete?: () => void; + showHandle?: boolean; +}) { return ( -
-
onSelect(command, e)} - > - {hasSubcommands ? ( + <> + {showHandle && ( + - ) : ( -
- )} - - {command.name} - - + {onDelete && ( + - {!isRoot && ( - - )} -
- - {isExpanded && hasSubcommands && ( -
- {subcommands.map((subcmd) => ( - - ))} -
)} -
+ ); } @@ -151,29 +68,15 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { setContextSelection, updateCommand, deleteCommand, + reorderCommands, } = useToolBuilder(); - const [expandedCommands, setExpandedCommands] = useState>( - new Set([tool.commands[0]?.key]), - ); const [isDialogOpen, setIsDialogOpen] = useState(false); const [dialogCommand, setDialogCommand] = useState(undefined); const [pendingParentKey, setPendingParentKey] = useState(undefined); const lastSelectedCommandIndexRef = { current: null as number | null }; - const toggleExpanded = (commandKey: string) => { - setExpandedCommands((prev) => { - const newSet = new Set(prev); - if (newSet.has(commandKey)) { - newSet.delete(commandKey); - } else { - newSet.add(commandKey); - } - return newSet; - }); - }; - const handleAddSubcommand = (parentKey?: string) => { setDialogCommand(undefined); setPendingParentKey(parentKey); @@ -186,7 +89,13 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { setIsDialogOpen(true); }; - const handleSelect = (command: Command, e: React.MouseEvent) => { + const handleRootClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setSelectedCommand(null); + setContextSelection({ commandKeys: [], parameterKeys: [] }); + }; + + const handleCommandClick = (command: Command, e: React.MouseEvent) => { e.stopPropagation(); const flatCommands = tool.commands; const index = flatCommands.findIndex((c) => c.key === command.key); @@ -217,52 +126,162 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { if (!dialogCommand) { addCommand(savedCommand); setSelectedCommand(savedCommand); - if (savedCommand.parentCommandKey) { - setExpandedCommands((prev) => { - const newSet = new Set(prev); - newSet.add(savedCommand.parentCommandKey!); - return newSet; - }); - } } else { updateCommand(savedCommand.key, savedCommand); } }; + const renderCommand = (command: Command) => { + const subcommands = tool.commands.filter((c) => c.parentCommandKey === command.key); + const isSelected = selectedCommand?.key === command.key; + const isContextSelected = contextSelection.commandKeys.includes(command.key); + const paramCount = tool.parameters.filter((p) => p.commandKey === command.key).length; + + const nameElement = ( + + {command.name} + {paramCount > 0 && ( + + {paramCount} + + )} + + ); + + const actions = ( + handleEdit(command)} + onAdd={() => handleAddSubcommand(command.key)} + onDelete={() => deleteCommand(command.key)} + showHandle + /> + ); + + if (subcommands.length > 0) { + return ( + handleCommandClick(command, e)} + > + cmd.key} + onValueChange={(newOrder) => + reorderCommands(newOrder.map((c) => c.key), command.key) + } + > + + {subcommands.map((subcmd) => ( + + {renderCommand(subcmd)} + + ))} + + + {({ value }) => { + const cmd = tool.commands.find((c) => c.key === value); + return ( +
+ {cmd?.name} +
+ ); + }} +
+
+
+ ); + } + + return ( + } + actions={actions} + onClick={(e) => handleCommandClick(command, e)} + > + {nameElement} + + ); + }; + const rootCommands = tool.commands.filter((cmd) => !cmd.parentCommandKey); + const isRootSelected = selectedCommand === null; + const rootParamCount = tool.parameters.filter((p) => !p.commandKey && !p.isGlobal).length; + const globalParamCount = tool.parameters.filter((p) => p.isGlobal).length; + + const rootElement = ( + + {tool.binaryName} + {rootParamCount > 0 && ( + + {rootParamCount} + + )} + {globalParamCount > 0 && ( + + {globalParamCount} + + )} + + ); return ( <> - -
- {rootCommands.map((command) => ( - - ))} -
-
- -
-
+ + {rootCommands.map((command) => ( + + {renderCommand(command)} + + ))} + + + {({ value }) => { + const cmd = tool.commands.find((c) => c.key === value); + return ( +
+ {cmd?.name} +
+ ); + }} +
+ + + c.parentCommandKey === pendingParentKey) + .map((c) => c.key)} + toolName={tool.binaryName} onSave={handleDialogSave} /> diff --git a/src/components/tool-editor/dialogs/command-dialog.tsx b/src/components/tool-editor/dialogs/command-dialog.tsx index c9839dc..1cedb39 100644 --- a/src/components/tool-editor/dialogs/command-dialog.tsx +++ b/src/components/tool-editor/dialogs/command-dialog.tsx @@ -21,6 +21,7 @@ interface CommandDialogProps { onOpenChange: (open: boolean) => void; command?: Command; parentKey?: string; + siblingKeys?: string[]; toolName: string; onSave: (command: Command) => void; } @@ -30,24 +31,34 @@ export function CommandDialog({ onOpenChange, command, parentKey, + siblingKeys = [], onSave, }: CommandDialogProps) { const isNewCommand = !command; - const [editCommand, setCommand] = useState( - () => - command ?? { - key: "", - name: "", - description: "", - sortOrder: 0, - parentCommandKey: parentKey, - }, - ); + const getDefaultCommand = () => + command ?? { + key: "", + name: "", + description: "", + sortOrder: 0, + parentCommandKey: parentKey, + }; + + const [editCommand, setCommand] = useState(getDefaultCommand); + + const sluggedName = slugify(editCommand.name); + const isDuplicate = + isNewCommand && + !!sluggedName && + (sluggedName === parentKey || siblingKeys.includes(sluggedName)); return ( { + if (!open) setCommand(getDefaultCommand()); + onOpenChange(open); + }} > @@ -71,37 +82,26 @@ export function CommandDialog({ })) } /> + {isDuplicate && ( +

+ A command with this name already exists at this level. +

+ )}
-
-
- - +
+
+ { setCommand((prev) => ({ ...prev, - sortOrder: Number.parseInt(e.target.value) || 0, - })) - } + interactive: checked, + })); + }} /> -
-
-
- { - setCommand((prev) => ({ - ...prev, - interactive: checked, - })); - }} - /> - -
+
@@ -117,12 +117,13 @@ export function CommandDialog({
-
- {parameters.map((parameter, index) => { - const paramGroups = getParameterExclusionGroups(parameter.key); - const isContextSelected = contextSelection.parameterKeys.includes(parameter.key); - const isAdded = pendingChanges?.added.has(parameter.key); - const isUpdated = pendingChanges?.updated.has(parameter.key); + p.key} + onValueChange={(newOrder) => reorderParameters(newOrder.map((p) => p.key))} + > + + {parameters.map((parameter, index) => { + const paramGroups = getParameterExclusionGroups(parameter.key); + const isContextSelected = contextSelection.parameterKeys.includes(parameter.key); + const isAdded = pendingChanges?.added.has(parameter.key); + const isUpdated = pendingChanges?.updated.has(parameter.key); - return ( -
handleParameterClick(e, parameter.key, index)} - > -
-
- - - {parameter.name} - {(parameter.longFlag || parameter.shortFlag) && ( - - ({[parameter.longFlag, parameter.shortFlag].filter(Boolean).join(", ")}) + return ( + +
handleParameterClick(e, parameter.key, index)} + > +
+
+ + + {parameter.name} + {(parameter.longFlag || parameter.shortFlag) && ( + + ({[parameter.longFlag, parameter.shortFlag].filter(Boolean).join(", ")}) + + )} - )} - -
-
- - -
-
+
+
+ + + + + +
+
{parameter.isRequired && (
+ ); })} - {removedParameters.map((key) => ( -
-
- {key} - - Removed - -
+ + + {({ value }) => { + const param = parameters.find((p) => p.key === value); + return ( +
+ {param?.name} +
+ ); + }} +
+ + {removedParameters.map((key) => ( +
+
+ {key} + + Removed +
- ))} -
+
+ ))}
); } diff --git a/src/components/tool-editor/preview-tabs.tsx b/src/components/tool-editor/preview-tabs.tsx index a969bee..257297d 100644 --- a/src/components/tool-editor/preview-tabs.tsx +++ b/src/components/tool-editor/preview-tabs.tsx @@ -111,6 +111,7 @@ export function PreviewTabs({ onSaveCommand, streamingTool, isAIGenerating }: Pr diff --git a/src/components/tool-editor/tool-editor.context.tsx b/src/components/tool-editor/tool-editor.context.tsx index 6bfc99a..84ab13d 100644 --- a/src/components/tool-editor/tool-editor.context.tsx +++ b/src/components/tool-editor/tool-editor.context.tsx @@ -25,7 +25,7 @@ export interface ContextSelection { export interface ToolBuilderState { tool: Tool; originalTool: Tool; - selectedCommand: Command; + selectedCommand: Command | null; selectedParameter: Parameter | null; contextSelection: ContextSelection; parameterValues: Record; @@ -47,7 +47,7 @@ type Action = | { type: "UPDATE_COMMAND"; payload: { commandKey: string; updates: Partial } } | { type: "REMOVE_PARAMETER"; payload: string } | { type: "SET_DIALOG_OPEN"; payload: { dialog: DialogKey; open: boolean } } - | { type: "SET_SELECTED_COMMAND"; payload: Command } + | { type: "SET_SELECTED_COMMAND"; payload: Command | null } | { type: "SET_SELECTED_PARAMETER"; payload: Parameter | null } | { type: "SET_CONTEXT_SELECTION"; payload: ContextSelection } | { type: "CLEAR_CONTEXT_SELECTION" } @@ -55,14 +55,16 @@ type Action = | { type: "ADD_EXCLUSION_GROUP"; payload: ExclusionGroup } | { type: "UPDATE_EXCLUSION_GROUP"; payload: ExclusionGroup } | { type: "REMOVE_EXCLUSION_GROUP"; payload: string } - | { type: "SET_PARAMETER_VALUE"; payload: { key: string; value: ParameterValue } }; + | { type: "SET_PARAMETER_VALUE"; payload: { key: string; value: ParameterValue } } + | { type: "REORDER_COMMANDS"; payload: { commandKeys: string[]; parentCommandKey?: string } } + | { type: "REORDER_PARAMETERS"; payload: { parameterKeys: string[] } }; function getDefaultState(tool: Tool): ToolBuilderState { const cleanTool = cleanupTool(tool); return { tool: cleanTool, originalTool: cleanTool, - selectedCommand: tool.commands[0] ?? ({} as Command), + selectedCommand: null, selectedParameter: null, contextSelection: { commandKeys: [], parameterKeys: [] }, parameterValues: {}, @@ -83,24 +85,36 @@ function toolBuilderReducer(state: ToolBuilderState, action: Action): ToolBuilde case "UPDATE_TOOL": return { ...state, tool: cleanupTool({ ...state.tool, ...action.payload }) }; - case "ADD_SUBCOMMAND": + case "ADD_SUBCOMMAND": { return { ...state, - tool: { ...state.tool, commands: [...state.tool.commands, action.payload] }, + tool: { + ...state.tool, + commands: [...state.tool.commands, action.payload], + }, }; + } case "DELETE_COMMAND": { const subcommands = getAllSubcommands(action.payload, state.tool.commands); const commandsToDelete = [action.payload, ...subcommands.map((c) => c.key)]; const newCommands = state.tool.commands.filter((cmd) => !commandsToDelete.includes(cmd.key)); + const survivingParams = state.tool.parameters.filter( + (param) => !commandsToDelete.includes(param.commandKey || ""), + ); + const newParams = + newCommands.length === 0 + ? survivingParams.map((p) => { + const { commandKey: _, isGlobal: __, ...rest } = p; + return rest; + }) + : survivingParams; return { ...state, tool: { ...state.tool, commands: newCommands, - parameters: state.tool.parameters.filter( - (param) => !commandsToDelete.includes(param.commandKey || ""), - ), + parameters: newParams, exclusionGroups: state.tool.exclusionGroups?.filter( (group) => !commandsToDelete.includes(group.commandKey || ""), ), @@ -113,7 +127,7 @@ function toolBuilderReducer(state: ToolBuilderState, action: Action): ToolBuilde }, selectedCommand: state.selectedCommand?.key === action.payload - ? (newCommands[0] ?? ({} as Command)) + ? (newCommands[0] ?? null) : state.selectedCommand, }; } @@ -179,7 +193,7 @@ function toolBuilderReducer(state: ToolBuilderState, action: Action): ToolBuilde if (parameterData.isGlobal && parameterData.isGlobal !== param.isGlobal) { updatedParam.commandKey = undefined; } - if (!parameterData.isGlobal && param.isGlobal) { + if (!parameterData.isGlobal && param.isGlobal && state.tool.commands.length > 0) { updatedParam.commandKey = state.selectedCommand?.key; } return updatedParam; @@ -225,6 +239,36 @@ function toolBuilderReducer(state: ToolBuilderState, action: Action): ToolBuilde parameterValues: { ...state.parameterValues, [action.payload.key]: action.payload.value }, }; + case "REORDER_COMMANDS": { + const { commandKeys, parentCommandKey: _parentCommandKey } = action.payload; + const commandMap = new Map(state.tool.commands.map((cmd) => [cmd.key, cmd])); + const siblingIndices = state.tool.commands + .map((cmd, i) => ({ cmd, i })) + .filter(({ cmd }) => commandKeys.includes(cmd.key)) + .map(({ i }) => i); + const newCommands = [...state.tool.commands]; + commandKeys.forEach((key, newIndex) => { + const cmd = commandMap.get(key)!; + newCommands[siblingIndices[newIndex]] = { ...cmd, sortOrder: newIndex }; + }); + return { ...state, tool: { ...state.tool, commands: newCommands } }; + } + + case "REORDER_PARAMETERS": { + const { parameterKeys } = action.payload; + const paramMap = new Map(state.tool.parameters.map((p) => [p.key, p])); + const siblingIndices = state.tool.parameters + .map((p, i) => ({ p, i })) + .filter(({ p }) => parameterKeys.includes(p.key)) + .map(({ i }) => i); + const newParameters = [...state.tool.parameters]; + parameterKeys.forEach((key, newIndex) => { + const param = paramMap.get(key)!; + newParameters[siblingIndices[newIndex]] = { ...param, sortOrder: newIndex }; + }); + return { ...state, tool: { ...state.tool, parameters: newParameters } }; + } + case "SET_CONTEXT_SELECTION": return { ...state, contextSelection: action.payload }; @@ -247,13 +291,16 @@ interface ToolBuilderContextValue extends ToolBuilderState { updateExclusionGroup: (updatedGroup: ExclusionGroup) => void; removeExclusionGroup: (groupKey: string) => void; setDialogOpen: (dialog: DialogKey, open: boolean) => void; - setSelectedCommand: (command: Command) => void; + setSelectedCommand: (command: Command | null) => void; setSelectedParameter: (parameter: Parameter | null) => void; setContextSelection: (selection: ContextSelection) => void; clearContextSelection: () => void; upsertParameter: (parameter: Parameter, originalKey?: string) => void; setParameterValue: (key: string, value: ParameterValue) => void; + reorderCommands: (commandKeys: string[], parentCommandKey?: string) => void; + reorderParameters: (parameterKeys: string[]) => void; getParametersForCommand: (commandKey: string) => Parameter[]; + getRootParameters: () => Parameter[]; getGlobalParameters: () => Parameter[]; getExclusionGroupsForCommand: (commandKey: string) => ExclusionGroup[]; } @@ -313,7 +360,7 @@ export function ToolBuilderProvider({ tool, children, initialState }: ToolBuilde const newGroup: ExclusionGroup = { ...group, key: slugify(group.name), - commandKey: state.selectedCommand?.key, + commandKey: state.tool.commands.length > 0 ? state.selectedCommand?.key : undefined, }; dispatch({ type: "ADD_EXCLUSION_GROUP", payload: newGroup }); toast("Group Added", { @@ -334,7 +381,7 @@ export function ToolBuilderProvider({ tool, children, initialState }: ToolBuilde setDialogOpen: (dialog: DialogKey, open: boolean) => dispatch({ type: "SET_DIALOG_OPEN", payload: { dialog, open } }), - setSelectedCommand: (command: Command) => + setSelectedCommand: (command: Command | null) => dispatch({ type: "SET_SELECTED_COMMAND", payload: command }), setSelectedParameter: (parameter: Parameter | null) => @@ -361,9 +408,18 @@ export function ToolBuilderProvider({ tool, children, initialState }: ToolBuilde setParameterValue: (key: string, value: ParameterValue) => dispatch({ type: "SET_PARAMETER_VALUE", payload: { key, value } }), + reorderCommands: (commandKeys: string[], parentCommandKey?: string) => + dispatch({ type: "REORDER_COMMANDS", payload: { commandKeys, parentCommandKey } }), + + reorderParameters: (parameterKeys: string[]) => + dispatch({ type: "REORDER_PARAMETERS", payload: { parameterKeys } }), + getParametersForCommand: (commandKey: string) => state.tool.parameters.filter((p) => !p.isGlobal && p.commandKey === commandKey), + getRootParameters: () => + state.tool.parameters.filter((p) => !p.commandKey && !p.isGlobal), + getGlobalParameters: () => state.tool.parameters.filter((p) => p.isGlobal), getExclusionGroupsForCommand: (commandKey: string) => diff --git a/src/components/tool-editor/tool-editor.tsx b/src/components/tool-editor/tool-editor.tsx index 6b2f0ff..fa47968 100644 --- a/src/components/tool-editor/tool-editor.tsx +++ b/src/components/tool-editor/tool-editor.tsx @@ -80,7 +80,7 @@ function ToolEditorContent({ const [initialToolJson, setInitialToolJson] = useState(() => JSON.stringify(tool)); const isDirty = JSON.stringify(tool) !== initialToolJson; - const isValid = tool.name.trim() !== "" && tool.displayName.trim() !== ""; + const isValid = (tool.binaryName ?? "").trim() !== "" && (tool.displayName ?? "").trim() !== ""; const pendingChanges = (() => { const currentParams = (streamingTool ?? tool).parameters; @@ -105,10 +105,10 @@ function ToolEditorContent({ const handleContribute = async () => { const json = JSON.stringify(tool, null, 2); - const filePath = `public/tools-collection/${tool.name}.json`; + const filePath = `public/tools-collection/${tool.binaryName}.json`; if (isNewTool) { - const message = encodeURIComponent(`feat(tools): add ${tool.name}`); + const message = encodeURIComponent(`feat(tools): add ${tool.binaryName}`); const filename = encodeURIComponent(filePath); if (json.length <= MAX_URL_JSON_LENGTH) { window.open( @@ -135,25 +135,23 @@ function ToolEditorContent({ }; return ( -
-
-
-

Commands

+
+
+
+

Commands

- -
- -
-
+
- {tool.displayName ? `${tool.displayName} (${tool.name})` : `${tool.name}`} + {tool.displayName + ? `${tool.displayName} (${tool.binaryName})` + : `${tool.binaryName}`}
+
+ +
+
+ +
diff --git a/src/components/tool-editor/tools.ts b/src/components/tool-editor/tools.ts index 023cd12..22d198d 100644 --- a/src/components/tool-editor/tools.ts +++ b/src/components/tool-editor/tools.ts @@ -5,15 +5,13 @@ import { JSONPath } from "jsonpath-plus"; import { z } from "zod"; export function applyMergePatch(base: Tool, patch: Partial): Tool { - const merged: Record = { ...base }; + const merged = { ...base, ...patch }; for (const [k, v] of Object.entries(patch)) { if (v === null) { - delete merged[k]; - } else { - merged[k] = v; + delete (merged as Record)[k]; } } - return cleanupTool(merged as unknown as Tool); + return cleanupTool(merged); } export function createEditTool(getBase: () => Tool, onPreview: (tool: Tool) => void) { diff --git a/src/components/ui/file-tree.tsx b/src/components/ui/file-tree.tsx new file mode 100644 index 0000000..22bcee8 --- /dev/null +++ b/src/components/ui/file-tree.tsx @@ -0,0 +1,538 @@ +import React, { + createContext, + forwardRef, + useCallback, + useContext, + useEffect, + useState, +} from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronRightIcon, FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { ScrollArea } from "@/components/ui/scroll-area" + +type TreeViewElement = { + id: string + name: string + type?: "file" | "folder" + isSelectable?: boolean + children?: TreeViewElement[] +} + +type TreeSortMode = + | "default" + | "none" + | ((a: TreeViewElement, b: TreeViewElement) => number) + +type TreeContextProps = { + selectedId: string | undefined + expandedItems: string[] | undefined + indicator: boolean + handleExpand: (id: string) => void + selectItem: (id: string) => void + setExpandedItems?: React.Dispatch> + openIcon?: React.ReactNode + closeIcon?: React.ReactNode + direction: "rtl" | "ltr" +} + +const TreeContext = createContext(null) + +const useTree = () => { + const context = useContext(TreeContext) + if (!context) { + throw new Error("useTree must be used within a TreeProvider") + } + return context +} + +type Direction = "rtl" | "ltr" | undefined + +const isFolderElement = (element: TreeViewElement) => { + if (element.type) { + return element.type === "folder" + } + + return Array.isArray(element.children) +} + +const mergeExpandedItems = ( + currentItems: string[] | undefined, + nextItems: string[] +) => [...new Set([...(currentItems ?? []), ...nextItems])] + +const treeCollator = new Intl.Collator("en", { + numeric: true, + sensitivity: "base", +}) + +const defaultTreeComparator = (a: TreeViewElement, b: TreeViewElement) => { + const aIsFolder = isFolderElement(a) + const bIsFolder = isFolderElement(b) + + if (aIsFolder !== bIsFolder) { + return aIsFolder ? -1 : 1 + } + + return treeCollator.compare(a.name, b.name) +} + +const getTreeComparator = (sort: TreeSortMode) => { + if (sort === "none") { + return undefined + } + + if (sort === "default") { + return defaultTreeComparator + } + + return sort +} + +const sortTreeElements = ( + elements: TreeViewElement[], + sort: TreeSortMode +): TreeViewElement[] => { + const comparator = getTreeComparator(sort) + + const nextElements = elements.map((element) => { + if (!Array.isArray(element.children)) { + return element + } + + return { + ...element, + children: sortTreeElements(element.children, sort), + } + }) + + if (!comparator) { + return nextElements + } + + return [...nextElements].sort(comparator) +} + +const renderTreeElements = ( + elements: TreeViewElement[], + sort: TreeSortMode +): React.ReactNode => + sortTreeElements(elements, sort).map((element) => { + if (isFolderElement(element)) { + return ( + + {Array.isArray(element.children) + ? renderTreeElements(element.children, sort) + : null} + + ) + } + + return ( + + {element.name} + + ) + }) + +type TreeViewProps = { + initialSelectedId?: string + indicator?: boolean + elements?: TreeViewElement[] + initialExpandedItems?: string[] + openIcon?: React.ReactNode + closeIcon?: React.ReactNode + sort?: TreeSortMode +} & Omit< + React.ComponentPropsWithoutRef, + "defaultValue" | "onValueChange" | "type" | "value" +> + +const Tree = forwardRef( + ( + { + className, + elements, + initialSelectedId, + initialExpandedItems, + children, + indicator = true, + openIcon, + closeIcon, + sort = "default", + dir, + ...props + }, + ref + ) => { + const [selectedId, setSelectedId] = useState( + initialSelectedId + ) + const [expandedItems, setExpandedItems] = useState( + initialExpandedItems + ) + + const selectItem = useCallback((id: string) => { + setSelectedId(id) + }, []) + + const handleExpand = useCallback((id: string) => { + setExpandedItems((prev) => { + if (prev?.includes(id)) { + return prev.filter((item) => item !== id) + } + return [...(prev ?? []), id] + }) + }, []) + + const expandSpecificTargetedElements = useCallback( + (elements?: TreeViewElement[], selectId?: string) => { + if (!elements || !selectId) return + const findParent = ( + currentElement: TreeViewElement, + currentPath: string[] = [] + ) => { + const isSelectable = currentElement.isSelectable ?? true + const newPath = [...currentPath, currentElement.id] + if (currentElement.id === selectId) { + if (isSelectable) { + setExpandedItems((prev) => mergeExpandedItems(prev, newPath)) + } else { + if (newPath.includes(currentElement.id)) { + newPath.pop() + setExpandedItems((prev) => mergeExpandedItems(prev, newPath)) + } + } + return + } + if ( + Array.isArray(currentElement.children) && + currentElement.children.length > 0 + ) { + currentElement.children.forEach((child) => { + findParent(child, newPath) + }) + } + } + elements.forEach((element) => { + findParent(element) + }) + }, + [] + ) + + useEffect(() => { + if (initialSelectedId) { + expandSpecificTargetedElements(elements, initialSelectedId) + } + }, [initialSelectedId, elements, expandSpecificTargetedElements]) + + const direction = dir === "rtl" ? "rtl" : "ltr" + const treeChildren = + children ?? (elements ? renderTreeElements(elements, sort) : null) + + return ( + +
+ + + {treeChildren} + + +
+
+ ) + } +) + +Tree.displayName = "Tree" + +const TreeIndicator = forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { direction } = useTree() + + return ( +
+ ) +}) + +TreeIndicator.displayName = "TreeIndicator" + +type FolderProps = { + expandedItems?: string[] + element: React.ReactNode + isSelectable?: boolean + isSelect?: boolean + actions?: React.ReactNode + onClick?: (e: React.MouseEvent) => void +} & React.ComponentPropsWithoutRef + +const Folder = forwardRef< + HTMLDivElement, + FolderProps & React.HTMLAttributes +>( + ( + { + className, + element, + value, + isSelectable = true, + isSelect, + actions, + onClick: onClickProp, + children, + ...props + }, + ref + ) => { + const { + direction, + handleExpand, + expandedItems, + indicator, + selectedId, + selectItem, + openIcon, + closeIcon, + } = useTree() + const isSelected = isSelect ?? selectedId === value + + return ( + + + +
{ + e.preventDefault() + if (onClickProp) { + onClickProp(e) + } else { + selectItem(value) + } + }} + > + { + e.stopPropagation() + handleExpand(value) + }} + > + + + {expandedItems?.includes(value) + ? (openIcon ?? ) + : (closeIcon ?? )} + {element} + {actions && ( + e.stopPropagation()}> + {actions} + + )} +
+
+
+ + {element && indicator && +
+ ) + } +) + +Folder.displayName = "Folder" + +const File = forwardRef< + HTMLDivElement, + { + value: string + handleSelect?: (id: string) => void + isSelectable?: boolean + isSelect?: boolean + fileIcon?: React.ReactNode + actions?: React.ReactNode + } & React.HTMLAttributes +>( + ( + { + value, + className, + handleSelect, + onClick, + isSelectable = true, + isSelect, + fileIcon, + actions, + children, + ...props + }, + ref + ) => { + const { direction, selectedId, selectItem } = useTree() + const isSelected = isSelect ?? selectedId === value + return ( +
{ + selectItem(value) + handleSelect?.(value) + onClick?.(event) + }} + {...props} + > + {fileIcon ?? } + {children} + {actions && ( + e.stopPropagation()}> + {actions} + + )} +
+ ) + } +) + +File.displayName = "File" + +const CollapseButton = forwardRef< + HTMLButtonElement, + { + elements: TreeViewElement[] + expandAll?: boolean + } & React.HTMLAttributes +>(({ className, elements, expandAll = false, children, ...props }, ref) => { + const { expandedItems, setExpandedItems } = useTree() + + const expendAllTree = useCallback((elements: TreeViewElement[]) => { + const expandedElementIds: string[] = [] + + const expandTree = (element: TreeViewElement) => { + const isSelectable = element.isSelectable ?? true + if (isSelectable && element.children && element.children.length > 0) { + expandedElementIds.push(element.id) + for (const child of element.children) { + expandTree(child) + } + } + } + + for (const element of elements) { + expandTree(element) + } + + return [...new Set(expandedElementIds)] + }, []) + + const closeAll = useCallback(() => { + setExpandedItems?.([]) + }, [setExpandedItems]) + + useEffect(() => { + if (expandAll) { + setExpandedItems?.(expendAllTree(elements)) + } + }, [expandAll, elements, expendAllTree, setExpandedItems]) + + return ( + + ) +}) + +CollapseButton.displayName = "CollapseButton" + +export { CollapseButton, File, Folder, Tree, type TreeViewElement } +export type { TreeSortMode } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index fcf9473..ce3a3ac 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -154,16 +154,9 @@ export function replaceKey(tool: Tool): Tool { export const defaultTool = (toolName?: string, displayName?: string): Tool => { const finalToolName = toolName || "my-tool"; return { - name: finalToolName, + binaryName: finalToolName, displayName: displayName || "My Tool", - commands: [ - { - key: slugify(finalToolName), - name: finalToolName, - description: "Main command", - sortOrder: 0, - }, - ], + commands: [], parameters: [ { key: "--help", @@ -172,7 +165,6 @@ export const defaultTool = (toolName?: string, displayName?: string): Tool => { parameterType: "Flag", dataType: "String", isRequired: false, - isGlobal: true, shortFlag: "-h", longFlag: "--help", isRepeatable: false, diff --git a/src/routes/tools/$toolName/edit.tsx b/src/routes/tools/$toolName/edit.tsx index 83b903a..72606a6 100644 --- a/src/routes/tools/$toolName/edit.tsx +++ b/src/routes/tools/$toolName/edit.tsx @@ -40,11 +40,11 @@ function RouteComponent() { const { isLocal } = Route.useSearch(); const [savedCommands, setSavedCommands] = useState(() => - tool ? getSavedCommandsFromStorage(tool.name) : [], + tool ? getSavedCommandsFromStorage(tool.binaryName) : [], ); const handleSaveCommand = (command: string) => { - const toolId = tool!.name; + const toolId = tool!.binaryName; const existingCommands = getSavedCommandsFromStorage(toolId); if (existingCommands.some((cmd) => cmd.command === command)) { toast.error("Command already exists", { @@ -62,7 +62,7 @@ function RouteComponent() { }; const handleDeleteSavedCommand = (commandKey: string) => { - const toolId = tool!.name; + const toolId = tool!.binaryName; removeSavedCommandFromStorage(toolId, commandKey); setSavedCommands(getSavedCommandsFromStorage(toolId)); }; @@ -73,7 +73,7 @@ function RouteComponent() { tool={tool!} isNewTool={!!isLocal} onSave={(tool) => { - localStorage.setItem(`tool-${tool.name}`, JSON.stringify(tool)); + localStorage.setItem(`tool-${tool.binaryName}`, JSON.stringify(tool)); }} savedCommands={savedCommands} onSaveCommand={handleSaveCommand} diff --git a/src/routes/tools/$toolName/index.tsx b/src/routes/tools/$toolName/index.tsx index bebcd5a..2269e20 100644 --- a/src/routes/tools/$toolName/index.tsx +++ b/src/routes/tools/$toolName/index.tsx @@ -5,7 +5,7 @@ import { slugify } from "@/components/commandly/utils/flat"; import { SavedCommandsDialog } from "@/components/tool-editor/dialogs/saved-commands-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Command, CommandGroup, CommandItem, CommandList } from "@/components/ui/command"; +import { Command, CommandGroup, CommandItem, CommandList, CommandSeparator } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; @@ -17,8 +17,8 @@ import { } from "@/lib/editor-utils"; import { SavedCommand } from "@/lib/types"; import { cn, defaultTool } from "@/lib/utils"; -import { createFileRoute } from "@tanstack/react-router"; -import { CheckIcon, ChevronsUpDownIcon, InfoIcon, SaveIcon, TerminalIcon } from "lucide-react"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import { CheckIcon, ChevronsUpDownIcon, Edit2Icon, InfoIcon, SaveIcon, TerminalIcon } from "lucide-react"; import { useQueryState } from "nuqs"; import { useState } from "react"; import { toast } from "sonner"; @@ -55,15 +55,31 @@ export const Route = createFileRoute("/tools/$toolName/")({ function RouteComponent() { const tool = Route.useLoaderData(); + const { newTool } = Route.useSearch(); const [parameterValues, setParameterValues] = useState({}); const [savedCommands, setSavedCommands] = useState(() => { if (!tool) return []; - const toolId = tool.name; + const toolId = tool.binaryName; return getSavedCommandsFromStorage(toolId); }); const [open, setOpen] = useState(false); const [savedCommandsOpen, setSavedCommandsOpen] = useState(false); + const hasUncategorizedParams = tool?.parameters.some((p) => !p.commandKey && !p.isGlobal) ?? false; + + const getCommandDepth = (key: string, depth = 0): number => { + const cmd = tool?.commands.find((c) => c.key === key); + if (!cmd?.parentCommandKey) return depth; + return getCommandDepth(cmd.parentCommandKey, depth + 1); + }; + + const getCommandLabel = (name: string): string => { + const cmd = tool?.commands.find((c) => c.name === name); + if (!cmd?.parentCommandKey) return name; + const parent = tool?.commands.find((c) => c.key === cmd.parentCommandKey); + return parent ? `${getCommandLabel(parent.name)} / ${name}` : name; + }; + const defaultCommandName = tool?.commands?.[0]?.name ?? ""; const [selectedCommand, setSelectedCommand] = useQueryState("command", { defaultValue: defaultCommandName, @@ -72,7 +88,7 @@ function RouteComponent() { if (!tool) return
Tool not found.
; const handleSaveCommand = (command: string) => { - const toolId = tool.name; + const toolId = tool.binaryName; const existingCommands = getSavedCommandsFromStorage(toolId); if (existingCommands.some((cmd) => cmd.command === command)) { toast.error("Command already exists", { @@ -95,7 +111,7 @@ function RouteComponent() { const handleDeleteCommand = (commandKey: string) => { if (!tool) return; - const toolId = tool.name; + const toolId = tool.binaryName; removeSavedCommandFromStorage(toolId, commandKey); setSavedCommands(getSavedCommandsFromStorage(toolId)); }; @@ -107,10 +123,10 @@ function RouteComponent() { - {tool.displayName ? `${tool.displayName} (${tool.name})` : `${tool.name}`} + {tool.displayName ? `${tool.displayName} (${tool.binaryName})` : `${tool.binaryName}`} {tool.info?.description && ( @@ -124,7 +140,22 @@ function RouteComponent() { )}

+ - - - - - - {tool.commands.map((option) => ( - { - setSelectedCommand(currentValue); - setOpen(false); - }} - > - {option.name} - - - ))} - - - - - -
+ {(tool.commands.length > 0 || hasUncategorizedParams) && ( +
+ Command + + + + + + + + {hasUncategorizedParams && ( + + { + setSelectedCommand(""); + setOpen(false); + }} + > + {tool.binaryName} + + + + )} + {hasUncategorizedParams && tool.commands.length > 0 && ( + + )} + {tool.commands.length > 0 && ( + + {tool.commands.map((option) => { + const depth = getCommandDepth(option.key); + return ( + { + setSelectedCommand(currentValue); + setOpen(false); + }} + style={{ paddingLeft: `${0.5 + depth * 1.25}rem` }} + > + {option.name} + + + ); + })} + + )} + + + + +
+ )}
command.name === selectedCommand, - )} + selectedCommand={ + selectedCommand === "" + ? null + : tool.commands.find((command) => command.name === selectedCommand) + } tool={tool} catalog={defaultComponents()} parameterValues={parameterValues} @@ -221,7 +289,11 @@ function RouteComponent() { command.name === selectedCommand)} + selectedCommand={ + selectedCommand === "" + ? null + : tool.commands.find((command) => command.name === selectedCommand) + } tool={tool} parameterValues={parameterValues} onSaveCommand={handleSaveCommand} diff --git a/src/routes/tools/index.tsx b/src/routes/tools/index.tsx index d7b1673..393789f 100644 --- a/src/routes/tools/index.tsx +++ b/src/routes/tools/index.tsx @@ -43,7 +43,9 @@ function RouteComponent() { const loaderData = Route.useLoaderData(); const [tools, setTools] = useState[]>(loaderData.serverTools || []); const [serverToolNames] = useState>( - new Set((loaderData.serverTools || []).map((t) => t.name).filter((n): n is string => !!n)), + new Set( + (loaderData.serverTools || []).map((t) => t.binaryName).filter((n): n is string => !!n), + ), ); useEffect(() => { @@ -53,7 +55,7 @@ function RouteComponent() { if (key?.startsWith("tool-")) { try { const tool = JSON.parse(localStorage.getItem(key)!) as Partial; - if (tool?.name && !serverToolNames.has(tool.name)) { + if (tool?.binaryName && !serverToolNames.has(tool.binaryName)) { localTools.push(tool); } } catch { @@ -63,8 +65,8 @@ function RouteComponent() { } if (localTools.length > 0) { setTools((prev) => { - const existingNames = new Set(prev.map((t) => t.name)); - const newTools = localTools.filter((t) => !existingNames.has(t.name)); + const existingNames = new Set(prev.map((t) => t.binaryName)); + const newTools = localTools.filter((t) => !existingNames.has(t.binaryName)); return [...newTools, ...prev]; }); } @@ -93,7 +95,7 @@ function RouteComponent() { const handleCreateTool = () => { const name = slugify(newToolName.trim()); const displayName = newToolDisplayName.trim() || newToolName.trim(); - const newTool: Tool = { name, displayName, commands: [], parameters: [] }; + const newTool: Tool = { binaryName: name, displayName, commands: [], parameters: [] }; localStorage.setItem(`tool-${name}`, JSON.stringify(newTool)); setNewToolDialogOpen(false); navigation({ @@ -104,14 +106,14 @@ function RouteComponent() { }; const handleDelete = (tool: Partial) => { - localStorage.removeItem(`tool-${tool.name}`); - setTools((prev) => prev.filter((t) => t.name !== tool.name)); + localStorage.removeItem(`tool-${tool.binaryName}`); + setTools((prev) => prev.filter((t) => t.binaryName !== tool.binaryName)); }; const filteredTools = React.useMemo(() => { return tools.filter((tool) => { const matchesName = searchValue - ? tool.name?.toLowerCase().includes(searchValue.toLowerCase()) || + ? tool.binaryName?.toLowerCase().includes(searchValue.toLowerCase()) || tool.displayName?.toLowerCase().includes(searchValue.toLowerCase()) : true; return matchesName; @@ -157,10 +159,9 @@ function RouteComponent() {
- + handleNewToolNameChange(e.target.value)} @@ -173,7 +174,6 @@ function RouteComponent() { { setNewToolDisplayName(e.target.value); @@ -239,9 +239,9 @@ function ListComponent({ {tools.map((tool: Partial, index: number) => { return ( ); diff --git a/tests/tool-editor/ai-chat-message-mapping.test.ts b/tests/tool-editor/ai-chat-message-mapping.test.ts index 83826b1..2200775 100644 --- a/tests/tool-editor/ai-chat-message-mapping.test.ts +++ b/tests/tool-editor/ai-chat-message-mapping.test.ts @@ -29,7 +29,7 @@ function makeTextMessage(role: "user" | "assistant", text: string, id?: string): function makeTool(name: string): Tool { return { key: crypto.randomUUID(), - name, + binaryName: name, displayName: name, description: "", version: "1.0.0", @@ -225,7 +225,7 @@ describe("toChatMessage", () => { expect(result.toolCalls![0].toolName).toBe("tavilyExtract"); expect(result.toolCalls![1].toolName).toBe("editTool"); expect(result.toolCalls![2].toolName).toBe("applyToolDefinition"); - expect(result.toolCalls![2].previewTool!.name).toBe("preview"); + expect(result.toolCalls![2].previewTool!.binaryName).toBe("preview"); expect(result.content).toBe("I've updated the tool based on the docs."); }); }); diff --git a/tests/tool-editor/command-tree.test.tsx b/tests/tool-editor/command-tree.test.tsx index 75f09e2..3940b0f 100644 --- a/tests/tool-editor/command-tree.test.tsx +++ b/tests/tool-editor/command-tree.test.tsx @@ -10,7 +10,7 @@ import { render, screen, fireEvent, waitFor, act } from "@testing-library/react" import { ReactNode } from "react"; const createComplexTool = (): Tool => ({ - name: "my-cli-tool", + binaryName: "my-cli-tool", displayName: "My CLI Tool", info: { description: "A sample CLI tool with nested commands", @@ -120,9 +120,21 @@ const createComplexTool = (): Tool => ({ metadata: { supportedInput: [], supportedOutput: [] }, }); +const simpleTestTool: Tool = { + ...defaultTool("test-tool", "Test tool"), + commands: [ + { + key: "test-tool", + name: "test-tool", + description: "Main command", + sortOrder: 0, + }, + ], +}; + const simpleTestState: Partial = { - tool: defaultTool("test-tool", "Test tool"), - selectedCommand: {} as Command, + tool: simpleTestTool, + selectedCommand: simpleTestTool.commands[0], }; const complexToolState = (): Partial => { @@ -148,323 +160,226 @@ function renderWithProvider(ui: ReactNode, initialState: Partial + btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), + ) as HTMLButtonElement[]; +} + +function findDeleteButton(container: Element): HTMLButtonElement | undefined { + return Array.from(container.querySelectorAll("button")).find((btn) => { + const svg = btn.querySelector("svg"); + return svg && svg.classList.contains("text-destructive"); + }) as HTMLButtonElement | undefined; +} + +function getRootTrigger(): Element { + const triggers = document.querySelectorAll("[data-radix-collection-item]"); + return triggers[0]; +} + +function getChevron(trigger: Element): Element { + return trigger.querySelector("[role='button']")!; +} + +function getTriggerFor(name: string): Element { + const elements = screen.getAllByText(name); + for (const el of elements) { + const trigger = el.closest("[data-radix-collection-item]") || el.closest("[role='button']"); + if (trigger && !trigger.classList.contains("font-medium")) return trigger; + } + const textEl = elements[0]; + return (textEl.closest("[data-radix-collection-item]") || textEl.closest("[role='button']"))!; +} + describe("CommandTree", () => { describe("Basic Rendering Tests", () => { - it("renders add command button", () => { + it("renders the tool name as the root node", () => { renderWithProvider(, simpleTestState); - expect(screen.getByText(/Add Command/)).toBeInTheDocument(); + expect(screen.getAllByText("test-tool").length).toBeGreaterThanOrEqual(1); }); - it("renders the root command (tool name)", () => { - renderWithProvider(, simpleTestState); - expect(screen.getByText("test-tool")).toBeInTheDocument(); - }); - - it("renders command hierarchy correctly with proper indentation", () => { - const initialState = complexToolState(); - renderWithProvider(, initialState); - expect(screen.getByText("my-cli-tool")).toBeInTheDocument(); + it("renders command hierarchy correctly", () => { + renderWithProvider(, complexToolState()); + expect(screen.getAllByText("my-cli-tool").length).toBeGreaterThanOrEqual(1); expect(screen.getByText("config")).toBeInTheDocument(); expect(screen.getByText("data")).toBeInTheDocument(); expect(screen.getByText("utils")).toBeInTheDocument(); expect(screen.getByText("help")).toBeInTheDocument(); }); - it("renders action buttons (Edit, Add, Delete) on hover", () => { - renderWithProvider(, simpleTestState); - const editButtons = screen.getAllByRole("button"); - const actionButtons = editButtons.filter( - (btn) => - btn.querySelector("svg") && - (btn.className.includes("opacity-0") || - btn.className.includes("group-hover:opacity-100")), - ); - expect(actionButtons.length).toBeGreaterThan(0); - }); - }); - - describe("Command Tree Structure Tests", () => { - it("renders subcommands when parent is expanded", async () => { + it("renders action buttons on command nodes", () => { renderWithProvider(, complexToolState()); - expect(screen.getByText("config")).toBeInTheDocument(); - - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); - - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - expect(screen.getByText("set")).toBeInTheDocument(); - expect(screen.getByText("list")).toBeInTheDocument(); - }); - } + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + expect(actions.length).toBeGreaterThan(0); }); - it("hides subcommands when parent is collapsed", async () => { + it("does not show delete button on root node", () => { renderWithProvider(, complexToolState()); - const dataElement = screen.getByText("data").closest("div"); - const expandButton = dataElement?.querySelector("button"); - - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("create")).toBeInTheDocument(); - }); - - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.queryByText("create")).not.toBeInTheDocument(); - expect(screen.queryByText("read")).not.toBeInTheDocument(); - expect(screen.queryByText("update")).not.toBeInTheDocument(); - expect(screen.queryByText("delete")).not.toBeInTheDocument(); - }); - } + const rootTrigger = getRootTrigger(); + const deleteBtn = findDeleteButton(rootTrigger); + expect(deleteBtn).toBeUndefined(); }); - it("shows correct chevron icons based on expansion state", () => { + it("shows delete button on command nodes", () => { renderWithProvider(, complexToolState()); - const utilsElement = screen.getByText("utils").closest("div"); - expect(utilsElement).toBeInTheDocument(); - const chevronButton = utilsElement?.querySelector("button"); - expect(chevronButton).toBeInTheDocument(); - const svgElement = chevronButton?.querySelector("svg"); - expect(svgElement).toBeInTheDocument(); + const helpTrigger = getTriggerFor("help"); + const deleteBtn = findDeleteButton(helpTrigger); + expect(deleteBtn).toBeDefined(); }); + }); - it("maintains correct indentation levels for nested commands", async () => { + describe("Command Tree Structure Tests", () => { + it("renders subcommands when parent is expanded", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - const getElement = screen.getByText("get").closest("div"); - const setElement = screen.getByText("set").closest("div"); - expect(getElement).toHaveStyle({ paddingLeft: expect.stringMatching(/\d+px/) }); - expect(setElement).toHaveStyle({ paddingLeft: expect.stringMatching(/\d+px/) }); - }); - } + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); + + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + expect(screen.getByText("set")).toBeInTheDocument(); + expect(screen.getByText("list")).toBeInTheDocument(); + }); }); - it("doesn't show delete button for root command", () => { + it("hides subcommands when parent is collapsed", async () => { renderWithProvider(, complexToolState()); - const rootElement = screen.getByText("my-cli-tool").closest("div"); - const buttons = rootElement?.querySelectorAll("button") || []; - const deleteButtons = Array.from(buttons).filter((btn) => { - const svg = btn.querySelector("svg"); - if (!svg) return false; - return ( - svg.classList.contains("text-destructive") || btn.classList.contains("text-destructive") - ); + + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + }); + + fireEvent.click(getChevron(configTrigger)); + await waitFor(() => { + expect(screen.queryByText("get")).not.toBeInTheDocument(); }); - expect(deleteButtons.length).toBe(0); }); }); describe("Interaction Tests", () => { it("clicking a command selects it", () => { renderWithProvider(, complexToolState()); - expect(capturedCtx.selectedCommand.name).toBe("my-cli-tool"); + expect(capturedCtx.selectedCommand?.name).toBe("my-cli-tool"); - const configElement = screen.getByText("config"); - fireEvent.click(configElement); - - expect(capturedCtx.selectedCommand.name).toBe("config"); + fireEvent.click(screen.getByText("config")); + expect(capturedCtx.selectedCommand?.name).toBe("config"); }); - it("clicking chevron toggles command expansion", async () => { + it("clicking the root node sets selectedCommand to null", () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); - - expect(screen.queryByText("get")).not.toBeInTheDocument(); - - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - expect(screen.getByText("set")).toBeInTheDocument(); - }); + expect(capturedCtx.selectedCommand?.name).toBe("my-cli-tool"); - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.queryByText("get")).not.toBeInTheDocument(); - expect(screen.queryByText("set")).not.toBeInTheDocument(); - }); - } + const rootTrigger = getRootTrigger(); + fireEvent.click(rootTrigger); + expect(capturedCtx.selectedCommand).toBeNull(); }); it("clicking edit button opens command dialog", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const buttons = Array.from(configElement?.querySelectorAll("button") || []); - const editButton = buttons.find( - (btn) => - btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), - ); + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + const editButton = actions[1]; - if (editButton) { - fireEvent.click(editButton); - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - expect(screen.getByText("Edit Command Settings")).toBeInTheDocument(); - }); - } else { - expect(buttons.length).toBeGreaterThan(1); - } + fireEvent.click(editButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + expect(screen.getByText("Edit Command Settings")).toBeInTheDocument(); + }); }); it("clicking add button on a command opens dialog for new subcommand", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const buttons = Array.from(configElement?.querySelectorAll("button") || []); - const actionButtons = buttons.filter( - (btn) => - btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), - ); - const addButton = actionButtons[1]; + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + const addButton = actions[2]; - if (addButton) { - fireEvent.click(addButton); - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - expect(screen.getByRole("heading", { name: "Add Command" })).toBeInTheDocument(); - }); - } else { - expect(actionButtons.length).toBeGreaterThanOrEqual(2); - } + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Add Command" })).toBeInTheDocument(); + }); }); it("clicking delete button removes the command", () => { renderWithProvider(, complexToolState()); const initialCommandCount = capturedCtx.tool.commands.length; - const helpElement = screen.getByText("help").closest("div"); - const buttons = Array.from(helpElement?.querySelectorAll("button") || []); - const deleteButton = buttons.find((btn) => { - const svg = btn.querySelector("svg"); - return svg && svg.classList.contains("text-destructive"); - }); + const helpTrigger = getTriggerFor("help"); + const deleteButton = findDeleteButton(helpTrigger); + expect(deleteButton).toBeDefined(); - if (deleteButton) { - fireEvent.click(deleteButton); - expect(capturedCtx.tool.commands.length).toBeLessThan(initialCommandCount); - expect(screen.queryByText("help")).not.toBeInTheDocument(); - } else { - expect(buttons.length).toBeGreaterThan(0); - } + fireEvent.click(deleteButton!); + expect(capturedCtx.tool.commands.length).toBeLessThan(initialCommandCount); + expect(screen.queryByText("help")).not.toBeInTheDocument(); }); - it("clicking 'Add Command' button opens dialog for new root-level command", async () => { + it("clicking root add button opens dialog for new root-level command", async () => { renderWithProvider(, complexToolState()); - const addCommandButton = screen.getByText(/Add Command/); - fireEvent.click(addCommandButton); + const rootTrigger = getRootTrigger(); + const actions = findActionButtons(rootTrigger); + const addButton = actions[0]; + fireEvent.click(addButton); await waitFor(() => { expect(screen.getByRole("dialog")).toBeInTheDocument(); expect(screen.getByRole("heading", { name: "Add Command" })).toBeInTheDocument(); }); }); - it("doesn't trigger selection when clicking action buttons", () => { - renderWithProvider(, complexToolState()); - const rootCommand = capturedCtx.tool.commands.find((c) => c.name === "my-cli-tool"); - - const configElement = screen.getByText("config").closest("div"); - const editButton = configElement?.querySelector("button svg")?.closest("button"); - - if (editButton && editButton.querySelector("svg")) { - fireEvent.click(editButton); - // Root should still be selected if stopPropagation works - if (rootCommand) { - expect(screen.getByText("my-cli-tool")).toBeInTheDocument(); - } - } - }); - - it("preserves expansion state when opening add command dialog", async () => { - renderWithProvider(, complexToolState()); - - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); - - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - }); - - const addCommandButton = screen.getByRole("button", { name: /Add Command/i }); - fireEvent.click(addCommandButton); - - expect(screen.getByText("get")).toBeInTheDocument(); - expect(screen.getByText("set")).toBeInTheDocument(); - } - }); - it("handles multiple levels of nesting correctly", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const configExpandButton = configElement?.querySelector("button"); - - if (configExpandButton) { - fireEvent.click(configExpandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - }); - - const getElement = screen.getByText("get"); - fireEvent.click(getElement); + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + }); - expect(capturedCtx.selectedCommand.name).toBe("get"); - expect(capturedCtx.selectedCommand.parentCommandKey).toBe("config"); - } + fireEvent.click(screen.getByText("get")); + expect(capturedCtx.selectedCommand?.name).toBe("get"); + expect(capturedCtx.selectedCommand?.parentCommandKey).toBe("config"); }); }); describe("State Management Tests", () => { it("updates selected command in context when clicking a command", () => { renderWithProvider(, complexToolState()); - expect(capturedCtx.selectedCommand.name).toBe("my-cli-tool"); - - const configElement = screen.getByText("config"); - fireEvent.click(configElement); + expect(capturedCtx.selectedCommand?.name).toBe("my-cli-tool"); - expect(capturedCtx.selectedCommand.name).toBe("config"); - expect(capturedCtx.selectedCommand.parentCommandKey).toBe("my-cli-tool"); + fireEvent.click(screen.getByText("config")); + expect(capturedCtx.selectedCommand?.name).toBe("config"); + expect(capturedCtx.selectedCommand?.parentCommandKey).toBe("my-cli-tool"); }); it("clicking edit opens dialog pre-filled with command details", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const buttons = Array.from(configElement?.querySelectorAll("button") || []); - const editButton = buttons.find( - (btn) => - btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), - ); + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + const editButton = actions[1]; - if (editButton) { - fireEvent.click(editButton); - await waitFor(() => { - expect(screen.getByText("Edit Command Settings")).toBeInTheDocument(); - const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; - expect(nameInput.value).toBe("config"); - }); - } + fireEvent.click(editButton); + await waitFor(() => { + expect(screen.getByText("Edit Command Settings")).toBeInTheDocument(); + const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + expect(nameInput.value).toBe("config"); + }); }); it("opens dialog when adding new command", async () => { renderWithProvider(, complexToolState()); - const addCommandButton = screen.getByText(/Add Command/); - fireEvent.click(addCommandButton); + const rootTrigger = getRootTrigger(); + const actions = findActionButtons(rootTrigger); + fireEvent.click(actions[0]); await waitFor(() => { expect(screen.getByRole("dialog")).toBeInTheDocument(); @@ -475,120 +390,161 @@ describe("CommandTree", () => { it("opens dialog when adding subcommand", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const buttons = Array.from(configElement?.querySelectorAll("button") || []); - const actionButtons = buttons.filter( - (btn) => - btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), - ); - const addButton = actionButtons[1]; + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + const addButton = actions[2]; - if (addButton) { - fireEvent.click(addButton); - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - expect(screen.getByRole("heading", { name: "Add Command" })).toBeInTheDocument(); - }); - } + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Add Command" })).toBeInTheDocument(); + }); }); it("saves subcommand with correct parentCommandKey when added via + button", async () => { renderWithProvider(, complexToolState()); const initialCount = capturedCtx.tool.commands.length; - const configElement = screen.getByText("config").closest("div"); - const buttons = Array.from(configElement?.querySelectorAll("button") || []); - const actionButtons = buttons.filter( - (btn) => - btn.classList.contains("opacity-0") && btn.classList.contains("group-hover:opacity-100"), - ); - const addButton = actionButtons[1]; + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + const addButton = actions[2]; - if (addButton) { - fireEvent.click(addButton); - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - }); + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + }); - const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; - fireEvent.change(nameInput, { target: { value: "new-sub" } }); + const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + fireEvent.change(nameInput, { target: { value: "new-sub" } }); - const saveButton = screen.getByRole("button", { name: "Add" }); - fireEvent.click(saveButton); + const saveButton = screen.getByRole("button", { name: "Add" }); + fireEvent.click(saveButton); - await waitFor(() => { - expect(capturedCtx.tool.commands.length).toBe(initialCount + 1); - }); + await waitFor(() => { + expect(capturedCtx.tool.commands.length).toBe(initialCount + 1); + }); - const newCmd = capturedCtx.tool.commands.find((c) => c.name === "new-sub"); - expect(newCmd).toBeDefined(); - expect(newCmd!.parentCommandKey).toBe("config"); - } + const newCmd = capturedCtx.tool.commands.find((c) => c.name === "new-sub"); + expect(newCmd).toBeDefined(); + expect(newCmd!.parentCommandKey).toBe("config"); }); - it("removes commands from context when deleting", () => { + it("disables save when subcommand name matches parent command name", async () => { renderWithProvider(, complexToolState()); - const initialCommandCount = capturedCtx.tool.commands.length; - const helpCommand = capturedCtx.tool.commands.find((cmd) => cmd.name === "help"); - expect(helpCommand).toBeDefined(); - - const helpElement = screen.getByText("help").closest("div"); - const buttons = Array.from(helpElement?.querySelectorAll("button") || []); - const deleteButton = buttons.find((btn) => { - const svg = btn.querySelector("svg"); - return svg && svg.classList.contains("text-destructive"); + + const configTrigger = getTriggerFor("config"); + const actions = findActionButtons(configTrigger); + const addButton = actions[2]; + + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); }); - if (deleteButton) { - fireEvent.click(deleteButton); - expect(capturedCtx.tool.commands.length).toBe(initialCommandCount - 1); - expect(capturedCtx.tool.commands.find((cmd) => cmd.name === "help")).toBeUndefined(); - } + const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + fireEvent.change(nameInput, { target: { value: "config" } }); + + const saveButton = screen.getByRole("button", { name: "Add" }); + expect(saveButton).toBeDisabled(); + expect( + screen.getByText("A command with this name already exists at this level."), + ).toBeInTheDocument(); }); - it("updates selected command when current selection is deleted", () => { - const initialState = complexToolState(); - const helpCmd = initialState.tool!.commands.find((c) => c.name === "help")!; - renderWithProvider(, { ...initialState, selectedCommand: helpCmd }); + it("disables save when subcommand name matches an existing sibling", async () => { + renderWithProvider(, complexToolState()); - expect(capturedCtx.selectedCommand.name).toBe("help"); + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + }); - const helpElement = screen.getByText("help").closest("div"); - const buttons = Array.from(helpElement?.querySelectorAll("button") || []); - const deleteButton = buttons.find((btn) => { - const svg = btn.querySelector("svg"); - return svg && svg.classList.contains("text-destructive"); + const actions = findActionButtons(configTrigger); + const addButton = actions[2]; + + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); }); - if (deleteButton) { - fireEvent.click(deleteButton); - expect(capturedCtx.selectedCommand.name).toBe("my-cli-tool"); - } + const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + fireEvent.change(nameInput, { target: { value: "get" } }); + + const saveButton = screen.getByRole("button", { name: "Add" }); + expect(saveButton).toBeDisabled(); + expect( + screen.getByText("A command with this name already exists at this level."), + ).toBeInTheDocument(); }); - it("maintains expanded commands state independently of context updates", async () => { + it("clears dialog inputs after closing and reopening", async () => { renderWithProvider(, complexToolState()); - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); + const rootTrigger = getRootTrigger(); + const rootActions = findActionButtons(rootTrigger); + const addButton = rootActions[0]; - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - }); + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + }); - const addCommandButton = screen.getByText(/Add Command/); - fireEvent.click(addCommandButton); + const nameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + fireEvent.change(nameInput, { target: { value: "some-command" } }); + expect(nameInput.value).toBe("some-command"); - expect(screen.getByText("get")).toBeInTheDocument(); - expect(screen.getByText("set")).toBeInTheDocument(); - } + const descInput = screen.getByLabelText("Description") as HTMLTextAreaElement; + fireEvent.change(descInput, { target: { value: "some description" } }); + + fireEvent.click(screen.getByRole("button", { name: "Add" })); + await waitFor(() => { + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + }); + + fireEvent.click(addButton); + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + }); + + const newNameInput = screen.getByLabelText("Command Name") as HTMLInputElement; + const newDescInput = screen.getByLabelText("Description") as HTMLTextAreaElement; + expect(newNameInput.value).toBe(""); + expect(newDescInput.value).toBe(""); + }); + + it("removes commands from context when deleting", () => { + renderWithProvider(, complexToolState()); + const initialCommandCount = capturedCtx.tool.commands.length; + expect(capturedCtx.tool.commands.find((cmd) => cmd.name === "help")).toBeDefined(); + + const helpTrigger = getTriggerFor("help"); + const deleteButton = findDeleteButton(helpTrigger); + expect(deleteButton).toBeDefined(); + + fireEvent.click(deleteButton!); + expect(capturedCtx.tool.commands.length).toBe(initialCommandCount - 1); + expect(capturedCtx.tool.commands.find((cmd) => cmd.name === "help")).toBeUndefined(); + }); + + it("updates selected command when current selection is deleted", () => { + const initialState = complexToolState(); + const helpCmd = initialState.tool!.commands.find((c) => c.name === "help")!; + renderWithProvider(, { ...initialState, selectedCommand: helpCmd }); + + expect(capturedCtx.selectedCommand?.name).toBe("help"); + + const helpTrigger = getTriggerFor("help"); + const deleteButton = findDeleteButton(helpTrigger); + expect(deleteButton).toBeDefined(); + + fireEvent.click(deleteButton!); + expect(capturedCtx.selectedCommand?.name).toBe("my-cli-tool"); }); it("responds to external context changes", async () => { renderWithProvider(, complexToolState()); - expect(screen.getByText("my-cli-tool")).toBeInTheDocument(); + expect(screen.getAllByText("my-cli-tool").length).toBeGreaterThanOrEqual(1); const dataCommand = capturedCtx.tool.commands.find((cmd) => cmd.name === "data"); if (dataCommand) { @@ -597,13 +553,13 @@ describe("CommandTree", () => { }); await waitFor(() => { - expect(screen.getByText("my-cli-tool")).toBeInTheDocument(); + expect(screen.getAllByText("my-cli-tool").length).toBeGreaterThanOrEqual(1); expect(screen.getByText("data")).toBeInTheDocument(); }); } }); - it("handles command hierarchy changes correctly", () => { + it("handles command hierarchy changes correctly", async () => { renderWithProvider(, complexToolState()); const currentCommands = capturedCtx.tool.commands; @@ -619,38 +575,12 @@ describe("CommandTree", () => { capturedCtx.updateTool({ commands: [...currentCommands, newCommand] }); }); - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); - if (expandButton) { - fireEvent.click(expandButton); + await waitFor(() => { expect(screen.getByText("new-test-command")).toBeInTheDocument(); - } - }); - - it("preserves component state during context updates", async () => { - renderWithProvider(, complexToolState()); - - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); - - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - }); - - const dataCommand = capturedCtx.tool.commands.find((cmd) => cmd.name === "data"); - if (dataCommand) { - act(() => { - capturedCtx.setSelectedCommand(dataCommand); - }); - } - - // Config should still be expanded - expect(screen.getByText("get")).toBeInTheDocument(); - expect(screen.getByText("set")).toBeInTheDocument(); - } + }); }); it("handles rapid context changes correctly", async () => { @@ -660,35 +590,21 @@ describe("CommandTree", () => { const dataCommand = commands.find((cmd) => cmd.name === "data")!; const utilsCommand = commands.find((cmd) => cmd.name === "utils")!; - if (configCommand && dataCommand && utilsCommand) { - act(() => { - capturedCtx.setSelectedCommand(configCommand); - capturedCtx.setSelectedCommand(dataCommand); - capturedCtx.setSelectedCommand(utilsCommand); - }); + act(() => { + capturedCtx.setSelectedCommand(configCommand); + capturedCtx.setSelectedCommand(dataCommand); + capturedCtx.setSelectedCommand(utilsCommand); + }); - await waitFor(() => { - expect(capturedCtx.selectedCommand.name).toBe("utils"); - }); + await waitFor(() => { + expect(capturedCtx.selectedCommand?.name).toBe("utils"); + }); - expect(screen.getByText("utils")).toBeInTheDocument(); - } + expect(screen.getByText("utils")).toBeInTheDocument(); }); }); describe("Edge Cases Tests", () => { - it("handles commands with no subcommands correctly", () => { - renderWithProvider(, complexToolState()); - - const helpElement = screen.getByText("help").closest("div"); - expect(helpElement).toBeInTheDocument(); - - const expandButton = helpElement?.querySelector("#expand-button"); - const spacerDiv = helpElement?.querySelector("div.w-4"); - expect(spacerDiv).toBeInTheDocument(); - expect(expandButton).not.toBeInTheDocument(); - }); - it("handles deep nesting of commands", async () => { const complexTool = createComplexTool(); const deepTool: Tool = { @@ -717,37 +633,20 @@ describe("CommandTree", () => { selectedCommand: deepTool.commands[0], }); - const configElement = screen.getByText("config").closest("div"); - const configExpandButton = configElement?.querySelector("button"); + fireEvent.click(getChevron(getTriggerFor("config"))); + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + }); - if (configExpandButton) { - fireEvent.click(configExpandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - }); + fireEvent.click(getChevron(getTriggerFor("get"))); + await waitFor(() => { + expect(screen.getByText("level3")).toBeInTheDocument(); + }); - const getElement = screen.getByText("get").closest("div"); - const getExpandButton = getElement?.querySelector("button"); - - if (getExpandButton) { - fireEvent.click(getExpandButton); - await waitFor(() => { - expect(screen.getByText("level3")).toBeInTheDocument(); - }); - - const level3Element = screen.getByText("level3").closest("div"); - const level3ExpandButton = level3Element?.querySelector("button"); - - if (level3ExpandButton) { - fireEvent.click(level3ExpandButton); - await waitFor(() => { - expect(screen.getByText("level4")).toBeInTheDocument(); - }); - const level4Element = screen.getByText("level4").closest("div"); - expect(level4Element).toBeInTheDocument(); - } - } - } + fireEvent.click(getChevron(getTriggerFor("level3"))); + await waitFor(() => { + expect(screen.getByText("level4")).toBeInTheDocument(); + }); }); it("maintains state correctly after command deletion", () => { @@ -755,80 +654,25 @@ describe("CommandTree", () => { const helpCmd = initialState.tool!.commands.find((c) => c.name === "help")!; renderWithProvider(, { ...initialState, selectedCommand: helpCmd }); - expect(capturedCtx.selectedCommand.name).toBe("help"); + expect(capturedCtx.selectedCommand?.name).toBe("help"); - const helpElement = screen.getByText("help").closest("div"); - const buttons = Array.from(helpElement?.querySelectorAll("button") || []); - const deleteButton = buttons.find((btn) => { - const svg = btn.querySelector("svg"); - return svg && svg.classList.contains("text-destructive"); - }); + const helpTrigger = getTriggerFor("help"); + const deleteButton = findDeleteButton(helpTrigger); + expect(deleteButton).toBeDefined(); - if (deleteButton) { - fireEvent.click(deleteButton); - expect(capturedCtx.selectedCommand.name).toBe("my-cli-tool"); - expect(screen.queryByText("help")).not.toBeInTheDocument(); - } + fireEvent.click(deleteButton!); + expect(capturedCtx.selectedCommand?.name).toBe("my-cli-tool"); + expect(screen.queryByText("help")).not.toBeInTheDocument(); }); it("handles empty command list gracefully", () => { - const complexTool = createComplexTool(); - const minimalTool: Tool = { - ...complexTool, - commands: [ - { - key: "minimal-tool-id", - name: "minimal-tool", - description: "Minimal tool with just root command", - sortOrder: 0, - }, - ], - }; - + const tool = defaultTool("empty-tool", "Empty Tool"); renderWithProvider(, { - tool: minimalTool, - selectedCommand: minimalTool.commands[0], + tool, + selectedCommand: null, }); - expect(screen.getByText("minimal-tool")).toBeInTheDocument(); - expect(screen.getByText(/Add Command/)).toBeInTheDocument(); - - const rootElement = screen.getByText("minimal-tool").closest("div"); - const spacerDiv = rootElement?.querySelector("div.w-4"); - expect(spacerDiv).toBeInTheDocument(); - }); - - it("preserves expansion state when opening add command dialog", async () => { - renderWithProvider(, complexToolState()); - - const configElement = screen.getByText("config").closest("div"); - const expandButton = configElement?.querySelector("button"); - - if (expandButton) { - fireEvent.click(expandButton); - await waitFor(() => { - expect(screen.getByText("get")).toBeInTheDocument(); - }); - - const buttons = Array.from(configElement?.querySelectorAll("button") || []); - const actionButtons = buttons.filter( - (btn) => - btn.classList.contains("opacity-0") && - btn.classList.contains("group-hover:opacity-100"), - ); - const addButton = actionButtons[1]; - - if (addButton) { - fireEvent.click(addButton); - expect(screen.getByText("get")).toBeInTheDocument(); - expect(screen.getByText("set")).toBeInTheDocument(); - - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - expect(screen.getByRole("heading", { name: "Add Command" })).toBeInTheDocument(); - }); - } - } + expect(screen.getByText("empty-tool")).toBeInTheDocument(); }); it("handles invalid command hierarchies gracefully", () => { @@ -860,7 +704,77 @@ describe("CommandTree", () => { ).not.toThrow(); expect(screen.getByText("root")).toBeInTheDocument(); - expect(screen.getByText(/Add Command/)).toBeInTheDocument(); + }); + + it("preserves expansion state when opening add command dialog", async () => { + renderWithProvider(, complexToolState()); + + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + }); + + const rootTrigger = getRootTrigger(); + const rootActions = findActionButtons(rootTrigger); + fireEvent.click(rootActions[0]); + + expect(screen.getByText("get")).toBeInTheDocument(); + expect(screen.getByText("set")).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByRole("dialog")).toBeInTheDocument(); + }); + }); + }); + + describe("Drag and Drop Tests", () => { + it("renders drag handle for non-root commands", () => { + renderWithProvider(, complexToolState()); + const helpTrigger = getTriggerFor("help"); + const actions = findActionButtons(helpTrigger); + // grip + edit + add + delete = 4 buttons for non-root leaf commands + expect(actions.length).toBeGreaterThanOrEqual(3); + }); + + it("does not render drag handle for the root node", () => { + renderWithProvider(, complexToolState()); + const rootTrigger = getRootTrigger(); + const actions = findActionButtons(rootTrigger); + // Root only has Add button (no grip, no edit, no delete) + expect(actions).toHaveLength(1); + }); + + it("renders drag handle for subcommands", async () => { + renderWithProvider(, complexToolState()); + const configTrigger = getTriggerFor("config"); + fireEvent.click(getChevron(configTrigger)); + await waitFor(() => { + expect(screen.getByText("get")).toBeInTheDocument(); + }); + const getTrigger = getTriggerFor("get"); + const actions = findActionButtons(getTrigger); + expect(actions.length).toBeGreaterThanOrEqual(3); + }); + + it("reorderCommands updates sortOrder in context", () => { + renderWithProvider(, complexToolState()); + expect(typeof capturedCtx.reorderCommands).toBe("function"); + + const rootChildren = capturedCtx.tool.commands.filter( + (c) => c.parentCommandKey === "my-cli-tool", + ); + const reversedKeys = [...rootChildren].reverse().map((c) => c.key); + + act(() => { + capturedCtx.reorderCommands(reversedKeys, "my-cli-tool"); + }); + + const updated = capturedCtx.tool.commands.filter( + (c) => c.parentCommandKey === "my-cli-tool", + ); + const first = updated.find((c) => c.sortOrder === 0); + expect(first?.key).toBe(reversedKeys[0]); }); }); }); diff --git a/tests/tool-editor/dialogs/command-dialog.test.tsx b/tests/tool-editor/dialogs/command-dialog.test.tsx index 1f30a6c..ba36da8 100644 --- a/tests/tool-editor/dialogs/command-dialog.test.tsx +++ b/tests/tool-editor/dialogs/command-dialog.test.tsx @@ -20,7 +20,7 @@ const createTestState = ( command: Command, toolName: string = "test-tool", ): Partial => ({ - tool: { ...defaultTool(toolName, "Test tool"), name: toolName, commands: [command] }, + tool: { ...defaultTool(toolName, "Test tool"), binaryName: toolName, commands: [command] }, selectedCommand: command, }); @@ -58,7 +58,7 @@ describe("CommandDialog - Rendering & Structure", () => { expect(screen.getByText("Edit Command Settings")).toBeInTheDocument(); expect(screen.getByLabelText("Command Name")).toBeInTheDocument(); - expect(screen.getByLabelText("Sort Order")).toBeInTheDocument(); + expect(screen.getByLabelText("Interactive")).toBeInTheDocument(); expect(screen.getByLabelText("Description")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Save Changes" })).toBeInTheDocument(); }); @@ -189,61 +189,6 @@ describe("CommandDialog - Form Fields", () => { expect((nameInput as HTMLInputElement).value).toBe("new-command"); }); - it("displays current sort order in the input", () => { - const command = createTestCommand({ sortOrder: 5 }); - renderWithProvider( - , - createTestState(command), - ); - - const sortOrderInput = screen.getByLabelText("Sort Order") as HTMLInputElement; - expect(sortOrderInput.value).toBe("5"); - }); - - it("updates sort order when input changes", () => { - const command = createTestCommand(); - renderWithProvider( - , - createTestState(command), - ); - - const sortOrderInput = screen.getByLabelText("Sort Order"); - fireEvent.change(sortOrderInput, { target: { value: "10" } }); - - expect((sortOrderInput as HTMLInputElement).value).toBe("10"); - }); - - it("defaults sort order to 0 for invalid input", () => { - const command = createTestCommand(); - renderWithProvider( - , - createTestState(command), - ); - - const sortOrderInput = screen.getByLabelText("Sort Order"); - fireEvent.change(sortOrderInput, { target: { value: "invalid" } }); - - expect((sortOrderInput as HTMLInputElement).value).toBe("0"); - }); - it("displays current description in the textarea", () => { const command = createTestCommand({ description: "My test description" }); renderWithProvider( @@ -354,7 +299,6 @@ describe("CommandDialog - Save Functionality", () => { ); fireEvent.change(screen.getByLabelText("Command Name"), { target: { value: "new-name" } }); - fireEvent.change(screen.getByLabelText("Sort Order"), { target: { value: "15" } }); fireEvent.change(screen.getByLabelText("Description"), { target: { value: "New description" }, }); @@ -365,7 +309,6 @@ describe("CommandDialog - Save Functionality", () => { expect.objectContaining({ name: "new-name", description: "New description", - sortOrder: 15, }), ); }); @@ -476,7 +419,7 @@ describe("CommandDialog - UI Elements and Layout", () => { ); expect(screen.getByLabelText("Command Name")).toBeInTheDocument(); - expect(screen.getByLabelText("Sort Order")).toBeInTheDocument(); + expect(screen.getByLabelText("Interactive")).toBeInTheDocument(); expect(screen.getByLabelText("Description")).toBeInTheDocument(); }); diff --git a/tests/tool-editor/dialogs/parameter-details-dialog.test.tsx b/tests/tool-editor/dialogs/parameter-details-dialog.test.tsx index 152dfdc..d57de68 100644 --- a/tests/tool-editor/dialogs/parameter-details-dialog.test.tsx +++ b/tests/tool-editor/dialogs/parameter-details-dialog.test.tsx @@ -36,7 +36,7 @@ const createTestState = ( toolName: string = "test-tool", command?: Command, ): Partial => ({ - tool: { ...defaultTool(toolName, "Test tool"), name: toolName }, + tool: { ...defaultTool(toolName, "Test tool"), binaryName: toolName }, selectedCommand: command || createTestCommand(), selectedParameter: parameter, }); diff --git a/tests/tool-editor/help-menu.test.tsx b/tests/tool-editor/help-menu.test.tsx index a9a6103..7ec526c 100644 --- a/tests/tool-editor/help-menu.test.tsx +++ b/tests/tool-editor/help-menu.test.tsx @@ -16,7 +16,7 @@ describe("HelpMenu", () => { it("does not render undefined when descriptions are missing", () => { const tool: Tool = { - name: "tool", + binaryName: "tool", displayName: "Tool", commands: [ { @@ -38,4 +38,22 @@ describe("HelpMenu", () => { expect(preview?.textContent).not.toContain("undefined"); expect(preview?.textContent).toContain("tool"); }); + + it("does not render COMMANDS section when there are no commands", () => { + const tool: Tool = { + binaryName: "tool", + displayName: "Tool", + commands: [], + parameters: [], + }; + + render( + + + , + ); + + const preview = screen.getByText(/USAGE:/).closest("pre"); + expect(preview?.textContent).not.toContain("COMMANDS:"); + }); }); diff --git a/tests/tool-editor/parameter-list.test.tsx b/tests/tool-editor/parameter-list.test.tsx index ffbc29f..c7b8a98 100644 --- a/tests/tool-editor/parameter-list.test.tsx +++ b/tests/tool-editor/parameter-list.test.tsx @@ -6,7 +6,7 @@ import { useToolBuilder, } from "@/components/tool-editor/tool-editor.context"; import { defaultTool } from "@/lib/utils"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, fireEvent, act } from "@testing-library/react"; import { ReactNode } from "react"; const createTestParameter = (overrides: Partial = {}): Parameter => ({ @@ -402,4 +402,45 @@ describe("ParameterList - Rendering & Structure", () => { expect(screen.getByText("(--verbose, -v)")).toBeInTheDocument(); }); }); + + describe("Drag and Drop", () => { + it("renders a drag handle for each parameter card", () => { + const parameters = [ + createTestParameter({ key: "p1", name: "param-one" }), + createTestParameter({ key: "p2", name: "param-two" }), + ]; + const state = baseTestState(); + state.tool = { ...state.tool!, parameters }; + renderWithProvider(, state); + + const cards = screen.getAllByText(/param-one|param-two/); + expect(cards.length).toBeGreaterThanOrEqual(2); + + const allButtons = document.querySelectorAll("button.opacity-0.group-hover\\:opacity-100"); + // Each card has grip + edit buttons (at minimum 2 × 2 = 4 hidden buttons) + expect(allButtons.length).toBeGreaterThanOrEqual(4); + }); + + it("reorderParameters updates sortOrder in context", () => { + const p1 = createTestParameter({ key: "p1", name: "param-one", commandKey: "test-command-key" }); + const p2 = createTestParameter({ key: "p2", name: "param-two", commandKey: "test-command-key" }); + const p3 = createTestParameter({ key: "p3", name: "param-three", commandKey: "test-command-key" }); + const state = baseTestState(); + state.tool = { ...state.tool!, parameters: [p1, p2, p3] }; + renderWithProvider(, state); + + expect(typeof capturedCtx.reorderParameters).toBe("function"); + + act(() => { + capturedCtx.reorderParameters(["p3", "p1", "p2"]); + }); + + const p3Updated = capturedCtx.tool.parameters.find((p) => p.key === "p3"); + const p1Updated = capturedCtx.tool.parameters.find((p) => p.key === "p1"); + const p2Updated = capturedCtx.tool.parameters.find((p) => p.key === "p2"); + expect(p3Updated?.sortOrder).toBe(0); + expect(p1Updated?.sortOrder).toBe(1); + expect(p2Updated?.sortOrder).toBe(2); + }); + }); }); diff --git a/tests/tool-editor/tool-editor.test.tsx b/tests/tool-editor/tool-editor.test.tsx index 16cdf41..58c1ce4 100644 --- a/tests/tool-editor/tool-editor.test.tsx +++ b/tests/tool-editor/tool-editor.test.tsx @@ -1,4 +1,5 @@ import ToolEditor from "@/components/tool-editor/tool-editor"; +import { Tool } from "@/components/commandly/types/flat"; import { defaultTool } from "@/lib/utils"; import { render, screen } from "@testing-library/react"; import { withNuqsTestingAdapter, type OnUrlUpdateFunction } from "nuqs/adapters/testing"; @@ -16,4 +17,18 @@ describe("ToolEditor", () => { }); expect(screen.getByText(/New Tool/, { selector: "span" })).toBeInTheDocument(); }); + + it("does not crash when binaryName or displayName is undefined", () => { + const onUrlUpdate = vi.fn(); + const incompleteTool = { ...defaultTool(), binaryName: undefined, displayName: undefined } as unknown as Tool; + + expect(() => + render(, { + wrapper: withNuqsTestingAdapter({ + searchParams: "?test=test", + onUrlUpdate, + }), + }) + ).not.toThrow(); + }); }); diff --git a/tests/tool-editor/tools.test.ts b/tests/tool-editor/tools.test.ts new file mode 100644 index 0000000..034de49 --- /dev/null +++ b/tests/tool-editor/tools.test.ts @@ -0,0 +1,54 @@ +import { applyMergePatch } from "@/components/tool-editor/tools"; +import { defaultTool } from "@/lib/utils"; + +describe("applyMergePatch", () => { + it("merges top-level scalar fields into base", () => { + const base = defaultTool("curl"); + const result = applyMergePatch(base, { displayName: "cURL Updated" }); + expect(result.displayName).toBe("cURL Updated"); + }); + + it("preserves unpatched fields from base", () => { + const base = defaultTool("curl"); + const result = applyMergePatch(base, { displayName: "cURL Updated" }); + expect(result.binaryName).toBe("curl"); + }); + + it("removes top-level fields set to null in the patch", () => { + const base = { ...defaultTool("curl"), info: { description: "A transfer tool" } }; + const result = applyMergePatch(base as unknown as Parameters[0], { info: null } as unknown as Parameters[1]); + expect((result as unknown as Record).info).toBeUndefined(); + }); + + it("does not mutate the base tool", () => { + const base = defaultTool("curl"); + applyMergePatch(base, { displayName: "New Name" }); + expect(base.displayName).toBe("My Tool"); + }); + + it("replaces arrays entirely rather than merging them", () => { + const base = defaultTool("curl"); + const newParams = [ + { + key: "output", + name: "Output", + longFlag: "--output", + parameterType: "Option" as const, + dataType: "String" as const, + isRequired: false, + isRepeatable: false, + }, + ]; + const result = applyMergePatch(base, { parameters: newParams }); + expect(result.parameters).toHaveLength(1); + expect(result.parameters[0].key).toBe("output"); + }); + + it("applies multiple patches independently", () => { + const base = defaultTool("curl"); + const first = applyMergePatch(base, { displayName: "Step 1" }); + const second = applyMergePatch(first, { binaryName: "wget" }); + expect(second.displayName).toBe("Step 1"); + expect(second.binaryName).toBe("wget"); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 187a836..2b456ca 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,7 +12,15 @@ export default defineConfig({ setupFiles: ["./tests/vitest.setup.ts"], coverage: { provider: "v8", - reporter: process.env.GITHUB_ACTIONS ? ["text", "github-actions"] : ["text"], + reporter: process.env.GITHUB_ACTIONS + ? ["text", "github-actions", "json-summary"] + : ["text"], + exclude: [ + "src/components/ui/**", + "src/components/ai-elements/**", + "src/routes/**", + "registry/commandly/ui/**", + ], }, }, }); From 8986869637a06858efe413d859040ac8a54f75d1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 16 Apr 2026 17:48:16 +0000 Subject: [PATCH 3/5] chore: update generated specifications and registry --- public/r/generated-command.json | 6 +++--- public/r/json-output.json | 4 ++-- public/r/tool-renderer.json | 4 ++-- public/r/ui.json | 8 ++++---- public/specification/nested.json | 3 +++ 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/public/r/generated-command.json b/public/r/generated-command.json index a0f479f..f4078c9 100644 --- a/public/r/generated-command.json +++ b/public/r/generated-command.json @@ -14,7 +14,7 @@ "files": [ { "path": "registry/commandly/generated-command.tsx", - "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", + "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command | null;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand: providedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n const selectedCommand = providedCommand === undefined ? tool.commands[0] : providedCommand;\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands && selectedCommand) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands, selectedCommand]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/generated-command.tsx" }, @@ -26,13 +26,13 @@ }, { "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 /** 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", + "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 $schema?: string;\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 /** 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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", "type": "registry:file", "target": "components/commandly/utils/flat.ts" }, diff --git a/public/r/json-output.json b/public/r/json-output.json index f084603..4e1aa71 100644 --- a/public/r/json-output.json +++ b/public/r/json-output.json @@ -25,13 +25,13 @@ }, { "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 /** 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", + "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 $schema?: string;\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 /** 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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", "type": "registry:file", "target": "components/commandly/utils/flat.ts" }, diff --git a/public/r/tool-renderer.json b/public/r/tool-renderer.json index e79c2bf..b3b977f 100644 --- a/public/r/tool-renderer.json +++ b/public/r/tool-renderer.json @@ -14,7 +14,7 @@ "files": [ { "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n const hasCommands = tool.commands.length > 0;\n\n const visibleParameters = useMemo(() => {\n if (!hasCommands) {\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", + "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = 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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/tool-renderer.tsx" }, @@ -32,7 +32,7 @@ }, { "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 /** 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", + "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 $schema?: string;\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 /** 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" } diff --git a/public/r/ui.json b/public/r/ui.json index d3315c2..cf790ad 100644 --- a/public/r/ui.json +++ b/public/r/ui.json @@ -23,7 +23,7 @@ "files": [ { "path": "registry/commandly/generated-command.tsx", - "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", + "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command | null;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand: providedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n const selectedCommand = providedCommand === undefined ? tool.commands[0] : providedCommand;\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands && selectedCommand) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands, selectedCommand]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/generated-command.tsx" }, @@ -35,7 +35,7 @@ }, { "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n const hasCommands = tool.commands.length > 0;\n\n const visibleParameters = useMemo(() => {\n if (!hasCommands) {\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", + "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = 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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/tool-renderer.tsx" }, @@ -53,13 +53,13 @@ }, { "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 /** 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", + "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 $schema?: string;\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 /** 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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", "type": "registry:file", "target": "components/commandly/utils/flat.ts" }, diff --git a/public/specification/nested.json b/public/specification/nested.json index 5ca6261..1ab0d6a 100644 --- a/public/specification/nested.json +++ b/public/specification/nested.json @@ -1,6 +1,9 @@ { "type": "object", "properties": { + "$schema": { + "type": "string" + }, "binaryName": { "description": "Unique binary name for the tool that it can be invoked from the command line (e.g. \"httpx\").", "type": "string" From 2ace6ab0faea883fd8f4e259ad7d140c205661de Mon Sep 17 00:00:00 2001 From: divyeshio <79130336+divyeshio@users.noreply.github.com> Date: Fri, 17 Apr 2026 00:48:26 +0530 Subject: [PATCH 4/5] feat: enhance command tree and tool editor components - Refactor command tree component for improved readability and maintainability. - Add interactive switch to tool details dialog for better user control. - Update parameter list rendering logic for clarity. - Enhance documentation with updated specifications and examples. - Implement copy functionality for documentation pages. - Add tests for new features and ensure existing functionality remains intact. --- public/r/generated-command.json | 10 +- public/r/json-output.json | 8 +- public/r/tool-renderer.json | 6 +- public/r/ui.json | 12 +- public/specification/flat.json | 4 + public/specification/nested.json | 4 + .../commandly/__tests__/json-output.test.tsx | 11 + registry/commandly/tool-renderer.tsx | 3 +- registry/commandly/types/flat.ts | 2 + registry/commandly/types/nested.ts | 3 +- registry/commandly/utils/nested.ts | 2 +- src/components/docs/docs-copy-page.tsx | 296 ++++++++++++++++++ src/components/tool-editor/command-tree.tsx | 67 +++- .../dialogs/tool-details-dialog.tsx | 9 + src/components/tool-editor/parameter-list.tsx | 130 ++++---- .../tool-editor/tool-editor.context.tsx | 3 +- src/routes/docs/$componentName.tsx | 29 +- .../docs/__collection__/generated-command.mdx | 17 +- .../docs/__collection__/json-output.mdx | 4 +- .../__collection__/specification-examples.mdx | 10 +- .../__collection__/specification-nested.mdx | 24 +- .../__collection__/specification-schema.mdx | 71 +++-- .../docs/__collection__/tool-renderer.mdx | 3 +- src/routes/tools/$toolName/index.tsx | 24 +- tests/docs/docs-copy-page.test.tsx | 63 ++++ tests/tool-editor/command-tree.test.tsx | 4 +- tests/tool-editor/parameter-list.test.tsx | 18 +- tests/tool-editor/tool-editor.test.tsx | 56 +++- tests/tool-editor/tools.test.ts | 5 +- vitest.config.ts | 4 +- 30 files changed, 718 insertions(+), 184 deletions(-) create mode 100644 src/components/docs/docs-copy-page.tsx create mode 100644 tests/docs/docs-copy-page.test.tsx diff --git a/public/r/generated-command.json b/public/r/generated-command.json index a0f479f..9795924 100644 --- a/public/r/generated-command.json +++ b/public/r/generated-command.json @@ -14,31 +14,31 @@ "files": [ { "path": "registry/commandly/generated-command.tsx", - "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", + "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command | null;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand: providedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n const selectedCommand = providedCommand === undefined ? tool.commands[0] : providedCommand;\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands && selectedCommand) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands, selectedCommand]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/generated-command.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 /** 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", + "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 /** 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", + "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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\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 $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n binaryName: tool.binaryName,\n url: tool.info?.url,\n displayName: tool.displayName,\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", + "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" } diff --git a/public/r/json-output.json b/public/r/json-output.json index f084603..6efde63 100644 --- a/public/r/json-output.json +++ b/public/r/json-output.json @@ -19,25 +19,25 @@ }, { "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 /** 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", + "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 /** 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", + "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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\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 $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n binaryName: tool.binaryName,\n url: tool.info?.url,\n displayName: tool.displayName,\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", + "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" } diff --git a/public/r/tool-renderer.json b/public/r/tool-renderer.json index e79c2bf..c57da7f 100644 --- a/public/r/tool-renderer.json +++ b/public/r/tool-renderer.json @@ -14,7 +14,7 @@ "files": [ { "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n const hasCommands = tool.commands.length > 0;\n\n const visibleParameters = useMemo(() => {\n if (!hasCommands) {\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", + "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/tool-renderer.tsx" }, @@ -26,13 +26,13 @@ }, { "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 /** 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", + "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 /** 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", + "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" } diff --git a/public/r/ui.json b/public/r/ui.json index d3315c2..56a7614 100644 --- a/public/r/ui.json +++ b/public/r/ui.json @@ -23,7 +23,7 @@ "files": [ { "path": "registry/commandly/generated-command.tsx", - "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n selectedCommand = selectedCommand || tool.commands[0];\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", + "content": "import { Parameter, ParameterValue, Tool, Command } from \"@/components/commandly/types/flat\";\nimport { getCommandPath } from \"@/components/commandly/utils/flat\";\nimport { Button } from \"@/components/ui/button\";\nimport { TerminalIcon, CopyIcon, SaveIcon } from \"lucide-react\";\nimport { useCallback, useEffect, useState, useMemo } from \"react\";\nimport { toast } from \"sonner\";\n\ninterface GeneratedCommandProps {\n tool: Tool;\n selectedCommand?: Command | null;\n parameterValues: Record;\n onSaveCommand?: (command: string) => void;\n}\n\nexport function GeneratedCommand({\n tool,\n selectedCommand: providedCommand,\n parameterValues,\n onSaveCommand,\n}: GeneratedCommandProps) {\n const selectedCommand = providedCommand === undefined ? tool.commands[0] : providedCommand;\n const hasCommands = tool.commands.length > 0;\n const [generatedCommand, setGeneratedCommand] = useState(\"\");\n\n const globalParameters = useMemo(() => {\n return tool.parameters?.filter((p) => p.isGlobal) || [];\n }, [tool]);\n\n const rootParameters = useMemo(() => {\n if (hasCommands && selectedCommand) return [];\n return tool.parameters?.filter((p) => !p.commandKey && !p.isGlobal) || [];\n }, [tool, hasCommands, selectedCommand]);\n\n const currentParameters = useMemo(() => {\n if (!selectedCommand) return [];\n return tool?.parameters?.filter((p) => p.commandKey === selectedCommand?.key) || [];\n }, [tool, selectedCommand]);\n\n const generateCommand = useCallback(() => {\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\n globalParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n rootParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false) {\n parametersWithValues.push({ param, value });\n }\n });\n\n currentParameters.forEach((param) => {\n const value = parameterValues[param.key];\n if (value !== undefined && value !== \"\" && value !== false && !param.isGlobal) {\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 = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`;\n } else if (param.isRepeatable && typeof value === \"number\" && value > 0) {\n const flag = param.shortFlag || param.longFlag;\n if (flag) command += ` ${flag}`.repeat(value);\n }\n } else if (param.parameterType === \"Option\") {\n const flag = param.shortFlag || param.longFlag;\n if (flag) {\n const separator = param.keyValueSeparator ?? \" \";\n if (Array.isArray(value)) {\n const entries = value.filter((v) => v !== \"\");\n if (entries.length > 0) {\n if (param.arraySeparator) {\n command += ` ${flag}${separator}${entries.join(param.arraySeparator)}`;\n } else {\n entries.forEach((v) => {\n command += ` ${flag}${separator}${v}`;\n });\n }\n }\n } else {\n command += ` ${flag}${separator}${value}`;\n }\n }\n }\n });\n\n positionalParams.forEach(({ value }) => {\n if (!Array.isArray(value)) {\n command += ` ${value}`;\n }\n });\n\n setGeneratedCommand(command);\n }, [\n tool,\n parameterValues,\n selectedCommand,\n hasCommands,\n globalParameters,\n rootParameters,\n currentParameters,\n ]);\n\n useEffect(() => {\n generateCommand();\n }, [generateCommand]);\n\n const copyCommand = () => {\n navigator.clipboard.writeText(generatedCommand);\n toast(\"Command copied!\");\n };\n\n return (\n
\n {generatedCommand ? (\n
\n
{generatedCommand}
\n
\n \n \n Copy Command\n \n {onSaveCommand && (\n onSaveCommand(generatedCommand)}\n variant=\"outline\"\n className=\"flex-1\"\n >\n \n Save Command\n \n )}\n
\n
\n ) : (\n
\n \n

Configure parameters to generate the command.

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/generated-command.tsx" }, @@ -35,7 +35,7 @@ }, { "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 = providedCommand ?? findDefaultCommand(tool);\n const hasCommands = tool.commands.length > 0;\n\n const visibleParameters = useMemo(() => {\n if (!hasCommands) {\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", + "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 \n );\n}\n\nfunction FlagInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n
\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 \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
\n {label}\n onUpdate(vals.join(separator))}\n placeholder=\"Select options\"\n />\n
\n );\n }\n\n return (\n
\n {label}\n \n \n \n {options.find((o) => o.value === value)?.label ?? \"Select an option\"}\n \n \n \n \n \n \n \n No option found.\n \n {options.map((option) => (\n {\n onUpdate(currentValue === value ? \"\" : currentValue);\n setOpen(false);\n }}\n >\n {option.label}\n \n \n ))}\n \n \n \n \n \n
\n );\n}\n\nfunction OptionBooleanInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n onUpdate(checked.toString())}\n />\n \n
\n );\n}\n\nfunction OptionTextInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\n );\n}\n\nfunction ArgumentInput({ parameter, value, onUpdate }: ParameterRenderContext) {\n return (\n
\n \n \n {parameter.parameterType}\n {parameter.position !== undefined && ` (${parameter.position})`}\n \n \n onUpdate(e.target.value)}\n placeholder=\"Enter value\"\n />\n
\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
\n {values.map((val, index) => (\n \n
\n {renderEntry({ parameter, value: val, onUpdate: (v) => updateAt(index, v) })}\n
\n {index > 0 && (\n removeAt(index)}\n >\n \n \n )}\n
\n ))}\n \n \n Add another\n \n
\n );\n}\n\nexport function defaultComponents(): ParameterRendererEntry[] {\n return [\n { condition: (p) => p.parameterType === \"Flag\", component: (ctx) => },\n {\n condition: (p) => p.parameterType === \"Argument\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Enum\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\" && p.dataType === \"Boolean\",\n component: (ctx) => ,\n },\n {\n condition: (p) => p.parameterType === \"Option\",\n component: (ctx) => ,\n },\n ];\n}\n\ninterface ToolRendererProps {\n selectedCommand?: Command | null;\n tool: Tool;\n catalog?: ParameterRendererEntry[];\n parameterValues: Record;\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 \n
\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 \n {parameter.isRepeatable ? (\n \n ) : (\n entry.component({ parameter, value, onUpdate })\n )}\n \n );\n })\n ) : (\n

No parameters available for this command.

\n )}\n
\n
\n );\n}\n", "type": "registry:component", "target": "components/commandly/tool-renderer.tsx" }, @@ -47,25 +47,25 @@ }, { "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 /** 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", + "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 /** 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", + "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 type { Command, Parameter, Tool } 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 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 = commands.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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\n};\n", + "content": "import type { Command, Parameter, Tool } 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\nconst SCHEMA_URL = \"https://commandly.divyeshio.in/specification/flat.json\";\n\nexport const sanitizeToolJSON = (tool: Tool) => {\n const parameters = tool.parameters.map(({ metadata: _metadata, ...param }) => param);\n\n return {\n $schema: SCHEMA_URL,\n ...tool,\n parameters,\n };\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\nexport const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {\n return {\n key: \"\",\n name: \"\",\n commandKey: isGlobal ? undefined : commandKey,\n parameterType: \"Option\",\n dataType: \"String\",\n ...(isGlobal ? { isGlobal: true } : {}),\n longFlag: \"\",\n };\n};\n\nconst isEmpty = (value: object | null | undefined): boolean => {\n if (value == null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return Object.keys(value).length === 0;\n};\n\nconst cleanParameter = (param: Parameter): Parameter => {\n const cleaned = { ...param };\n\n if (!cleaned.enum || cleaned.enum.values.length === 0) delete cleaned.enum;\n if (isEmpty(cleaned.validations)) delete cleaned.validations;\n if (isEmpty(cleaned.dependencies)) delete cleaned.dependencies;\n\n if (cleaned.metadata) {\n const meta = { ...cleaned.metadata };\n if (isEmpty(meta.tags)) delete meta.tags;\n if (isEmpty(meta)) {\n delete cleaned.metadata;\n } else {\n cleaned.metadata = meta;\n }\n }\n\n return cleaned;\n};\n\nexport const cleanupTool = (tool: Tool): Tool => {\n const cleaned = { ...tool };\n\n if (isEmpty(cleaned.exclusionGroups)) delete cleaned.exclusionGroups;\n if (isEmpty(cleaned.metadata)) delete cleaned.metadata;\n\n cleaned.parameters = cleaned.parameters.map(cleanParameter);\n\n return cleaned;\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 $schema: \"https://commandly.divyeshio.in/specification/nested.json\",\n binaryName: tool.binaryName,\n url: tool.info?.url,\n displayName: tool.displayName,\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", + "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" } diff --git a/public/specification/flat.json b/public/specification/flat.json index 853c9c8..d2c0981 100644 --- a/public/specification/flat.json +++ b/public/specification/flat.json @@ -9,6 +9,10 @@ "description": "Human-readable display name for the tool (e.g. \"HTTPx\").", "type": "string" }, + "interactive": { + "description": "Whether the root tool invocation opens an interactive session or prompt.", + "type": "boolean" + }, "info": { "description": "General information about the tool such as description, version, and URL.", "$ref": "#/definitions/ToolInfo" diff --git a/public/specification/nested.json b/public/specification/nested.json index 5ca6261..7e2f9a0 100644 --- a/public/specification/nested.json +++ b/public/specification/nested.json @@ -9,6 +9,10 @@ "description": "Human-readable display name for the tool (e.g. \"HTTPx\").", "type": "string" }, + "interactive": { + "description": "Whether the root tool invocation opens an interactive session or prompt.", + "type": "boolean" + }, "info": { "description": "General information about the tool such as description, version, and URL.", "$ref": "#/definitions/ToolInfo" diff --git a/registry/commandly/__tests__/json-output.test.tsx b/registry/commandly/__tests__/json-output.test.tsx index 4172f0e..90b97c1 100644 --- a/registry/commandly/__tests__/json-output.test.tsx +++ b/registry/commandly/__tests__/json-output.test.tsx @@ -122,4 +122,15 @@ describe("convertToNestedStructure", () => { expect(result.exclusionGroups).toHaveLength(1); expect(result.exclusionGroups![0].parameters).toContain("--help"); }); + + it("includes root interactive when enabled", () => { + const tool: Tool = { + ...defaultTool(), + interactive: true, + }; + + const result = convertToNestedStructure(tool); + + expect(result.interactive).toBe(true); + }); }); diff --git a/registry/commandly/tool-renderer.tsx b/registry/commandly/tool-renderer.tsx index abb4d61..ae04b57 100644 --- a/registry/commandly/tool-renderer.tsx +++ b/registry/commandly/tool-renderer.tsx @@ -371,7 +371,8 @@ export function ToolRenderer({ parameterValues, updateParameterValue, }: ToolRendererProps) { - const selectedCommand = providedCommand === undefined ? findDefaultCommand(tool) : providedCommand; + const selectedCommand = + providedCommand === undefined ? findDefaultCommand(tool) : providedCommand; const hasCommands = tool.commands.length > 0; const visibleParameters = useMemo(() => { diff --git a/registry/commandly/types/flat.ts b/registry/commandly/types/flat.ts index 810c39b..54bd121 100644 --- a/registry/commandly/types/flat.ts +++ b/registry/commandly/types/flat.ts @@ -155,6 +155,8 @@ export interface Tool { binaryName: string; /** Human-readable display name for the tool (e.g. "HTTPx"). */ displayName: string; + /** Whether the root tool invocation opens an interactive session or prompt. */ + interactive?: boolean; /** General information about the tool such as description, version, and URL. */ info?: ToolInfo; /** List of all commands and subcommands defined for this tool. */ diff --git a/registry/commandly/types/nested.ts b/registry/commandly/types/nested.ts index 3997a87..a64b351 100644 --- a/registry/commandly/types/nested.ts +++ b/registry/commandly/types/nested.ts @@ -93,11 +93,12 @@ export interface NestedExclusionGroup { } export interface NestedTool { - $schema?: string; /** Unique binary name for the tool that it can be invoked from the command line (e.g. "httpx"). */ binaryName: string; /** Human-readable display name for the tool (e.g. "HTTPx"). */ displayName: string; + /** Whether the root tool invocation opens an interactive session or prompt. */ + interactive?: boolean; /** General information about the tool such as description, version, and URL. */ info?: ToolInfo; /** The homepage or documentation URL for the tool. */ diff --git a/registry/commandly/utils/nested.ts b/registry/commandly/utils/nested.ts index 9b86a3d..4b150f7 100644 --- a/registry/commandly/utils/nested.ts +++ b/registry/commandly/utils/nested.ts @@ -77,10 +77,10 @@ export const convertToNestedStructure = (tool: Tool): NestedTool => { tool.commands.length === 0 ? tool.parameters.filter((p) => !p.commandKey && !p.isGlobal) : []; return { - $schema: "https://commandly.divyeshio.in/specification/nested.json", binaryName: tool.binaryName, url: tool.info?.url, displayName: tool.displayName, + interactive: tool.interactive, info: tool.info, metadata: tool.metadata, rootParameters: rootParameters.map(convertParameter), diff --git a/src/components/docs/docs-copy-page.tsx b/src/components/docs/docs-copy-page.tsx new file mode 100644 index 0000000..257088c --- /dev/null +++ b/src/components/docs/docs-copy-page.tsx @@ -0,0 +1,296 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Separator } from "@/components/ui/separator"; +import { CheckIcon, ChevronDownIcon, CopyIcon } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; + +function getPromptUrl(baseURL: string, url: string) { + return `${baseURL}?q=${encodeURIComponent( + `I’m looking at this Commandly documentation page: ${url}.\nHelp me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it.`, + )}`; +} + +interface DocsCopyPageProps { + page: string; + sourceUrl?: string; +} + +function getMenuItems(url: string, sourceUrl?: string) { + return { + source: () => + sourceUrl ? ( + + + + + View Markdown + + ) : null, + v0: () => ( + + + + + Open in v0 + + ), + chatgpt: () => ( + + + + + Open in ChatGPT + + ), + claude: () => ( + + + + + Open in Claude + + ), + scira: () => ( + + + + + + + + + + + Open in Scira + + ), + }; +} + +export function DocsCopyPage({ page, sourceUrl }: DocsCopyPageProps) { + const [currentUrl, setCurrentUrl] = useState(""); + const [isCopied, setIsCopied] = useState(false); + + useEffect(() => { + setCurrentUrl(window.location.href); + }, [page]); + + useEffect(() => { + if (!isCopied) { + return; + } + + const timer = window.setTimeout(() => setIsCopied(false), 2000); + return () => window.clearTimeout(timer); + }, [isCopied]); + + const copyPage = async () => { + let content = page; + + if (sourceUrl) { + try { + const response = await fetch(sourceUrl); + if (response.ok) { + content = await response.text(); + } + } catch { + // Fall back to the preloaded page content when the source URL is unavailable. + } + } + + await navigator.clipboard.writeText(content); + setIsCopied(true); + }; + const menuItems = useMemo(() => getMenuItems(currentUrl, sourceUrl), [currentUrl, sourceUrl]); + + const trigger = ( + + ); + + return ( + +
+ + + + + {trigger} + + + {Object.entries(menuItems).map(([key, item]) => { + const content = item(); + if (!content) { + return null; + } + + return ( + + {content} + + ); + })} + + + + + {trigger} + + + {Object.entries(menuItems).map(([key, item]) => { + const content = item(); + if (!content) { + return null; + } + + return ( + + ); + })} + +
+
+ ); +} diff --git a/src/components/tool-editor/command-tree.tsx b/src/components/tool-editor/command-tree.tsx index b273c0f..64b3bd7 100644 --- a/src/components/tool-editor/command-tree.tsx +++ b/src/components/tool-editor/command-tree.tsx @@ -12,7 +12,14 @@ import { SortableOverlay, } from "@/components/ui/sortable"; import { cn } from "@/lib/utils"; -import { ChevronRightIcon, Edit2Icon, GripVerticalIcon, PlusIcon, TerminalIcon, Trash2Icon } from "lucide-react"; +import { + ChevronRightIcon, + Edit2Icon, + GripVerticalIcon, + PlusIcon, + TerminalIcon, + Trash2Icon, +} from "lucide-react"; import { useState } from "react"; const ROOT_ID = "__root__"; @@ -42,15 +49,30 @@ function CommandActions({ )} {onEdit && ( - )} - {onDelete && ( - )} @@ -141,7 +163,10 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { {command.name} {paramCount > 0 && ( - + {paramCount} )} @@ -174,12 +199,18 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { value={subcommands} getItemValue={(cmd) => cmd.key} onValueChange={(newOrder) => - reorderCommands(newOrder.map((c) => c.key), command.key) + reorderCommands( + newOrder.map((c) => c.key), + command.key, + ) } > {subcommands.map((subcmd) => ( - + {renderCommand(subcmd)} ))} @@ -207,7 +238,7 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { "group w-full px-2 py-1.5", isChatOpen && isContextSelected && "ring-1 ring-primary", )} - fileIcon={} + fileIcon={} actions={actions} onClick={(e) => handleCommandClick(command, e)} > @@ -225,12 +256,18 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { {tool.binaryName} {rootParamCount > 0 && ( - + {rootParamCount} )} {globalParamCount > 0 && ( - + {globalParamCount} )} @@ -259,12 +296,18 @@ export function CommandTree({ isChatOpen = false }: { isChatOpen?: boolean }) { value={rootCommands} getItemValue={(cmd) => cmd.key} onValueChange={(newOrder) => - reorderCommands(newOrder.map((c) => c.key), undefined) + reorderCommands( + newOrder.map((c) => c.key), + undefined, + ) } > {rootCommands.map((command) => ( - + {renderCommand(command)} ))} diff --git a/src/components/tool-editor/dialogs/tool-details-dialog.tsx b/src/components/tool-editor/dialogs/tool-details-dialog.tsx index 8eaa556..c3c18a3 100644 --- a/src/components/tool-editor/dialogs/tool-details-dialog.tsx +++ b/src/components/tool-editor/dialogs/tool-details-dialog.tsx @@ -11,6 +11,7 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { MultiSelect } from "@/components/ui/multi-select"; +import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { SupportedToolInputType, SupportedToolOutputType } from "@/lib/types"; import { SettingsIcon } from "lucide-react"; @@ -77,6 +78,14 @@ export function ToolDetailsDialog() { onChange={(e) => updateTool({ info: { ...tool.info, version: e.target.value } })} />
+
+ updateTool({ interactive: checked })} + /> + +
diff --git a/src/components/tool-editor/parameter-list.tsx b/src/components/tool-editor/parameter-list.tsx index 7b48c0c..35067fd 100644 --- a/src/components/tool-editor/parameter-list.tsx +++ b/src/components/tool-editor/parameter-list.tsx @@ -74,7 +74,11 @@ export function ParameterList({ ? getExclusionGroupsForCommand(selectedCommand.key) : []; - const parameters = isGlobal ? globalParameters : selectedCommand ? commandParameters : rootParameters; + const parameters = isGlobal + ? globalParameters + : selectedCommand + ? commandParameters + : rootParameters; const removedParameters = isGlobal ? pendingChanges @@ -123,11 +127,7 @@ export function ParameterList({ {title} ({parameters.length})
-
- {parameter.isRequired && ( - - required - - )} - - {parameter.parameterType} - - - {parameter.dataType} - - {isGlobal && ( - - global - - )} - {paramGroups.map((group) => ( - - - {group.name} - - ))} - {isAdded && ( - - Added - - )} - {isUpdated && ( - - Updated - - )} -
-
+
+ {parameter.isRequired && ( + + required + + )} + + {parameter.parameterType} + + + {parameter.dataType} + + {isGlobal && ( + + global + + )} + {paramGroups.map((group) => ( + + + {group.name} + + ))} + {isAdded && ( + + Added + + )} + {isUpdated && ( + + Updated + + )} +
+
- ); - })} + ); + })} {({ value }) => { diff --git a/src/components/tool-editor/tool-editor.context.tsx b/src/components/tool-editor/tool-editor.context.tsx index 84ab13d..4dc4c69 100644 --- a/src/components/tool-editor/tool-editor.context.tsx +++ b/src/components/tool-editor/tool-editor.context.tsx @@ -417,8 +417,7 @@ export function ToolBuilderProvider({ tool, children, initialState }: ToolBuilde getParametersForCommand: (commandKey: string) => state.tool.parameters.filter((p) => !p.isGlobal && p.commandKey === commandKey), - getRootParameters: () => - state.tool.parameters.filter((p) => !p.commandKey && !p.isGlobal), + getRootParameters: () => state.tool.parameters.filter((p) => !p.commandKey && !p.isGlobal), getGlobalParameters: () => state.tool.parameters.filter((p) => p.isGlobal), diff --git a/src/routes/docs/$componentName.tsx b/src/routes/docs/$componentName.tsx index aec2cdf..0f4ed3f 100644 --- a/src/routes/docs/$componentName.tsx +++ b/src/routes/docs/$componentName.tsx @@ -1,12 +1,19 @@ +import { DocsCopyPage } from "@/components/docs/docs-copy-page"; import { mdxComponents } from "@/components/docs/mdx-components"; -import { fetchDocComponent } from "@/lib/api/docs.api"; import { createFileRoute } from "@tanstack/react-router"; import { ComponentType, lazy } from "react"; +const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/divyeshio/commandly/refs/heads/main"; + const componentCache = new Map>(); +const rawCache = new Map(); const docModules = import.meta.glob<{ default: ComponentType<{ components?: object }>; }>("./__collection__/*.mdx"); +const rawDocModules = import.meta.glob("./__collection__/*.mdx", { + query: "?raw", + import: "default", +}); const MissingDocumentation: ComponentType<{ components?: object }> = () => { return
Documentation not found
; @@ -15,8 +22,19 @@ const MissingDocumentation: ComponentType<{ components?: object }> = () => { export const Route = createFileRoute("/docs/$componentName")({ component: RouteComponent, loader: async ({ params: { componentName } }) => { - const { component } = await fetchDocComponent(componentName); + const moduleLoader = docModules[`./__collection__/${componentName}.mdx`]; + const rawLoader = rawDocModules[`./__collection__/${componentName}.mdx`]; + + if (!moduleLoader || !rawLoader) { + throw new Error(`Documentation not found for "${componentName}"`); + } + + const module = await moduleLoader(); + const raw = await rawLoader(); + const component = module.default; + componentCache.set(componentName, component); + rawCache.set(componentName, raw); return { componentName }; }, preload: true, @@ -25,6 +43,7 @@ export const Route = createFileRoute("/docs/$componentName")({ function RouteComponent() { const { componentName } = Route.useLoaderData(); let Component = componentCache.get(componentName); + const raw = rawCache.get(componentName) ?? ""; if (!Component) { Component = lazy(async () => { @@ -40,6 +59,12 @@ function RouteComponent() { return (
+
+ +
); diff --git a/src/routes/docs/__collection__/generated-command.mdx b/src/routes/docs/__collection__/generated-command.mdx index e29c712..f109c26 100644 --- a/src/routes/docs/__collection__/generated-command.mdx +++ b/src/routes/docs/__collection__/generated-command.mdx @@ -47,9 +47,9 @@ import { GeneratedCommand } from "@/components/commandly/generated-command"; ```tsx const tool: Tool = { - name: "curl", + binaryName: "curl", displayName: "curl", - commands: [{ key: "curl", name: "curl", isDefault: true }], + commands: [{ key: "curl", name: "curl" }], parameters: [ { key: "url", @@ -90,12 +90,12 @@ const [values, setValues] = useState({}) ### GeneratedCommand -| Prop | Type | Default | Description | -| ----------------- | -------------------------------- | ------------------ | ------------------------------------------------------------ | -| `tool` | `Tool` | — | The tool definition including name, commands, and parameters | -| `selectedCommand` | `Command` | `tool.commands[0]` | The currently selected command to generate the string for | -| `parameterValues` | `Record` | — | Map of parameter key to current value | -| `onSaveCommand` | `(command: string) => void` | — | Optional callback fired when the save button is clicked | +| Prop | Type | Default | Description | +| ----------------- | -------------------------------- | ------------------ | ----------------------------------------------------------------------- | +| `tool` | `Tool` | — | The tool definition including `binaryName`, commands, and parameters | +| `selectedCommand` | `Command \| null` | `tool.commands[0]` | The currently selected command, or `null` to render the root invocation | +| `parameterValues` | `Record` | — | Map of parameter key to current value | +| `onSaveCommand` | `(command: string) => void` | — | Optional callback fired when the save button is clicked | ## Notes @@ -104,3 +104,4 @@ const [values, setValues] = useState({}) - Option parameters are joined using `keyValueSeparator` (defaults to a space) - Argument parameters are appended in order of their `position` field - Global parameters are included regardless of the selected command +- Passing `selectedCommand={null}` includes root-level parameters and skips command path segments diff --git a/src/routes/docs/__collection__/json-output.mdx b/src/routes/docs/__collection__/json-output.mdx index fad987d..21ef99d 100644 --- a/src/routes/docs/__collection__/json-output.mdx +++ b/src/routes/docs/__collection__/json-output.mdx @@ -46,9 +46,9 @@ import { JsonOutput } from "@/components/commandly/json-output"; ```tsx const tool: Tool = { - name: "curl", + binaryName: "curl", displayName: "curl", - commands: [{ key: "curl", name: "curl", isDefault: true }], + commands: [{ key: "curl", name: "curl" }], parameters: [ { key: "url", diff --git a/src/routes/docs/__collection__/specification-examples.mdx b/src/routes/docs/__collection__/specification-examples.mdx index 098b283..e67be84 100644 --- a/src/routes/docs/__collection__/specification-examples.mdx +++ b/src/routes/docs/__collection__/specification-examples.mdx @@ -7,7 +7,7 @@ A simple tool with one command, one flag, and one option. ```json { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "curl", + "binaryName": "curl", "displayName": "Curl", "info": { "description": "curl is a command line tool for transferring data with URLs." @@ -17,7 +17,6 @@ A simple tool with one command, one flag, and one option. "key": "curl", "name": "curl", "description": "Run curl to download files.", - "isDefault": true, "sortOrder": 1 } ], @@ -67,17 +66,17 @@ The same tool expressed using the nested format. Parameters are embedded inside ```json { "$schema": "https://commandly.divyeshio.in/specification/nested.json", - "name": "curl", + "binaryName": "curl", "displayName": "Curl", "info": { "description": "curl is a command line tool for transferring data with URLs." }, + "rootParameters": [], "globalParameters": [], "commands": [ { "name": "curl", "description": "Run curl to download files.", - "isDefault": true, "sortOrder": 1, "subcommands": [], "parameters": [ @@ -122,13 +121,12 @@ A tool where parameters belong to different commands via `commandKey`, and one c ```json { "$schema": "https://commandly.divyeshio.in/specification/flat.json", - "name": "mytool", + "binaryName": "mytool", "displayName": "My Tool", "commands": [ { "key": "mytool", "name": "mytool", - "isDefault": true, "sortOrder": 1 }, { diff --git a/src/routes/docs/__collection__/specification-nested.mdx b/src/routes/docs/__collection__/specification-nested.mdx index a93fe8c..9de0050 100644 --- a/src/routes/docs/__collection__/specification-nested.mdx +++ b/src/routes/docs/__collection__/specification-nested.mdx @@ -8,10 +8,12 @@ Unlike the [Flat Schema](/docs/specification-schema), there are no `key` or `com | Field | Type | Required | Description | | ------------------ | ------------------------------------- | -------- | ------------------------------------------------------------------------------------ | -| `name` | string | ✓ | Unique identifier for the tool. | +| `binaryName` | string | ✓ | Unique CLI binary name for the tool. | | `displayName` | string | ✓ | Human-readable display name. | +| `interactive` | boolean | — | If `true`, invoking the root tool opens an interactive session or prompt. | | `info` | ToolInfo Object | — | Metadata about the tool. See [ToolInfo](/docs/specification-schema#toolinfo-object). | | `url` | string | — | URL to the tool's homepage or repository. | +| `rootParameters` | NestedParameter Object[] | ✓ | Parameters available on the root invocation when no command is selected. | | `globalParameters` | NestedParameter Object[] | ✓ | Parameters that apply across all commands. | | `commands` | NestedCommand Object[] | ✓ | List of commands with embedded parameters and subcommands. | | `exclusionGroups` | NestedExclusionGroup Object[] \| null | — | Groups of mutually exclusive or required parameters. | @@ -20,15 +22,15 @@ Unlike the [Flat Schema](/docs/specification-schema), there are no `key` or `com ## NestedCommand Object -| Field | Type | Required | Description | -| ------------- | ------------------------ | -------- | ---------------------------------------------------------------------------------------- | -| `name` | string | ✓ | Command name as it appears in the CLI invocation. | -| `description` | string | — | Human-readable description of the command. | -| `isDefault` | boolean | — | If `true`, this command is invoked when no subcommand is specified. Defaults to `false`. | -| `sortOrder` | number | — | Display sort order. Defaults to `0`. | -| `parameters` | NestedParameter Object[] | ✓ | Parameters belonging to this command. | -| `subcommands` | NestedCommand Object[] | ✓ | Nested subcommands. Use an empty array if none. | -| `interactive` | boolean | — | If `true`, the command requires interactive user input. | +| Field | Type | Required | Description | +| ----------------- | ----------------------------- | -------- | ------------------------------------------------------- | +| `name` | string | ✓ | Command name as it appears in the CLI invocation. | +| `description` | string | — | Human-readable description of the command. | +| `interactive` | boolean | — | If `true`, the command requires interactive user input. | +| `sortOrder` | number | — | Display sort order. Defaults to `0`. | +| `parameters` | NestedParameter Object[] | ✓ | Parameters belonging to this command. | +| `subcommands` | NestedCommand Object[] | ✓ | Nested subcommands. Use an empty array if none. | +| `exclusionGroups` | NestedExclusionGroup Object[] | — | Exclusion groups scoped to this command. | ## NestedParameter Object @@ -46,7 +48,7 @@ Identical to the flat [Parameter Object](/docs/specification-schema#parameter-ob | `group` | string | — | Display group label. | | `shortFlag` | string | — | Short flag form (e.g. `-o`). | | `longFlag` | string | — | Long flag form (e.g. `--output`). | -| `position` | number | — | Positional index, starting from `1`. Required for `Argument` type. | +| `position` | number | — | Zero-based positional index. Required for `Argument` type. | | `sortOrder` | number | — | Display sort order. | | `arraySeparator` | string | — | Separator for repeatable parameters joined into a single token. | | `keyValueSeparator` | string | — | Character between the flag and its value. Defaults to a space. | diff --git a/src/routes/docs/__collection__/specification-schema.mdx b/src/routes/docs/__collection__/specification-schema.mdx index 67e3069..4524656 100644 --- a/src/routes/docs/__collection__/specification-schema.mdx +++ b/src/routes/docs/__collection__/specification-schema.mdx @@ -8,12 +8,12 @@ The root object of a Commandly flat description. | Field | Type | Required | Description | | ----------------- | ----------------------- | -------- | -------------------------------------------------------------------------- | -| `name` | string | ✓ | Unique identifier for the tool. Must match the filename (without `.json`). | +| `binaryName` | string | ✓ | Unique CLI binary name for the tool, such as `httpx` or `curl`. | | `displayName` | string | ✓ | Human-readable display name. | -| `info` | ToolInfo Object | — | Metadata about the tool. | -| `url` | string | — | URL to the tool's homepage or repository. | -| `commands` | Command Object[] | ✓ | List of commands. Must contain at least one entry. | -| `parameters` | Parameter Object[] | ✓ | Flat list of all parameters across all commands. | +| `interactive` | boolean | — | If `true`, invoking the root tool opens an interactive session or prompt. | +| `info` | ToolInfo Object | — | Metadata about the tool, including the homepage URL. | +| `commands` | Command Object[] | ✓ | List of commands and subcommands. Can be empty for tools without commands. | +| `parameters` | Parameter Object[] | ✓ | Flat list of all parameters across root, command, and global scope. | | `exclusionGroups` | ExclusionGroup Object[] | — | Groups of mutually exclusive or required parameters. | | `metadata` | ToolMetadata Object | — | Custom metadata. | @@ -27,40 +27,39 @@ The root object of a Commandly flat description. ## Command Object -| Field | Type | Required | Description | -| ------------------ | ------- | -------- | ---------------------------------------------------------------------------------------- | -| `key` | string | ✓ | Unique identifier. Referenced by parameters via `commandKey`. | -| `name` | string | ✓ | Command name as it appears in the CLI invocation. | -| `parentCommandKey` | string | — | Key of the parent command. Omit for root-level commands. | -| `description` | string | — | Human-readable description of the command. | -| `isDefault` | boolean | — | If `true`, this command is invoked when no subcommand is specified. Defaults to `false`. | -| `sortOrder` | number | — | Display sort order. | -| `interactive` | boolean | — | If `true`, the command requires interactive user input at runtime. Defaults to `false`. | +| Field | Type | Required | Description | +| ------------------ | ------- | -------- | --------------------------------------------------------------------------------------- | +| `key` | string | ✓ | Unique identifier. Referenced by parameters via `commandKey`. | +| `name` | string | ✓ | Command name as it appears in the CLI invocation. | +| `parentCommandKey` | string | — | Key of the parent command. Omit for root-level commands. | +| `description` | string | — | Human-readable description of the command. | +| `interactive` | boolean | — | If `true`, the command requires interactive user input at runtime. Defaults to `false`. | +| `sortOrder` | number | — | Display sort order. | ## Parameter Object -| Field | Type | Required | Description | -| ------------------- | ---------------------------- | -------- | ----------------------------------------------------------------------------- | -| `key` | string | ✓ | Unique identifier for the parameter. | -| `name` | string | ✓ | Human-readable parameter name. | -| `parameterType` | ParameterType | ✓ | One of `Flag`, `Option`, or `Argument`. | -| `dataType` | ParameterDataType | ✓ | One of `String`, `Number`, `Boolean`, or `Enum`. | -| `isRequired` | boolean | — | Whether the parameter must be provided. | -| `isRepeatable` | boolean | — | Whether the parameter can appear more than once. | -| `isGlobal` | boolean | — | If `true`, the parameter applies to all commands regardless of `commandKey`. | -| `commandKey` | string | — | Key of the command this parameter belongs to. Omit for global parameters. | -| `description` | string | — | Human-readable description. | -| `group` | string | — | Display group label for UI grouping. | -| `shortFlag` | string | — | Short flag form (e.g. `-o`). Used for `Flag` and `Option` types. | -| `longFlag` | string | — | Long flag form (e.g. `--output`). Used for `Flag` and `Option` types. | -| `position` | number | — | Positional index, starting from `1`. Required for `Argument` type parameters. | -| `sortOrder` | number | — | Display sort order. | -| `arraySeparator` | string | — | Separator used when a repeatable parameter is joined into a single token. | -| `keyValueSeparator` | string | — | Character between the flag and its value. Defaults to a space. | -| `enum` | ParameterEnumValues Object | — | Required when `dataType` is `Enum`. | -| `validations` | ParameterValidation Object[] | — | Validation rules applied to the parameter value. | -| `dependencies` | ParameterDependency Object[] | — | Conditional relationships with other parameters. | -| `metadata` | ParameterMetadata Object | — | Custom metadata. | +| Field | Type | Required | Description | +| ------------------- | ---------------------------- | -------- | ---------------------------------------------------------------------------------------- | +| `key` | string | ✓ | Unique identifier for the parameter. | +| `name` | string | ✓ | Human-readable parameter name. | +| `parameterType` | ParameterType | ✓ | One of `Flag`, `Option`, or `Argument`. | +| `dataType` | ParameterDataType | ✓ | One of `String`, `Number`, `Boolean`, or `Enum`. | +| `isRequired` | boolean | — | Whether the parameter must be provided. | +| `isRepeatable` | boolean | — | Whether the parameter can appear more than once. | +| `isGlobal` | boolean | — | If `true`, the parameter applies to all commands regardless of `commandKey`. | +| `commandKey` | string | — | Key of the command this parameter belongs to. Omit for root-level and global parameters. | +| `description` | string | — | Human-readable description. | +| `group` | string | — | Display group label for UI grouping. | +| `shortFlag` | string | — | Short flag form (e.g. `-o`). Used for `Flag` and `Option` types. | +| `longFlag` | string | — | Long flag form (e.g. `--output`). Used for `Flag` and `Option` types. | +| `position` | number | — | Zero-based positional index. Required for `Argument` type parameters. | +| `sortOrder` | number | — | Display sort order. | +| `arraySeparator` | string | — | Separator used when a repeatable parameter is joined into a single token. | +| `keyValueSeparator` | string | — | Character between the flag and its value. Defaults to a space. | +| `enum` | ParameterEnumValues Object | — | Required when `dataType` is `Enum`. | +| `validations` | ParameterValidation Object[] | — | Validation rules applied to the parameter value. | +| `dependencies` | ParameterDependency Object[] | — | Conditional relationships with other parameters. | +| `metadata` | ParameterMetadata Object | — | Custom metadata. | ## ParameterType diff --git a/src/routes/docs/__collection__/tool-renderer.mdx b/src/routes/docs/__collection__/tool-renderer.mdx index f741974..0f3cc81 100644 --- a/src/routes/docs/__collection__/tool-renderer.mdx +++ b/src/routes/docs/__collection__/tool-renderer.mdx @@ -115,5 +115,6 @@ const customCatalog: ParameterRendererEntry[] = [ - The default catalog renders `Flag` → checkbox, `Option` → text/number/select input, `Argument` → text input - Global parameters are shown regardless of the selected command -- If no command is selected, the component auto-detects the default command by `isDefault` flag, then by name match, then falls back to the first command +- If `selectedCommand` is omitted, the component first looks for a command whose `name` matches `tool.binaryName`, then falls back to the first command +- If `selectedCommand` is explicitly `null`, the component renders root-level parameters plus any global parameters - Provide a custom `catalog` to render domain-specific parameter types differently diff --git a/src/routes/tools/$toolName/index.tsx b/src/routes/tools/$toolName/index.tsx index 2269e20..415cc93 100644 --- a/src/routes/tools/$toolName/index.tsx +++ b/src/routes/tools/$toolName/index.tsx @@ -5,7 +5,13 @@ import { slugify } from "@/components/commandly/utils/flat"; import { SavedCommandsDialog } from "@/components/tool-editor/dialogs/saved-commands-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Command, CommandGroup, CommandItem, CommandList, CommandSeparator } from "@/components/ui/command"; +import { + Command, + CommandGroup, + CommandItem, + CommandList, + CommandSeparator, +} from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; @@ -18,7 +24,14 @@ import { import { SavedCommand } from "@/lib/types"; import { cn, defaultTool } from "@/lib/utils"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { CheckIcon, ChevronsUpDownIcon, Edit2Icon, InfoIcon, SaveIcon, TerminalIcon } from "lucide-react"; +import { + CheckIcon, + ChevronsUpDownIcon, + Edit2Icon, + InfoIcon, + SaveIcon, + TerminalIcon, +} from "lucide-react"; import { useQueryState } from "nuqs"; import { useState } from "react"; import { toast } from "sonner"; @@ -65,7 +78,8 @@ function RouteComponent() { }); const [open, setOpen] = useState(false); const [savedCommandsOpen, setSavedCommandsOpen] = useState(false); - const hasUncategorizedParams = tool?.parameters.some((p) => !p.commandKey && !p.isGlobal) ?? false; + const hasUncategorizedParams = + tool?.parameters.some((p) => !p.commandKey && !p.isGlobal) ?? false; const getCommandDepth = (key: string, depth = 0): number => { const cmd = tool?.commands.find((c) => c.key === key); @@ -240,7 +254,9 @@ function RouteComponent() { diff --git a/tests/docs/docs-copy-page.test.tsx b/tests/docs/docs-copy-page.test.tsx new file mode 100644 index 0000000..7797c1e --- /dev/null +++ b/tests/docs/docs-copy-page.test.tsx @@ -0,0 +1,63 @@ +import { DocsCopyPage } from "@/components/docs/docs-copy-page"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +describe("DocsCopyPage", () => { + const writeText = vi.fn().mockResolvedValue(undefined); + const fetchMock = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + Object.defineProperty(window, "location", { + value: { href: "https://commandly.divyeshio.in/docs/tool-renderer" }, + writable: true, + }); + Object.defineProperty(navigator, "clipboard", { + value: { writeText }, + configurable: true, + }); + Object.defineProperty(globalThis, "fetch", { + value: fetchMock, + configurable: true, + writable: true, + }); + }); + + it("copies the shipped page content from the source URL", async () => { + fetchMock.mockResolvedValue({ + ok: true, + text: vi.fn().mockResolvedValue("# Fetched\n\nLive docs"), + }); + + render( + , + ); + + fireEvent.click(screen.getByRole("button", { name: /copy page/i })); + + await vi.waitFor(() => { + expect(fetchMock).toHaveBeenCalledWith( + "https://raw.githubusercontent.com/divyeshio/commandly/refs/heads/main/src/routes/docs/__collection__/tool-renderer.mdx", + ); + expect(writeText).toHaveBeenCalledWith("# Fetched\n\nLive docs"); + }); + }); + + it("renders the secondary copy actions menu", () => { + render( + , + ); + + fireEvent.click(screen.getAllByRole("button", { name: /open copy actions/i })[1]); + + expect(screen.getByText("View Markdown")).toBeInTheDocument(); + expect(screen.getByText("Open in ChatGPT")).toBeInTheDocument(); + expect(screen.getByText("Open in Claude")).toBeInTheDocument(); + }); +}); diff --git a/tests/tool-editor/command-tree.test.tsx b/tests/tool-editor/command-tree.test.tsx index 3940b0f..28b3941 100644 --- a/tests/tool-editor/command-tree.test.tsx +++ b/tests/tool-editor/command-tree.test.tsx @@ -770,9 +770,7 @@ describe("CommandTree", () => { capturedCtx.reorderCommands(reversedKeys, "my-cli-tool"); }); - const updated = capturedCtx.tool.commands.filter( - (c) => c.parentCommandKey === "my-cli-tool", - ); + const updated = capturedCtx.tool.commands.filter((c) => c.parentCommandKey === "my-cli-tool"); const first = updated.find((c) => c.sortOrder === 0); expect(first?.key).toBe(reversedKeys[0]); }); diff --git a/tests/tool-editor/parameter-list.test.tsx b/tests/tool-editor/parameter-list.test.tsx index c7b8a98..acf90e8 100644 --- a/tests/tool-editor/parameter-list.test.tsx +++ b/tests/tool-editor/parameter-list.test.tsx @@ -422,9 +422,21 @@ describe("ParameterList - Rendering & Structure", () => { }); it("reorderParameters updates sortOrder in context", () => { - const p1 = createTestParameter({ key: "p1", name: "param-one", commandKey: "test-command-key" }); - const p2 = createTestParameter({ key: "p2", name: "param-two", commandKey: "test-command-key" }); - const p3 = createTestParameter({ key: "p3", name: "param-three", commandKey: "test-command-key" }); + const p1 = createTestParameter({ + key: "p1", + name: "param-one", + commandKey: "test-command-key", + }); + const p2 = createTestParameter({ + key: "p2", + name: "param-two", + commandKey: "test-command-key", + }); + const p3 = createTestParameter({ + key: "p3", + name: "param-three", + commandKey: "test-command-key", + }); const state = baseTestState(); state.tool = { ...state.tool!, parameters: [p1, p2, p3] }; renderWithProvider(, state); diff --git a/tests/tool-editor/tool-editor.test.tsx b/tests/tool-editor/tool-editor.test.tsx index 58c1ce4..ce62ed5 100644 --- a/tests/tool-editor/tool-editor.test.tsx +++ b/tests/tool-editor/tool-editor.test.tsx @@ -1,10 +1,36 @@ -import ToolEditor from "@/components/tool-editor/tool-editor"; import { Tool } from "@/components/commandly/types/flat"; +import { ToolDetailsDialog } from "@/components/tool-editor/dialogs/tool-details-dialog"; +import ToolEditor from "@/components/tool-editor/tool-editor"; +import { + ToolBuilderProvider, + ToolBuilderState, + useToolBuilder, +} from "@/components/tool-editor/tool-editor.context"; import { defaultTool } from "@/lib/utils"; -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { withNuqsTestingAdapter, type OnUrlUpdateFunction } from "nuqs/adapters/testing"; +import { ReactNode } from "react"; import { vi } from "vitest"; +let capturedCtx: ReturnType; + +function ContextCapture() { + capturedCtx = useToolBuilder(); + return null; +} + +function renderWithProvider(ui: ReactNode, initialState: Partial) { + return render( + + {ui} + + , + ); +} + describe("ToolEditor", () => { it("renders tool name and displayName", () => { const onUrlUpdate = vi.fn(); @@ -20,7 +46,11 @@ describe("ToolEditor", () => { it("does not crash when binaryName or displayName is undefined", () => { const onUrlUpdate = vi.fn(); - const incompleteTool = { ...defaultTool(), binaryName: undefined, displayName: undefined } as unknown as Tool; + const incompleteTool = { + ...defaultTool(), + binaryName: undefined, + displayName: undefined, + } as unknown as Tool; expect(() => render(, { @@ -28,7 +58,25 @@ describe("ToolEditor", () => { searchParams: "?test=test", onUrlUpdate, }), - }) + }), ).not.toThrow(); }); + + it("updates root interactive from tool settings dialog", () => { + renderWithProvider(, { + tool: defaultTool("test-tool", "Test Tool"), + dialogs: { + parameterDetails: false, + editTool: true, + savedCommands: false, + exclusionGroups: false, + }, + }); + + const interactiveSwitch = screen.getByLabelText("Interactive"); + + expect(capturedCtx.tool.interactive).toBeUndefined(); + fireEvent.click(interactiveSwitch); + expect(capturedCtx.tool.interactive).toBe(true); + }); }); diff --git a/tests/tool-editor/tools.test.ts b/tests/tool-editor/tools.test.ts index 034de49..a032618 100644 --- a/tests/tool-editor/tools.test.ts +++ b/tests/tool-editor/tools.test.ts @@ -16,7 +16,10 @@ describe("applyMergePatch", () => { it("removes top-level fields set to null in the patch", () => { const base = { ...defaultTool("curl"), info: { description: "A transfer tool" } }; - const result = applyMergePatch(base as unknown as Parameters[0], { info: null } as unknown as Parameters[1]); + const result = applyMergePatch( + base as unknown as Parameters[0], + { info: null } as unknown as Parameters[1], + ); expect((result as unknown as Record).info).toBeUndefined(); }); diff --git a/vitest.config.ts b/vitest.config.ts index 2b456ca..86fb804 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,9 +12,7 @@ export default defineConfig({ setupFiles: ["./tests/vitest.setup.ts"], coverage: { provider: "v8", - reporter: process.env.GITHUB_ACTIONS - ? ["text", "github-actions", "json-summary"] - : ["text"], + reporter: process.env.GITHUB_ACTIONS ? ["text", "github-actions", "json-summary"] : ["text"], exclude: [ "src/components/ui/**", "src/components/ai-elements/**", From 291720a686d4450110877a97c7cac17b42869071 Mon Sep 17 00:00:00 2001 From: divyeshio <79130336+divyeshio@users.noreply.github.com> Date: Fri, 17 Apr 2026 00:59:57 +0530 Subject: [PATCH 5/5] fix: remove unnecessary reporter option for GitHub Actions in coverage configuration --- vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index 86fb804..134ced0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ setupFiles: ["./tests/vitest.setup.ts"], coverage: { provider: "v8", - reporter: process.env.GITHUB_ACTIONS ? ["text", "github-actions", "json-summary"] : ["text"], + reporter: process.env.GITHUB_ACTIONS ? ["text", "json-summary"] : ["text"], exclude: [ "src/components/ui/**", "src/components/ai-elements/**",