Conversation
…schemas, update tool editor and AI parsing components to reflect changes, implement new registry API for fetching tool data, enhance documentation routes, and improve overall code structure and readability.
There was a problem hiding this comment.
Pull request overview
This PR modernizes the “Commandly” tool/registry plumbing by removing Zod-based runtime schemas, switching schema generation to typescript-json-schema, and adding a new /docs section backed by a registry API. It also updates contribution/automation workflows (tool submission and generated-artifact updates) and surfaces build metadata (commit SHA) in the UI.
Changes:
- Add
/docsroutes and registry API to browse registry items and view details. - Replace Zod schemas/validators with TypeScript interfaces + JSON schema generation via
typescript-json-schema. - Update CI/workflows for tool PR validation and auto-regeneration of specs/registry artifacts; add
COMMIT_SHAdefine.
Reviewed changes
Copilot reviewed 42 out of 46 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.ts | Adds /docs prerender and injects COMMIT_SHA build define |
| src/vite-env.d.ts | Declares global COMMIT_SHA type |
| src/routes/tools/$toolName/index.tsx | Replaces Zod search validation + localStorage tool parsing logic |
| src/routes/tools/$toolName/edit.tsx | Replaces Zod search validation + localStorage tool parsing logic |
| src/routes/index.tsx | Shows commit SHA link in footer |
| src/routes/docs/index.tsx | New docs index listing registry items |
| src/routes/docs/$componentName.tsx | New docs detail page for a registry item |
| src/routes/__root.tsx | Updates navbar (Playground/Docs) and root HTML props |
| src/routeTree.gen.ts | Adds generated routes for /docs |
| src/lib/types.ts | Replaces Zod types with TS types + module augmentation for ToolMetadata |
| src/lib/api/registry.api.ts | New API for fetching registry list/items (isomorphic list) |
| src/components/tool-editor-ui/tool-card.tsx | Adjusts link search params construction for newTool |
| src/components/tool-editor-ui/manual-new-tool.tsx | Moves ManualNewTool import to src/lib/types |
| src/components/tool-editor-ui/import.tsx | Removes Zod parse and casts imported JSON to Tool |
| src/components/tool-editor-ui/ai-parsing.tsx | Switches schema source to /specification/flat.json and removes Zod validation |
| scripts/generate-specification.ts | Generates flat/nested JSON Schema from TS types via typescript-json-schema |
| registry/commandly/tool-editor/tool-editor.tsx | Updates “Contribute” flow to open GitHub file editor (new/edit) |
| registry/commandly/tool-editor/dialogs/tool-details-dialog.tsx | Casts metadata updates to ToolMetadata |
| registry/commandly/tool-editor/dialogs/parameter-details-dialog.tsx | Makes longFlag trimming null-safe |
| registry/commandly/lib/utils/commandly-nested.ts | Provides defaults for isDefault/sortOrder when nesting |
| registry/commandly/lib/types/commandly.ts | Replaces Zod schemas with TS interfaces for flat tool model |
| registry/commandly/lib/types/commandly-nested.ts | Replaces Zod schemas with TS interfaces for nested tool model |
| registry/commandly/tests/json-output.test.tsx | Updates casting in test after type changes |
| registry.json | Updates registry dependency lists to remove Zod |
| public/specification/nested.json | Updates generated nested JSON Schema output |
| public/specification/flat.json | Updates generated flat JSON Schema output |
| public/r/ui.json | Updates registry item payload to remove Zod dependency + refresh embedded file contents |
| public/r/runtime-preview.json | Refreshes embedded file contents |
| public/r/registry.json | Updates public registry dependency lists to remove Zod |
| public/r/json-output.json | Refreshes embedded file contents |
| public/r/generated-command.json | Refreshes embedded file contents |
| public/r/core.json | Removes deprecated/duplicate registry item |
| public/r/commandly-utils.json | Refreshes embedded file contents |
| public/r/commandly-types.json | Removes Zod dependency + refreshes embedded file contents |
| package.json | Removes @tanstack/zod-adapter, adds typescript-json-schema |
| .vscode/tasks.json | Adds a “bun dev” task that runs on folder open |
| .github/workflows/validate-tool-pr.yml | Adds PR-time validation for changed public/tools-collection/** JSON files |
| .github/workflows/tool-submission.yml | Removes issue-driven tool submission automation |
| .github/workflows/pr.yml | Regenerates specs/registry during PR checks and attempts to commit/push |
| .github/workflows/ci-cd.yml | Regenerates specs/registry during deploy workflow and attempts to commit/push |
| .github/PULL_REQUEST_TEMPLATE/tool-submission.md | Fixes template table formatting |
| .github/ISSUE_TEMPLATE/new-tool.yml | Removes issue form for new tools |
| .github/ISSUE_TEMPLATE/edit-tool.yml | Removes issue form for tool edits |
| const validateJson = useCallback( | ||
| (jsonString: string) => { | ||
| try { | ||
| const parsedTool = ToolSchema.parse(JSON.parse(jsonString)); | ||
| const parsedTool = JSON.parse(jsonString) as Tool; | ||
| const modifiedTool = replaceKey(parsedTool); | ||
| setJson(JSON.stringify(modifiedTool, null, 2)); | ||
| onParseCompleted(modifiedTool); | ||
| } catch (error) { | ||
| console.error("Error parsing JSON:", error); | ||
| onParseCompleted(null); | ||
| if (error instanceof z.ZodError) { | ||
| toast.error(`Invalid JSON: ${error.name}. Please check the format.`, { | ||
| description: z.prettifyError(error), | ||
| duration: 5000, | ||
| }); | ||
| } else { | ||
| toast.error("Failed to parse JSON. Please check the format."); | ||
| } | ||
| toast.error("Failed to parse JSON. Please check the format."); | ||
| } |
There was a problem hiding this comment.
validateJson now only checks that the text is valid JSON, then casts to Tool. If the AI returns a JSON object that doesn’t match the Tool shape, this will still be treated as “valid” and can cause downstream runtime errors. Consider adding at least a lightweight structural validation (required fields + array checks) before calling onParseCompleted / replacing keys.
| - name: Commit changes if any | ||
| run: | | ||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | ||
| git config --global user.name "github-actions[bot]" | ||
|
|
||
| if git diff --quiet; then | ||
| echo "No changes to commit" | ||
| else | ||
| git add . | ||
| git commit -m "chore: update generated specifications and registry" | ||
| git push origin HEAD:${{ github.head_ref }} | ||
| fi |
There was a problem hiding this comment.
This workflow pushes commits back to the PR branch (git push) during CI. For PRs from forks, the job will fail because the token can’t push to the contributor’s branch, and even for same-repo PRs it mutates the branch during checks (can invalidate reviews / create surprise commits). Consider making this conditional on !github.event.pull_request.head.repo.fork (or removing the push and instead failing with instructions / using a separate bot workflow).
| const flatProgram = TJS.programFromConfig(tsConfigPath, [ | ||
| join(process.cwd(), "registry/commandly/lib/types/commandly.ts"), | ||
| ]); | ||
| const flatSchema = TJS.generateSchema(flatProgram, "Tool", settings); | ||
| const flatOutputPath = join(process.cwd(), "public", "specification", "flat.json"); | ||
| writeFileSync(flatOutputPath, JSON.stringify(flatJsonSchema, null, 2)); | ||
| writeFileSync(flatOutputPath, JSON.stringify(flatSchema, null, 2)); | ||
|
|
||
| // Generate Nested Specification | ||
| const nestedJsonSchema = z.toJSONSchema(NestedToolSchema); | ||
| const nestedProgram = TJS.programFromConfig(tsConfigPath, [ | ||
| join(process.cwd(), "registry/commandly/lib/types/commandly-nested.ts"), | ||
| ]); | ||
| const nestedSchema = TJS.generateSchema(nestedProgram, "NestedTool", settings); | ||
| const nestedOutputPath = join(process.cwd(), "public", "specification", "nested.json"); | ||
| writeFileSync(nestedOutputPath, JSON.stringify(nestedJsonSchema, null, 2)); | ||
| writeFileSync(nestedOutputPath, JSON.stringify(nestedSchema, null, 2)); |
There was a problem hiding this comment.
TJS.generateSchema(...) can return null (e.g., if the type name can’t be resolved or the program fails). Currently the script will write null to the schema files and still exit 0, which can silently break downstream consumers (AI parsing, validation, etc.). Add explicit null checks and throw a descriptive error / non-zero exit before writing files.
| try { | ||
| const importedData = JSON.parse(jsonInput); | ||
| const tool = ToolSchema.parse(importedData); | ||
| const tool = importedData as Tool; | ||
| onParseCompleted(tool); | ||
| setJsonInput(""); |
There was a problem hiding this comment.
The importer now accepts any valid JSON and casts it to Tool without validating required fields (e.g., name, displayName, commands, parameters). Invalid-but-JSON input will get past this try/catch and likely fail later in the editor/runtime preview. Consider adding minimal shape checks here (or a runtime schema validator) and showing a helpful error before calling onParseCompleted.
| @@ -32,6 +32,25 @@ jobs: | |||
| - name: Install dependencies | |||
| run: bun install | |||
|
|
|||
| - name: Generate specifications | |||
| run: bun run specification:build | |||
|
|
|||
| - name: Build registry | |||
| run: bun run registry:build | |||
|
|
|||
| - name: Commit changes if any | |||
| run: | | |||
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |||
| git config --global user.name "github-actions[bot]" | |||
|
|
|||
| if git diff --quiet; then | |||
| echo "No changes to commit" | |||
| else | |||
| git add . | |||
| git commit -m "chore: update generated specifications and registry" | |||
| git push origin HEAD:main | |||
| fi | |||
There was a problem hiding this comment.
This deploy workflow commits and pushes generated files back to main as part of a run triggered by push to main. That can cause extra pipeline runs (push → workflow → push → workflow), can fail on protected branches, and makes deployments less deterministic. Consider avoiding pushes from CI (treat generated artifacts as build outputs) or adding guards (e.g., skip if github.actor is the actions bot / use [skip ci] / separate scheduled update workflow).
| const { execSync } = require("child_process"); | ||
|
|
||
| const changedFiles = execSync( | ||
| `git diff --name-only origin/${context.payload.pull_request.base.ref}...HEAD -- "public/tools-collection/"`, | ||
| { encoding: "utf-8" } | ||
| ).trim().split("\n").filter(Boolean); | ||
|
|
There was a problem hiding this comment.
git diff --name-only origin/${baseRef}...HEAD is likely to fail in Actions because actions/checkout defaults to fetch-depth: 1 and won’t have origin/${baseRef} fetched. Set fetch-depth: 0 (or explicitly fetch the base ref) before running the diff, or use the GitHub API (pulls.listFiles) to get the changed files instead of shelling out to git.
| const { execSync } = require("child_process"); | |
| const changedFiles = execSync( | |
| `git diff --name-only origin/${context.payload.pull_request.base.ref}...HEAD -- "public/tools-collection/"`, | |
| { encoding: "utf-8" } | |
| ).trim().split("\n").filter(Boolean); | |
| const prNumber = context.payload.pull_request.number; | |
| const files = await github.paginate( | |
| github.rest.pulls.listFiles, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber, | |
| per_page: 100, | |
| } | |
| ); | |
| const changedFiles = files | |
| .map((f) => f.filename) | |
| .filter((name) => name.startsWith("public/tools-collection/")); |
| define: { | ||
| COMMIT_SHA: `"${process.env.GITHUB_SHA?.slice(0, 7).toString()}"`, | ||
| }, |
There was a problem hiding this comment.
process.env.GITHUB_SHA?.slice(0, 7).toString() will throw at build time when GITHUB_SHA is unset (optional chaining only applies to the slice call; .toString() is still invoked on the possibly-undefined result). Consider safely deriving the value (e.g., const sha = process.env.GITHUB_SHA?.slice(0,7) ?? "") and using JSON.stringify(sha) so local builds/CI without that env don’t crash and COMMIT_SHA is falsy when missing.
| if (newTool) { | ||
| const newToolData = localStorage.getItem(`tool-${newTool}`); | ||
| if (newToolData) { | ||
| const validatedTool = zodValidator(ToolSchema).parse(JSON.parse(newToolData)); | ||
| return validatedTool; | ||
| return JSON.parse(newToolData) as Tool; | ||
| } else { | ||
| return defaultTool() as Tool; |
There was a problem hiding this comment.
JSON.parse(newToolData) is unguarded here. If localStorage contains invalid JSON (corrupted value, manual edits, older format), the route loader will throw and the page will fail to render. Wrap the parse in a try/catch and fall back to defaultTool() (and optionally clear the bad localStorage entry / show a toast).
| loader: async ({ params: { toolName }, deps: { newTool } }) => { | ||
| if (newTool) { | ||
| const newToolData = localStorage.getItem(`tool-${newTool}`); | ||
| if (newToolData) { | ||
| const validatedTool = zodValidator(ToolSchema).parse(JSON.parse(newToolData)); | ||
| return validatedTool; | ||
| return JSON.parse(newToolData) as Tool; | ||
| } else { | ||
| return defaultTool() as Tool; | ||
| } |
There was a problem hiding this comment.
JSON.parse(newToolData) is unguarded here. A corrupted/legacy localStorage value will throw in the loader and break the edit page. Add a try/catch with a safe fallback (e.g., defaultTool() and/or clearing the invalid tool-${newTool} entry).
No description provided.