Conversation
- Updated CommandDialog to use command keys for updates. - Refactored ExclusionGroupsDialog to replace command IDs with keys in various functions. - Modified ParameterDetailsDialog to handle parameters using keys instead of IDs. - Adjusted SavedCommandsDialog to delete commands using keys. - Changed HelpMenu to filter commands and parameters based on keys. - Updated ParameterList to utilize keys for parameter management. - Refactored tool-editor store actions to work with command and parameter keys. - Adjusted AIParsing component to replace IDs with keys during tool parsing. - Updated saved commands handling in routes to use keys instead of IDs. - Removed UUID dependency where applicable and replaced with slugified keys.
- Added a new `Explore` agent for efficient codebase exploration and Q&A. - Refactored tool schemas to replace `supportedInput` and `supportedOutput` with a unified `metadata` object. - Created a new nested JSON schema for tools, enhancing the structure and validation. - Updated existing tools to utilize the new metadata format, ensuring consistency across the codebase. - Modified related utility functions and tests to accommodate the new metadata structure.
…luding key generation and import adjustments
There was a problem hiding this comment.
Pull request overview
This PR migrates the Commandly tool/editor data model away from UUID-based id fields to string-based key fields, and consolidates tool I/O capabilities under a metadata object while updating generated specs and bundled tool JSON to match.
Changes:
- Replace
id/*Idfields withkey/*Keyacross Commandly types, editor/store logic, runtime preview, and routes. - Move
supportedInput/supportedOutputintotool.metadataand update tool cards, scripts, MCP output, and public JSON assets. - Generate both flat and nested JSON schema specifications and add nested specification output.
Reviewed changes
Copilot reviewed 43 out of 44 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routes/tools/index.tsx | Stops generating/storing tool UUID ids during navigation/import. |
| src/routes/tools/$toolName/index.tsx | Updates saved command + command list handling to use key and tool name as id basis. |
| src/lib/utils.ts | Replaces replaceId with replaceKey migration helper using slug-based keys. |
| src/lib/types.ts | Adds schemas/types for supported tool I/O metadata. |
| src/components/tool-editor-ui/tool-card.tsx | Reads supported I/O from tool.metadata instead of top-level fields. |
| src/components/tool-editor-ui/dialogs/saved-commands-dialog.tsx | Switches SavedCommand list keys/deletes from id to key. |
| src/components/tool-editor-ui/ai-parsing.tsx | Uses replaceKey instead of replaceId after schema validation. |
| scripts/generate-tools-json.ts | Emits metadata instead of top-level supported I/O arrays. |
| scripts/generate-specification.ts | Writes both public/specification/flat.json and nested.json. |
| registry/commandly/tool-editor/tool-editor.tsx | Updates saved command delete/load paths to use tool name + key. |
| registry/commandly/tool-editor/tool-editor.store.ts | Migrates selectors/actions from ids to keys; updates saved command / exclusion group handling. |
| registry/commandly/tool-editor/parameter-list.tsx | Uses commandKey / parameter.key / group.key throughout list rendering/actions. |
| registry/commandly/tool-editor/help-menu.tsx | Migrates command hierarchy traversal from parent ids to parent keys. |
| registry/commandly/tool-editor/dialogs/tool-details-dialog.tsx | Writes supported I/O into tool.metadata and imports types from src/lib/types. |
| registry/commandly/tool-editor/dialogs/saved-commands-dialog.tsx | Migrates props and UI list keys/deletes from id to key. |
| registry/commandly/tool-editor/dialogs/parameter-details-dialog.tsx | Migrates dependency/validation/enum value identifiers to key and updates reference fields to *Key. |
| registry/commandly/tool-editor/dialogs/exclusion-groups-dialog.tsx | Migrates exclusion group editing to key and parameterKeys. |
| registry/commandly/tool-editor/dialogs/command-dialog.tsx | Updates save call to updateCommand to use command key. |
| registry/commandly/tool-editor/command-tree.tsx | Migrates tree expansion/selection/delete/add flows from ids to keys. |
| registry/commandly/runtime-preview.tsx | Uses parameter.key and commandKey for rendering and value updates. |
| registry/commandly/lib/utils/commandly.ts | Adds slugify, migrates command/parameter creation and hierarchy helpers to keys, moves supported I/O to metadata. |
| registry/commandly/lib/utils/commandly-nested.ts | Updates nested conversion to resolve dependencies/relationships via keys and moves supported I/O to metadata. |
| registry/commandly/lib/types/commandly.ts | Updates Zod schemas/types from ids to keys; replaces supported I/O schemas with generic metadata. |
| registry/commandly/lib/types/commandly-nested.ts | Replaces TS interfaces with Zod schemas and adds nested tool metadata schema. |
| registry/commandly/generated-command.tsx | Uses commandKey and parameter keys for value lookup/filtering. |
| registry/commandly/tests/tool-editor/parameter-list.test.tsx | Updates test fixtures/assertions for key-based model. |
| registry/commandly/tests/tool-editor/dialogs/parameter-details-dialog.test.tsx | Updates test fixtures/assertions for key-based model. |
| registry/commandly/tests/tool-editor/dialogs/command-dialog.test.tsx | Updates test fixtures/assertions for key-based command identity. |
| registry/commandly/tests/tool-editor/command-tree.test.tsx | Updates command tree tests for parentCommandKey/key model + metadata presence. |
| registry/commandly/tests/generated-command.test.tsx | Updates mocked store/tool data to include keys. |
| public/tools.json | Updates tool list entries to use metadata for supported I/O. |
| public/tools-collection/yt-dlp.json | Migrates tool JSON to metadata + key-based commands/parameters. |
| public/tools-collection/urlfinder.json | Migrates tool JSON to metadata + key-based commands/parameters. |
| public/tools-collection/subfinder.json | Migrates tool JSON to metadata + key-based commands/parameters. |
| public/tools-collection/shuffledns.json | Migrates tool JSON to metadata + key-based commands/parameters. |
| public/tools-collection/dnsx.json | Migrates tool JSON to metadata + key-based commands/parameters. |
| public/tools-collection/cdncheck.json | Migrates tool JSON to metadata + key-based commands/parameters/exclusion groups. |
| public/tools-collection/asnmap.json | Migrates tool JSON to metadata + key-based commands/parameters. |
| public/specification/nested.json | Adds generated nested JSON schema spec output. |
| public/specification/flat.json | Updates generated flat JSON schema spec for key-based model + metadata. |
| mcp/index.ts | Emits metadata instead of supportedInput/supportedOutput fields. |
| .github/agents/Explore.agent.md | Adds an exploration subagent configuration for repo Q&A/search. |
Comments suppressed due to low confidence (3)
registry/commandly/tool-editor/dialogs/tool-details-dialog.tsx:109
defaultValue={tool.metadata.supportedInput}(and supportedOutput below) assumes those properties always exist. ButToolSchemacurrently typesmetadataas a generic record, so these may beundefinedat runtime (e.g., older tools), which can breakMultiSelectif it expects an array. Provide a safe fallback (e.g.,tool.metadata.supportedInput ?? []) or validatemetadataagainst a schema that requires these arrays.
registry/commandly/tool-editor/tool-editor.store.ts:194- Saved command keys are generated via
slugify(command.substring(0, 20)), which can collide for different commands with the same prefix. Since deletion/removal uses the key as the identifier, collisions can cause the wrong entry to be deleted or make entries unreachable. Use a collision-resistant key (e.g., uuid, hash of full command, or slug + unique suffix) and keep it stable.
registry/commandly/lib/utils/commandly.ts:304 createNewParameterinitializeskeyas an empty string. Because the rest of the editor now treatskeyas the primary identifier (React keys, updates, deletes, upserts), creating multiple new parameters or saving before a key is generated can cause duplicate identifiers and incorrect upsert/delete behavior. Consider generating a temporary unique key at creation time (and keeping it stable) or enforce key assignment before the parameter can be saved/used.
export const createNewParameter = (isGlobal: boolean, commandKey?: string): Parameter => {
return {
key: "",
name: "",
commandKey: isGlobal ? undefined : commandKey,
description: "",
parameterType: "Option",
dataType: "String",
isRequired: false,
| addSavedCommandToStorage(`saved-${toolId}`, newSavedCommand); | ||
| setSavedCommands(getSavedCommandsFromStorage(toolId)); |
There was a problem hiding this comment.
addSavedCommandToStorage is being called with a toolId that already includes the saved- prefix (addSavedCommandToStorage(saved-${toolId}, ...)). Since the storage helpers themselves prefix with saved- when reading, this causes reads from saved-saved-${toolId} and will overwrite existing saved commands instead of appending. Pass the un-prefixed tool id consistently (e.g., tool.name) and keep the prefixing logic in one place (either helpers or callers, not both).
| if (!tool) return; | ||
| const toolId = tool.id || tool.name; | ||
| removeSavedCommandFromStorage(`saved-${toolId}`, commandId); | ||
| const toolId = tool.name; | ||
| removeSavedCommandFromStorage(`saved-${toolId}`, commandKey); | ||
| setSavedCommands(getSavedCommandsFromStorage(toolId)); |
There was a problem hiding this comment.
removeSavedCommandFromStorage is called with a toolId that includes the saved- prefix. With the current helper implementations this results in reading from saved-saved-... and can clear or fail to remove the intended entry. Standardize the storage key format (prefer passing the un-prefixed tool id to all helpers) to avoid double-prefixing.
| addSavedCommandToStorage(`saved-${toolId}`, newSavedCommand); | ||
|
|
||
| toast("Command Saved", { | ||
| description: "Command has been saved successfully.", | ||
| }); | ||
| }, | ||
|
|
||
| removeSavedCommand(commandId: string) { | ||
| removeSavedCommand(commandKey: string) { | ||
| toolBuilderStore.setState((state) => { | ||
| const toolId = state.tool.id || state.tool.name; | ||
| removeSavedCommandFromStorage(`saved-${toolId}`, commandId); | ||
| const toolId = state.tool.name; | ||
| removeSavedCommandFromStorage(`saved-${toolId}`, commandKey); | ||
| return { ...state }; | ||
| }); |
There was a problem hiding this comment.
addSavedCommandToStorage / removeSavedCommandFromStorage are being called with a toolId that includes the saved- prefix. The underlying helpers already apply the prefix when reading, so this double-prefixing (saved-saved-...) breaks append/remove semantics and can overwrite or clear the list unexpectedly. Pass the un-prefixed id consistently and ensure helpers use the same key for get/set/remove.
| const handleDeleteCommand = (commandKey: string) => { | ||
| const toolId = tool.name; | ||
| removeSavedCommandFromStorage(`saved-${toolId}`, commandKey); | ||
| setSavedCommands(savedCommands.filter((cmd) => cmd.key !== commandKey)); |
There was a problem hiding this comment.
removeSavedCommandFromStorage is invoked with a toolId that already includes the saved- prefix. Given the current helper implementations, this can lead to reads from saved-saved-... and cause removals to fail or clear the wrong entry list. Standardize storage key construction in one place and pass a consistent (un-prefixed) tool id.
| setParameter((prev) => { | ||
| const next = { ...prev, ...updates }; | ||
| if (updates.name || updates.longFlag) { | ||
| let generatedKey = slugify(next.longFlag || next.name); | ||
|
|
||
| // Get current tool state to check for duplicate keys | ||
| const toolState = toolBuilderStore.state; | ||
| const existingParam = toolState.tool.parameters.find( | ||
| (p) => p.key === generatedKey && p.key !== prev.key, | ||
| ); | ||
|
|
||
| // If duplicate exists and parameter is not global, prefix with command key | ||
| if (existingParam && !next.isGlobal && commandKey) { | ||
| generatedKey = `${commandKey}-${generatedKey}`; | ||
| } | ||
|
|
||
| next.key = generatedKey; | ||
| } |
There was a problem hiding this comment.
updateParameter auto-regenerates parameter.key whenever name/longFlag changes. Since key is now the primary identifier (and upsertParameter matches by key), changing it will typically create a new parameter on save and leave the old one behind, and it can also break references (exclusionGroups.parameterKeys, dependency parameterKey/dependsOnParameterKey, parameterValues keyed by parameter.key). Keys should be stable once created, or key changes must be migrated across the entire tool state in one transaction.
| const keyMap: Record<string, string> = {}; | ||
|
|
||
| // Map tool id | ||
| if (clone.id) { | ||
| idMap[clone.id] = uuidv7(); | ||
| function mapKey(oldKey: string, newKey: string): string { | ||
| if (!oldKey) return ""; | ||
| keyMap[oldKey] = newKey; | ||
| return keyMap[oldKey]; | ||
| } | ||
|
|
||
| // Map command ids | ||
| clone.commands.forEach((cmd) => { | ||
| idMap[cmd.id] = uuidv7(); | ||
| if (cmd.parentCommandId) idMap[cmd.parentCommandId] = uuidv7(); | ||
| // Map command keys (handle commandId as well during migration) | ||
| clone.commands.forEach((cmd: Command) => { | ||
| const oldKey = cmd.key; |
There was a problem hiding this comment.
replaceKey only records a mapping when oldKey is truthy (if (!oldKey) return ""). If a parsed tool contains empty-string keys (allowed by the current schemas and also produced by createNewParameter), the "replacement" step will keep empty keys, which can lead to duplicate identifiers and broken lookups. Consider generating keys even when the existing key is empty/missing, and ensure generated keys are unique within the tool.
| export const removeSavedCommandFromStorage = (toolId: string, commandKey: string): void => { | ||
| const existingCommands = getSavedCommandsFromStorage(toolId); | ||
| const updatedCommands = existingCommands.filter((cmd) => cmd.id !== commandId); | ||
| const updatedCommands = existingCommands.filter((cmd) => cmd.key !== commandKey); | ||
| saveSavedCommandsToStorage(toolId, updatedCommands); | ||
| }; |
There was a problem hiding this comment.
The saved-command storage helpers are inconsistent about whether toolId already includes the saved- prefix: getSavedCommandsFromStorage reads from saved-${toolId}, while saveSavedCommandsToStorage writes to toolId directly. When callers pass an already-prefixed id (e.g., saved-${tool.name}), reads happen from saved-saved-..., which makes removeSavedCommandFromStorage behave like a full reset and makes addSavedCommandToStorage overwrite instead of append. Unify the contract (either always prefix inside helpers, or always prefix at call sites) and ensure get/set/remove all use the same key.
No description provided.