feat: add reusable ToolHealthBadge and ToolHealthBanner components
- Create ToolHealthBadge component in @tpmjs/ui for broken tool indicator - Create ToolHealthBanner component in @tpmjs/ui for detailed health warnings - Integrate both components into playground ToolsSidebar: - Badge shows in tool cards in left sidebar - Banner shows in tool detail modal - Update search-registry to include health fields in API responses - Add package.json exports for new health components These components provide consistent UI for displaying broken tool status across the application (tool search, tool detail pages, playground). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
54ee6439cb
commit
a9e91c02d1
24 changed files with 729 additions and 405 deletions
306
DENO_NODE_PACKAGE_ISSUE.md
Normal file
306
DENO_NODE_PACKAGE_ISSUE.md
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
# Running `ai-sdk-tool-code-execution` in Deno - Compatibility Issue
|
||||
|
||||
## Problem Summary
|
||||
|
||||
We need to run the npm package `ai-sdk-tool-code-execution` in a Deno runtime environment on Railway. The package requires Node.js built-ins (`node:sqlite`, `undici`) that don't exist in Deno, and we're looking for a solution to make it work.
|
||||
|
||||
## Environment
|
||||
|
||||
- **Runtime:** Deno 1.39.0 on Railway
|
||||
- **Package:** `ai-sdk-tool-code-execution@0.0.2`
|
||||
- **Import Method:** Dynamic imports via esm.sh CDN
|
||||
- **Use Case:** Remote code execution for AI SDK tools
|
||||
|
||||
## What We're Trying to Do
|
||||
|
||||
We have a Deno server that dynamically imports npm packages at runtime to provide AI SDK tools. The workflow is:
|
||||
|
||||
1. User requests a tool (e.g., `executeCode`)
|
||||
2. Deno server fetches the package from esm.sh or npm
|
||||
3. Server loads the tool's schema and execution function
|
||||
4. Server executes the tool with user-provided parameters
|
||||
|
||||
## The Package We Need
|
||||
|
||||
**Package:** `ai-sdk-tool-code-execution`
|
||||
**Version:** `0.0.2`
|
||||
**Description:** Execute Python code in a sandboxed environment using Vercel Sandbox
|
||||
**npm URL:** https://www.npmjs.com/package/ai-sdk-tool-code-execution
|
||||
**CDN URLs:**
|
||||
- esm.sh: `https://esm.sh/ai-sdk-tool-code-execution@0.0.2`
|
||||
- jsdelivr: `https://cdn.jsdelivr.net/npm/ai-sdk-tool-code-execution@0.0.2/+esm`
|
||||
|
||||
**Dependencies (from package.json):**
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"ai": "^4.0.18",
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"undici": "^7.16.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Issue:** The package depends on:
|
||||
- `better-sqlite3` → which requires `node:sqlite` (Node.js built-in)
|
||||
- `undici` → HTTP client that uses Node.js internals
|
||||
|
||||
## What We've Tried
|
||||
|
||||
### Attempt 1: Deno npm: Specifier (Node.js Compatibility Mode)
|
||||
|
||||
**Code:**
|
||||
```typescript
|
||||
const npmUrl = `npm:ai-sdk-tool-code-execution@0.0.2`;
|
||||
const module = await import(npmUrl);
|
||||
```
|
||||
|
||||
**Error:**
|
||||
```
|
||||
Loading unprepared module: npm:ai-sdk-tool-code-execution@0.0.2
|
||||
```
|
||||
|
||||
**Why it failed:** Deno's npm compatibility requires the package to be "prepared" (downloaded/cached) before import. Dynamic imports of unprepared npm packages fail.
|
||||
|
||||
### Attempt 2: esm.sh with Node.js Target
|
||||
|
||||
**Code:**
|
||||
```typescript
|
||||
const esmUrl = `https://esm.sh/ai-sdk-tool-code-execution@0.0.2?target=esnext`;
|
||||
const module = await import(esmUrl);
|
||||
```
|
||||
|
||||
**Error:**
|
||||
```
|
||||
Module not found "https://esm.sh/node:sqlite?target=esnext"
|
||||
at https://esm.sh/undici@^7.16.0?target=esnext:25:8
|
||||
```
|
||||
|
||||
**Why it failed:** The package code imports `node:sqlite` which esm.sh tries to load from `https://esm.sh/node:sqlite?target=esnext`, but `node:sqlite` is a Node.js built-in, not an npm package.
|
||||
|
||||
### Attempt 3: Multi-Strategy with Fallback
|
||||
|
||||
**Code:**
|
||||
```typescript
|
||||
let module;
|
||||
let importError;
|
||||
|
||||
// Strategy 1: npm: specifier
|
||||
try {
|
||||
const npmUrl = `npm:${packageName}@${version}`;
|
||||
module = await import(npmUrl);
|
||||
} catch (error) {
|
||||
importError = error;
|
||||
|
||||
// Strategy 2: esm.sh with esnext target
|
||||
try {
|
||||
const esmUrl = `https://esm.sh/${packageName}@${version}?target=esnext`;
|
||||
module = await import(esmUrl);
|
||||
} catch (esmError) {
|
||||
return { success: false, error: esmError.message };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Result:** Both strategies fail with the same errors as above.
|
||||
|
||||
## Current Deno Configuration
|
||||
|
||||
**`deno.json`:**
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"lib": ["deno.window"],
|
||||
"strict": true
|
||||
},
|
||||
"nodeModulesDir": true,
|
||||
"unstable": ["byonm"],
|
||||
"imports": {
|
||||
"zod-to-json-schema": "https://esm.sh/zod-to-json-schema@3.25.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Settings:**
|
||||
- `nodeModulesDir: true` - Creates `node_modules` directory for npm packages
|
||||
- `unstable: ["byonm"]` - Enables "Bring Your Own Node Modules" mode
|
||||
|
||||
## Full Error Details
|
||||
|
||||
### npm: Strategy Error
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Failed to import package: ...",
|
||||
"details": {
|
||||
"npmError": "Loading unprepared module: npm:ai-sdk-tool-code-execution@0.0.2, imported from: file:///app/server.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### esm.sh Strategy Error
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Failed to import package: Module not found \"https://esm.sh/node:sqlite?target=esnext\"",
|
||||
"details": {
|
||||
"esmError": "Module not found \"https://esm.sh/node:sqlite?target=esnext\".\n at https://esm.sh/undici@^7.16.0?target=esnext:25:8"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Deep Dive
|
||||
|
||||
### Why This Package Needs Node.js
|
||||
|
||||
1. **better-sqlite3** - Native Node.js addon for SQLite
|
||||
- Uses `node:sqlite` built-in
|
||||
- Compiled C++ bindings
|
||||
- Not available in Deno without Node compatibility layer
|
||||
|
||||
2. **undici** - Modern HTTP client for Node.js
|
||||
- Uses Node.js streams and buffer APIs
|
||||
- Optimized for Node.js internals
|
||||
- May work in Deno with polyfills, but blocked by sqlite dependency
|
||||
|
||||
### Deno's Node.js Compatibility
|
||||
|
||||
Deno supports many Node.js built-ins via `node:*` imports:
|
||||
- `node:fs`, `node:path`, `node:http`, `node:crypto`, etc.
|
||||
|
||||
**BUT** it does NOT support:
|
||||
- `node:sqlite` (not a standard Node.js built-in)
|
||||
- Native addons (`.node` files)
|
||||
- Some advanced internal APIs
|
||||
|
||||
### The Import Flow
|
||||
|
||||
1. **Deno tries to import** `npm:ai-sdk-tool-code-execution@0.0.2`
|
||||
2. **Package resolves to** esm.sh or npm registry
|
||||
3. **Package imports** `better-sqlite3`
|
||||
4. **better-sqlite3 imports** `node:sqlite`
|
||||
5. **FAILURE:** `node:sqlite` doesn't exist in Deno or esm.sh
|
||||
|
||||
## Questions for ChatGPT
|
||||
|
||||
1. **Can Deno's npm compatibility layer handle `better-sqlite3` or `node:sqlite`?**
|
||||
- Is there a Deno-compatible SQLite library we could alias?
|
||||
- Can we use import maps to redirect `node:sqlite` to a Deno polyfill?
|
||||
|
||||
2. **Can we "prepare" the npm module in Deno before dynamic import?**
|
||||
- Is there a way to pre-cache npm packages in Deno?
|
||||
- Can we use `deno vendor` or similar to prepare the package?
|
||||
|
||||
3. **Can esm.sh or other CDNs provide Node.js built-in polyfills?**
|
||||
- Does esm.sh have a mode that bundles Node.js built-ins?
|
||||
- Are there CDN parameters we're missing?
|
||||
|
||||
4. **Could we use Deno's `--node-modules-dir` flag differently?**
|
||||
- Should we install the package via npm/pnpm first?
|
||||
- Can we point Deno to pre-installed node_modules?
|
||||
|
||||
5. **Is there a way to patch/bundle the package to remove Node.js dependencies?**
|
||||
- Could we create a Deno-compatible fork?
|
||||
- Are there tools to transpile Node.js packages to Deno?
|
||||
|
||||
6. **Alternative: Different code execution package?**
|
||||
- Are there Deno-native code execution tools?
|
||||
- Could we use WebAssembly or browser-based sandboxing?
|
||||
|
||||
## What Would Success Look Like
|
||||
|
||||
**Ideal outcome:**
|
||||
```typescript
|
||||
// This should work in Deno:
|
||||
const module = await import('npm:ai-sdk-tool-code-execution@0.0.2');
|
||||
const { executeCode } = module;
|
||||
|
||||
// And this should execute:
|
||||
const result = await executeCode.execute({
|
||||
code: 'print(fibonacci(10))',
|
||||
language: 'python'
|
||||
});
|
||||
```
|
||||
|
||||
**Acceptable outcome:**
|
||||
```typescript
|
||||
// Some preparation step, then:
|
||||
const module = await import('https://esm.sh/ai-sdk-tool-code-execution@0.0.2');
|
||||
// Works without errors
|
||||
```
|
||||
|
||||
## Repository Context
|
||||
|
||||
**Project:** TPMJS - Tool Package Manager for AI SDK
|
||||
**Server:** `apps/railway-executor/server.ts`
|
||||
**Config:** `apps/railway-executor/deno.json`
|
||||
**Deployment:** Railway with Deno runtime
|
||||
|
||||
**Server Code (Simplified):**
|
||||
```typescript
|
||||
async function loadAndDescribe(req: Request): Promise<Response> {
|
||||
const { packageName, exportName, version, importUrl } = await req.json();
|
||||
|
||||
// Try npm: specifier first
|
||||
try {
|
||||
const npmUrl = `npm:${packageName}@${version}`;
|
||||
const module = await import(npmUrl);
|
||||
const tool = module[exportName];
|
||||
return Response.json({ success: true, tool });
|
||||
} catch (error) {
|
||||
// Try esm.sh fallback
|
||||
const esmUrl = `https://esm.sh/${packageName}@${version}?target=esnext`;
|
||||
const module = await import(esmUrl);
|
||||
const tool = module[exportName];
|
||||
return Response.json({ success: true, tool });
|
||||
}
|
||||
}
|
||||
|
||||
Deno.serve({ port: 3001 }, handler);
|
||||
```
|
||||
|
||||
## Live Error Logs
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
curl -X POST https://endearing-commitment-production.up.railway.app/load-and-describe \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"packageName": "ai-sdk-tool-code-execution",
|
||||
"exportName": "executeCode",
|
||||
"version": "0.0.2",
|
||||
"importUrl": "https://esm.sh/ai-sdk-tool-code-execution@0.0.2"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Failed to import package: Module not found \"https://esm.sh/node:sqlite?target=esnext\"",
|
||||
"details": {
|
||||
"npmError": "Loading unprepared module: npm:ai-sdk-tool-code-execution@0.0.2",
|
||||
"esmError": "Module not found \"https://esm.sh/node:sqlite?target=esnext\""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Context
|
||||
|
||||
- We successfully load other packages (e.g., `@tpmjs/hello`, `zod-to-json-schema`)
|
||||
- Only packages with Node.js built-in dependencies fail
|
||||
- Switching to Node.js would work, but we prefer Deno's security model
|
||||
- This is for a production tool registry serving AI SDK tools to users
|
||||
|
||||
## Related Resources
|
||||
|
||||
- **Deno npm compatibility:** https://deno.com/manual/node/npm_specifiers
|
||||
- **Deno Node built-ins:** https://deno.com/manual/node/node_specifiers
|
||||
- **esm.sh documentation:** https://esm.sh/
|
||||
- **Package source:** https://www.npmjs.com/package/ai-sdk-tool-code-execution
|
||||
- **Deno SQLite libraries:** https://deno.land/x/sqlite@v3.8
|
||||
|
||||
---
|
||||
|
||||
**Question for ChatGPT:** Is there any way to make `ai-sdk-tool-code-execution` work in Deno, given these constraints? If not, what's the closest alternative that would work in Deno's runtime?
|
||||
2
apps/playground/next-env.d.ts
vendored
2
apps/playground/next-env.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
|
||||
import { Input } from '@tpmjs/ui/Input/Input';
|
||||
import { ToolHealthBadge } from '@tpmjs/ui/ToolHealthBadge/ToolHealthBadge';
|
||||
import { ToolHealthBanner } from '@tpmjs/ui/ToolHealthBanner/ToolHealthBanner';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface Tool {
|
||||
|
|
@ -16,6 +18,10 @@ interface Tool {
|
|||
frameworks?: string[];
|
||||
env?: Array<{ name: string; description: string; required?: boolean; default?: string }>;
|
||||
importUrl?: string;
|
||||
importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||
executionHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||
healthCheckError?: string | null;
|
||||
lastHealthCheck?: string | null;
|
||||
}
|
||||
|
||||
export function ToolsSidebar(): React.ReactElement {
|
||||
|
|
@ -86,11 +92,16 @@ export function ToolsSidebar(): React.ReactElement {
|
|||
<p className="text-xs text-foreground-secondary line-clamp-2">
|
||||
{tool.description}
|
||||
</p>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<div className="mt-2 flex items-center gap-2 flex-wrap">
|
||||
<Badge variant="secondary" size="sm">
|
||||
{tool.category}
|
||||
</Badge>
|
||||
<span className="text-xs text-foreground-tertiary">v{tool.version}</span>
|
||||
<ToolHealthBadge
|
||||
importHealth={tool.importHealth}
|
||||
executionHealth={tool.executionHealth}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -156,6 +167,15 @@ export function ToolsSidebar(): React.ReactElement {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Health warning banner */}
|
||||
<ToolHealthBanner
|
||||
importHealth={selectedTool.importHealth}
|
||||
executionHealth={selectedTool.executionHealth}
|
||||
healthCheckError={selectedTool.healthCheckError}
|
||||
lastHealthCheck={selectedTool.lastHealthCheck}
|
||||
className="mb-6"
|
||||
/>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mb-6">
|
||||
<h3 className="mb-2 text-lg font-semibold text-foreground">Description</h3>
|
||||
|
|
|
|||
2
apps/web/next-env.d.ts
vendored
2
apps/web/next-env.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export async function GET() {
|
|||
// Official tools count (isOfficial is at package level)
|
||||
prisma.tool.count({
|
||||
where: {
|
||||
package: { isOfficial: true }
|
||||
package: { isOfficial: true },
|
||||
},
|
||||
}),
|
||||
|
||||
|
|
|
|||
|
|
@ -5,46 +5,46 @@
|
|||
@layer base {
|
||||
/* Light mode (default) */
|
||||
:root {
|
||||
/* Backgrounds & Surfaces */
|
||||
--background: 0 0% 100%; /* Pure white */
|
||||
--surface: 210 20% 98%; /* Off-white */
|
||||
/* Backgrounds & Surfaces - DRAMATIC CONTRAST */
|
||||
--background: 220 15% 96%; /* Light blue-gray background */
|
||||
--surface: 0 0% 100%; /* Pure white - cards really pop! */
|
||||
--surface-elevated: 0 0% 100%; /* White (elevated) */
|
||||
--surface-overlay: 0 0% 98%; /* Light gray */
|
||||
--surface-overlay: 0 0% 100%; /* White overlays */
|
||||
|
||||
/* Foreground (Text) */
|
||||
--foreground: 222 47% 11%; /* Almost black */
|
||||
--foreground-secondary: 215 16% 47%; /* Medium gray */
|
||||
--foreground-tertiary: 215 16% 65%; /* Light gray */
|
||||
--foreground-muted: 215 16% 75%; /* Very light gray */
|
||||
--foreground-secondary: 215 25% 35%; /* Much darker for readability */
|
||||
--foreground-tertiary: 215 20% 50%; /* Medium gray */
|
||||
--foreground-muted: 215 16% 65%; /* Light gray */
|
||||
|
||||
/* Borders */
|
||||
--border: 214 32% 91%; /* Light gray */
|
||||
--border-strong: 214 32% 80%; /* Medium gray */
|
||||
--border-subtle: 214 20% 95%; /* Very light gray */
|
||||
/* Borders - MUCH MORE VISIBLE */
|
||||
--border: 214 25% 80%; /* Strong medium gray */
|
||||
--border-strong: 214 30% 60%; /* Dark gray for emphasis */
|
||||
--border-subtle: 214 20% 88%; /* Subtle but visible */
|
||||
|
||||
/* Interactive States */
|
||||
--primary: 222 47% 11%; /* Dark for light mode */
|
||||
--primary-foreground: 210 40% 98%; /* Light text */
|
||||
--secondary: 210 40% 96%; /* Light secondary */
|
||||
/* Interactive States - MODERN & REFINED */
|
||||
--primary: 221 83% 53%; /* Sophisticated blue */
|
||||
--primary-foreground: 0 0% 100%; /* White text */
|
||||
--secondary: 220 15% 90%; /* Subtle gray-blue bg */
|
||||
--secondary-foreground: 222 47% 11%; /* Dark text */
|
||||
--accent: 210 40% 96%; /* Accent bg */
|
||||
--accent-foreground: 222 47% 11%; /* Accent text */
|
||||
--muted: 210 40% 96%; /* Muted bg */
|
||||
--muted-foreground: 215 16% 47%; /* Muted text */
|
||||
--accent: 221 75% 95%; /* Soft blue tint */
|
||||
--accent-foreground: 221 70% 35%; /* Rich blue text */
|
||||
--muted: 220 15% 92%; /* Subtle muted bg */
|
||||
--muted-foreground: 215 25% 40%; /* Darker muted text */
|
||||
|
||||
/* Status Colors */
|
||||
--success: 142 71% 45%;
|
||||
--success-foreground: 142 76% 15%;
|
||||
--error: 0 72% 51%;
|
||||
--error-foreground: 0 86% 17%;
|
||||
--warning: 38 92% 50%;
|
||||
--warning-foreground: 48 96% 19%;
|
||||
--info: 217 91% 60%;
|
||||
--info-foreground: 214 95% 23%;
|
||||
/* Status Colors - Modern Editorial Palette */
|
||||
--success: 152 57% 45%; /* Refined emerald green */
|
||||
--success-foreground: 0 0% 100%; /* White text on success */
|
||||
--error: 0 65% 51%; /* Sophisticated red, less harsh */
|
||||
--error-foreground: 0 0% 100%; /* White text on error */
|
||||
--warning: 36 100% 50%; /* Warm sophisticated amber */
|
||||
--warning-foreground: 0 0% 100%; /* White text on warning */
|
||||
--info: 210 100% 56%; /* Cool modern blue */
|
||||
--info-foreground: 0 0% 100%; /* White text on info */
|
||||
|
||||
/* Destructive (legacy) */
|
||||
--destructive: 0 84% 60%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--destructive: 0 65% 51%; /* Match error color */
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
|
||||
/* Form Elements */
|
||||
--input: 214 32% 91%;
|
||||
|
|
@ -52,11 +52,11 @@
|
|||
--ring-offset: 0 0% 100%;
|
||||
|
||||
/* Grid/Blueprint Pattern */
|
||||
--grid-color: 214 32% 95%;
|
||||
--grid-color: 214 32% 90%;
|
||||
--grid-size: 24px; /* Grid cell size */
|
||||
|
||||
/* Card */
|
||||
--card: 0 0% 100%;
|
||||
--card: 0 0% 100%; /* Pure white - stands out dramatically on blue-gray background */
|
||||
--card-foreground: 222 47% 11%;
|
||||
|
||||
/* Border Radii */
|
||||
|
|
@ -93,9 +93,9 @@
|
|||
--tracking-wider: 0.05em;
|
||||
--tracking-widest: 0.1em;
|
||||
|
||||
/* Brutalist Accent Colors */
|
||||
--brutalist-accent: 221 83% 53%; /* #0066ff - Electric blue for light mode */
|
||||
--brutalist-accent-hover: 221 83% 43%;
|
||||
/* Brutalist Accent Colors - Modern Editorial */
|
||||
--brutalist-accent: 221 83% 53%; /* Sophisticated blue */
|
||||
--brutalist-accent-hover: 221 83% 45%; /* Slightly deeper on hover */
|
||||
}
|
||||
|
||||
/* Dark mode (opt-in) - Vercel/Cursor/Perplexity aesthetic */
|
||||
|
|
@ -127,19 +127,19 @@
|
|||
--muted: 210 10% 12%; /* Muted background */
|
||||
--muted-foreground: 210 8% 60%; /* Muted text */
|
||||
|
||||
/* Status Colors (desaturated for dark mode) */
|
||||
--success: 142 71% 45%; /* #10b981 - Green */
|
||||
--success-foreground: 142 76% 95%; /* Light green text */
|
||||
--error: 0 72% 51%; /* #ef4444 - Red */
|
||||
--error-foreground: 0 86% 97%; /* Light red text */
|
||||
--warning: 38 92% 50%; /* #f59e0b - Amber */
|
||||
--warning-foreground: 48 96% 89%; /* Light amber text */
|
||||
--info: 217 91% 60%; /* #3b82f6 - Blue */
|
||||
--info-foreground: 214 95% 93%; /* Light blue text */
|
||||
/* Status Colors - Modern Editorial (Dark Mode) */
|
||||
--success: 152 57% 50%; /* Refined emerald - slightly brighter for dark */
|
||||
--success-foreground: 0 0% 100%; /* White text */
|
||||
--error: 0 65% 58%; /* Sophisticated red - brighter for dark */
|
||||
--error-foreground: 0 0% 100%; /* White text */
|
||||
--warning: 36 100% 55%; /* Warm amber - brighter for dark */
|
||||
--warning-foreground: 0 0% 100%; /* White text */
|
||||
--info: 210 100% 60%; /* Cool blue - brighter for dark */
|
||||
--info-foreground: 0 0% 100%; /* White text */
|
||||
|
||||
/* Destructive (legacy support) */
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive-foreground: 0 86% 97%;
|
||||
--destructive: 0 65% 58%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
|
||||
/* Form Elements */
|
||||
--input: 210 10% 20%; /* Input border */
|
||||
|
|
@ -153,9 +153,9 @@
|
|||
--card: 210 10% 8%;
|
||||
--card-foreground: 210 10% 90%;
|
||||
|
||||
/* Brutalist Accent Colors */
|
||||
--brutalist-accent: 158 100% 50%; /* #00ff88 - Neon green for dark mode */
|
||||
--brutalist-accent-hover: 158 100% 40%;
|
||||
/* Brutalist Accent Colors - Modern Editorial */
|
||||
--brutalist-accent: 210 100% 60%; /* Cool sophisticated blue for dark mode */
|
||||
--brutalist-accent-hover: 210 100% 65%; /* Slightly brighter on hover */
|
||||
}
|
||||
|
||||
/* Base element styles */
|
||||
|
|
|
|||
|
|
@ -9,21 +9,16 @@ import { HeroSection } from '../components/home/HeroSection';
|
|||
async function getHomePageData() {
|
||||
try {
|
||||
// Fetch stats in parallel
|
||||
const [toolCount, simulationCount, featuredTools, categoryStats] = await Promise.all([
|
||||
const [packageCount, toolCount, featuredTools, categoryStats] = await Promise.all([
|
||||
// Total package count
|
||||
prisma.package.count(),
|
||||
|
||||
// Total tool count
|
||||
prisma.tool.count(),
|
||||
|
||||
// Total successful simulations (as proxy for invocations)
|
||||
prisma.simulation.count({
|
||||
where: { status: 'success' },
|
||||
}),
|
||||
|
||||
// Top 6 featured tools by quality score
|
||||
prisma.tool.findMany({
|
||||
orderBy: [
|
||||
{ qualityScore: 'desc' },
|
||||
{ package: { npmDownloadsLastMonth: 'desc' } },
|
||||
],
|
||||
orderBy: [{ qualityScore: 'desc' }, { package: { npmDownloadsLastMonth: 'desc' } }],
|
||||
take: 6,
|
||||
select: {
|
||||
id: true,
|
||||
|
|
@ -50,30 +45,10 @@ async function getHomePageData() {
|
|||
}),
|
||||
]);
|
||||
|
||||
// Calculate average latency from recent successful simulations
|
||||
const recentSimulations = await prisma.simulation.findMany({
|
||||
where: {
|
||||
status: 'success',
|
||||
executionTimeMs: { not: null },
|
||||
},
|
||||
select: { executionTimeMs: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100,
|
||||
});
|
||||
|
||||
const avgLatency =
|
||||
recentSimulations.length > 0
|
||||
? Math.round(
|
||||
recentSimulations.reduce((sum, s) => sum + (s.executionTimeMs || 0), 0) /
|
||||
recentSimulations.length
|
||||
)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
stats: {
|
||||
packageCount,
|
||||
toolCount,
|
||||
invocations: simulationCount,
|
||||
avgLatency,
|
||||
categoryCount: categoryStats.length,
|
||||
},
|
||||
featuredTools,
|
||||
|
|
@ -86,9 +61,8 @@ async function getHomePageData() {
|
|||
console.error('Failed to fetch homepage data:', error);
|
||||
return {
|
||||
stats: {
|
||||
packageCount: 0,
|
||||
toolCount: 0,
|
||||
invocations: 0,
|
||||
avgLatency: 0,
|
||||
categoryCount: 0,
|
||||
},
|
||||
featuredTools: [],
|
||||
|
|
@ -238,17 +212,30 @@ export default async function HomePage(): Promise<React.ReactElement> {
|
|||
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<p className="text-sm text-foreground-secondary">© 2025 TPMJS. All rights reserved.</p>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<button type="button" className="text-foreground-secondary hover:text-foreground">
|
||||
Privacy
|
||||
</button>
|
||||
<span className="text-border">·</span>
|
||||
<button type="button" className="text-foreground-secondary hover:text-foreground">
|
||||
Terms
|
||||
</button>
|
||||
<span className="text-border">·</span>
|
||||
<button type="button" className="text-foreground-secondary hover:text-foreground">
|
||||
<a
|
||||
href="mailto:hello@tpmjs.com"
|
||||
className="text-foreground-secondary hover:text-foreground transition-colors"
|
||||
>
|
||||
Contact
|
||||
</button>
|
||||
</a>
|
||||
<span className="text-border">·</span>
|
||||
<a
|
||||
href="https://github.com/your-org/tpmjs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-foreground-secondary hover:text-foreground transition-colors"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<span className="text-border">·</span>
|
||||
<a
|
||||
href="https://twitter.com/tpmjs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-foreground-secondary hover:text-foreground transition-colors"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@ export default function ToolDetailPage({
|
|||
)}
|
||||
|
||||
{/* Tags */}
|
||||
{tool.tags.length > 0 && (
|
||||
{tool.tags && tool.tags.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Tags</CardTitle>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ export default function ToolSearchPage(): React.ReactElement {
|
|||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [availableCategories, setAvailableCategories] = useState<string[]>([]);
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
const [officialCount, setOfficialCount] = useState(0);
|
||||
|
||||
// Fetch tools from API
|
||||
useEffect(() => {
|
||||
|
|
@ -79,14 +81,32 @@ export default function ToolSearchPage(): React.ReactElement {
|
|||
params.set('broken', 'true');
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/tools?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
// Fetch tools and counts in parallel
|
||||
const [toolsResponse, allCountResponse, officialCountResponse] = await Promise.all([
|
||||
fetch(`/api/tools?${params.toString()}`),
|
||||
fetch('/api/tools'),
|
||||
fetch('/api/tools?official=true'),
|
||||
]);
|
||||
|
||||
if (data.success) {
|
||||
const fetchedTools = data.data;
|
||||
const [toolsData, allCountData, officialCountData] = await Promise.all([
|
||||
toolsResponse.json(),
|
||||
allCountResponse.json(),
|
||||
officialCountResponse.json(),
|
||||
]);
|
||||
|
||||
if (toolsData.success) {
|
||||
const fetchedTools = toolsData.data;
|
||||
setTools(fetchedTools);
|
||||
setError(null);
|
||||
|
||||
// Update counts
|
||||
if (allCountData.success) {
|
||||
setTotalCount(allCountData.data.length);
|
||||
}
|
||||
if (officialCountData.success) {
|
||||
setOfficialCount(officialCountData.data.length);
|
||||
}
|
||||
|
||||
// Extract unique categories from all tools
|
||||
const categories = new Set<string>();
|
||||
|
||||
|
|
@ -98,7 +118,7 @@ export default function ToolSearchPage(): React.ReactElement {
|
|||
|
||||
setAvailableCategories(Array.from(categories).sort());
|
||||
} else {
|
||||
setError(data.error || 'Failed to fetch tools');
|
||||
setError(toolsData.error || 'Failed to fetch tools');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
|
|
@ -186,11 +206,11 @@ export default function ToolSearchPage(): React.ReactElement {
|
|||
{/* Tabs */}
|
||||
<Tabs
|
||||
tabs={[
|
||||
{ id: 'all', label: 'All Tools', count: tools.length },
|
||||
{ id: 'all', label: 'All Tools', count: totalCount },
|
||||
{
|
||||
id: 'featured',
|
||||
label: 'Official',
|
||||
count: tools.filter((t) => t.package.isOfficial).length,
|
||||
count: officialCount,
|
||||
},
|
||||
]}
|
||||
activeTab={activeTab}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ import { useState } from 'react';
|
|||
|
||||
interface HeroSectionProps {
|
||||
stats: {
|
||||
packageCount: number;
|
||||
toolCount: number;
|
||||
invocations: number;
|
||||
avgLatency: number;
|
||||
categoryCount: number;
|
||||
};
|
||||
}
|
||||
|
|
@ -71,23 +70,15 @@ export function HeroSection({ stats }: HeroSectionProps): React.ReactElement {
|
|||
|
||||
{/* Live Metrics Strip */}
|
||||
<div className="mb-12 flex flex-wrap items-center gap-3 border-l-[6px] border-brutalist-accent pl-6 font-mono text-base md:text-lg font-bold uppercase tracking-wider">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">{formatNumber(stats.packageCount)}</span>
|
||||
<span className="text-foreground-secondary">PACKAGES</span>
|
||||
</div>
|
||||
<span className="text-foreground-tertiary">/</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">{formatNumber(stats.toolCount)}</span>
|
||||
<span className="text-foreground-secondary">TOOLS</span>
|
||||
</div>
|
||||
<span className="text-foreground-tertiary">/</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">
|
||||
{formatNumber(stats.invocations)}
|
||||
{stats.invocations >= 1000 ? '+' : ''}
|
||||
</span>
|
||||
<span className="text-foreground-secondary">INVOCATIONS</span>
|
||||
</div>
|
||||
<span className="text-foreground-tertiary">/</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-foreground">{stats.avgLatency || '--'}ms</span>
|
||||
<span className="text-foreground-secondary">AVG LATENCY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subheading */}
|
||||
|
|
|
|||
|
|
@ -109,9 +109,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
|
|||
|
||||
console.log('[createToolDefinition] Created Zod schema:', inputSchema);
|
||||
|
||||
const sanitizedName = sanitizeToolName(
|
||||
`${tool.package.npmPackageName}-${tool.exportName}`
|
||||
);
|
||||
const sanitizedName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.exportName}`);
|
||||
|
||||
// AI SDK v6 tool definition
|
||||
return {
|
||||
|
|
@ -199,9 +197,7 @@ export async function executeToolWithAgent(
|
|||
onTokenUpdate?: (tokens: Partial<TokenBreakdown>) => void
|
||||
) {
|
||||
const toolDef = createToolDefinition(tool);
|
||||
const sanitizedToolName = sanitizeToolName(
|
||||
`${tool.package.npmPackageName}-${tool.exportName}`
|
||||
);
|
||||
const sanitizedToolName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.exportName}`);
|
||||
|
||||
console.log('[executeToolWithAgent] Tool name:', sanitizedToolName);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,17 +10,10 @@
|
|||
"clean": "rm -rf dist",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"tpmjs-tool",
|
||||
"ai-sdk",
|
||||
"hello",
|
||||
"example"
|
||||
],
|
||||
"keywords": ["tpmjs-tool", "ai-sdk", "hello", "example"],
|
||||
"tpmjs": {
|
||||
"category": "text-analysis",
|
||||
"frameworks": [
|
||||
"vercel-ai"
|
||||
],
|
||||
"frameworks": ["vercel-ai"],
|
||||
"tools": [
|
||||
{
|
||||
"exportName": "helloWorldTool",
|
||||
|
|
@ -70,8 +63,5 @@
|
|||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
"files": ["dist", "README.md"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ export const searchTpmjsToolsTool = tool({
|
|||
env: tool.package.env,
|
||||
version: tool.package.npmVersion,
|
||||
importUrl: `https://esm.sh/${tool.package.npmPackageName}@${tool.package.npmVersion}`,
|
||||
importHealth: tool.importHealth,
|
||||
executionHealth: tool.executionHealth,
|
||||
healthCheckError: tool.healthCheckError,
|
||||
lastHealthCheck: tool.lastHealthCheck,
|
||||
})),
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -170,9 +170,10 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
|
|||
const data = multiResult.data;
|
||||
|
||||
// Determine tier based on tool richness
|
||||
const hasRichFields = data.tools.some(
|
||||
(tool) => tool.parameters || tool.returns || tool.aiAgent
|
||||
) || data.env || data.frameworks;
|
||||
const hasRichFields =
|
||||
data.tools.some((tool) => tool.parameters || tool.returns || tool.aiAgent) ||
|
||||
data.env ||
|
||||
data.frameworks;
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
|
|
@ -203,8 +204,11 @@ export function validateTpmjsField(tpmjs: unknown): ValidationResult {
|
|||
};
|
||||
|
||||
const hasRichFields =
|
||||
legacyData.parameters || legacyData.returns || legacyData.env ||
|
||||
legacyData.frameworks || legacyData.aiAgent;
|
||||
legacyData.parameters ||
|
||||
legacyData.returns ||
|
||||
legacyData.env ||
|
||||
legacyData.frameworks ||
|
||||
legacyData.aiAgent;
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
|
|
|
|||
|
|
@ -134,6 +134,14 @@
|
|||
"./ActivityStream/ActivityStream": {
|
||||
"types": "./dist/ActivityStream/ActivityStream.d.ts",
|
||||
"default": "./dist/ActivityStream/ActivityStream.js"
|
||||
},
|
||||
"./ToolHealthBadge/ToolHealthBadge": {
|
||||
"types": "./dist/ToolHealthBadge/ToolHealthBadge.d.ts",
|
||||
"default": "./dist/ToolHealthBadge/ToolHealthBadge.js"
|
||||
},
|
||||
"./ToolHealthBanner/ToolHealthBanner": {
|
||||
"types": "./dist/ToolHealthBanner/ToolHealthBanner.d.ts",
|
||||
"default": "./dist/ToolHealthBanner/ToolHealthBanner.js"
|
||||
}
|
||||
},
|
||||
"files": ["dist"],
|
||||
|
|
@ -152,6 +160,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@tpmjs/utils": "workspace:*",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -163,6 +172,7 @@
|
|||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"eslint": "^9.39.1",
|
||||
"glob": "^13.0.0",
|
||||
"happy-dom": "^15.11.7",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { cn } from '@tpmjs/utils/cn';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import type { CodeBlockProps } from './types';
|
||||
import {
|
||||
|
|
@ -8,11 +10,75 @@ import {
|
|||
codeBlockCopyButtonVariants,
|
||||
} from './variants';
|
||||
|
||||
// Custom light theme with better contrast
|
||||
const customLightTheme = {
|
||||
'code[class*="language-"]': {
|
||||
color: '#24292e',
|
||||
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
|
||||
fontSize: '1em',
|
||||
textAlign: 'left',
|
||||
whiteSpace: 'pre',
|
||||
wordSpacing: 'normal',
|
||||
wordBreak: 'normal',
|
||||
wordWrap: 'normal',
|
||||
lineHeight: '1.5',
|
||||
tabSize: '4',
|
||||
hyphens: 'none',
|
||||
},
|
||||
'pre[class*="language-"]': {
|
||||
color: '#24292e',
|
||||
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
|
||||
fontSize: '1em',
|
||||
textAlign: 'left',
|
||||
whiteSpace: 'pre',
|
||||
wordSpacing: 'normal',
|
||||
wordBreak: 'normal',
|
||||
wordWrap: 'normal',
|
||||
lineHeight: '1.5',
|
||||
tabSize: '4',
|
||||
hyphens: 'none',
|
||||
padding: '1em',
|
||||
margin: '0.5em 0',
|
||||
overflow: 'auto',
|
||||
},
|
||||
comment: { color: '#6a737d', fontStyle: 'italic' },
|
||||
prolog: { color: '#6a737d' },
|
||||
doctype: { color: '#6a737d' },
|
||||
cdata: { color: '#6a737d' },
|
||||
punctuation: { color: '#24292e' },
|
||||
property: { color: '#005cc5' },
|
||||
tag: { color: '#22863a' },
|
||||
boolean: { color: '#005cc5' },
|
||||
number: { color: '#005cc5' },
|
||||
constant: { color: '#005cc5' },
|
||||
symbol: { color: '#005cc5' },
|
||||
deleted: { color: '#b31d28' },
|
||||
selector: { color: '#22863a' },
|
||||
'attr-name': { color: '#6f42c1' },
|
||||
string: { color: '#032f62' },
|
||||
char: { color: '#032f62' },
|
||||
builtin: { color: '#005cc5' },
|
||||
inserted: { color: '#22863a' },
|
||||
operator: { color: '#d73a49' },
|
||||
entity: { color: '#6f42c1' },
|
||||
url: { color: '#032f62' },
|
||||
variable: { color: '#e36209' },
|
||||
atrule: { color: '#d73a49' },
|
||||
'attr-value': { color: '#032f62' },
|
||||
function: { color: '#6f42c1' },
|
||||
'class-name': { color: '#6f42c1' },
|
||||
keyword: { color: '#d73a49' },
|
||||
regex: { color: '#032f62' },
|
||||
important: { color: '#d73a49', fontWeight: 'bold' },
|
||||
bold: { fontWeight: 'bold' },
|
||||
italic: { fontStyle: 'italic' },
|
||||
};
|
||||
|
||||
/**
|
||||
* CodeBlock component
|
||||
*
|
||||
* Displays formatted code with optional copy functionality.
|
||||
* Includes syntax-highlighted display and copy-to-clipboard button.
|
||||
* Displays formatted code with syntax highlighting and optional copy functionality.
|
||||
* Uses Prism syntax highlighter for beautiful code display.
|
||||
* Built with React and JSX.
|
||||
*
|
||||
* @example
|
||||
|
|
@ -32,7 +98,10 @@ import {
|
|||
* ```
|
||||
*/
|
||||
export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
({ className, code, language = 'text', size = 'md', showCopy = true, ...props }, ref) => {
|
||||
(
|
||||
{ className, code, language = 'text', size = 'md', showCopy = true, theme = 'light', ...props },
|
||||
ref
|
||||
) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
|
|
@ -46,16 +115,42 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
|||
}
|
||||
};
|
||||
|
||||
// Map language aliases to supported languages
|
||||
const languageMap: Record<string, string> = {
|
||||
js: 'javascript',
|
||||
ts: 'typescript',
|
||||
jsx: 'jsx',
|
||||
tsx: 'tsx',
|
||||
py: 'python',
|
||||
rb: 'ruby',
|
||||
sh: 'bash',
|
||||
yml: 'yaml',
|
||||
};
|
||||
|
||||
const normalizedLanguage = languageMap[language] || language;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cn(codeBlockContainerVariants(), className)} {...props}>
|
||||
<code
|
||||
className={codeBlockCodeVariants({
|
||||
size,
|
||||
})}
|
||||
data-language={language}
|
||||
>
|
||||
{code}
|
||||
</code>
|
||||
<div className={codeBlockCodeVariants({ size })} data-language={normalizedLanguage}>
|
||||
<SyntaxHighlighter
|
||||
language={normalizedLanguage}
|
||||
style={theme === 'dark' ? oneDark : oneLight}
|
||||
customStyle={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
background: 'transparent',
|
||||
fontSize: 'inherit',
|
||||
lineHeight: 'inherit',
|
||||
}}
|
||||
codeTagProps={{
|
||||
style: {
|
||||
fontFamily: 'inherit',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
{showCopy && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, 'ch
|
|||
code: string;
|
||||
|
||||
/**
|
||||
* Programming language (for display/metadata only)
|
||||
* Programming language for syntax highlighting
|
||||
* @default 'text'
|
||||
*/
|
||||
language?: string;
|
||||
|
|
@ -26,6 +26,12 @@ export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, 'ch
|
|||
* @default true
|
||||
*/
|
||||
showCopy?: boolean;
|
||||
|
||||
/**
|
||||
* Color theme for syntax highlighting
|
||||
* @default 'light'
|
||||
*/
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export const inputVariants = createVariants({
|
|||
'font-sans',
|
||||
// Borders & Radius
|
||||
'rounded-md border',
|
||||
// Background
|
||||
'bg-background',
|
||||
// Background - Pure white to stand out
|
||||
'bg-surface',
|
||||
// Transitions
|
||||
'transition-base',
|
||||
// Focus
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export const textareaVariants = createVariants({
|
|||
'font-sans',
|
||||
// Borders & Radius
|
||||
'rounded-md border',
|
||||
// Background
|
||||
'bg-background',
|
||||
// Background - Pure white to stand out
|
||||
'bg-surface',
|
||||
// Transitions
|
||||
'transition-base',
|
||||
// Focus
|
||||
|
|
|
|||
35
packages/ui/src/ToolHealthBadge/ToolHealthBadge.tsx
Normal file
35
packages/ui/src/ToolHealthBadge/ToolHealthBadge.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
'use client';
|
||||
|
||||
import type React from 'react';
|
||||
import { Badge } from '../Badge/Badge';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
export interface ToolHealthBadgeProps {
|
||||
importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||
executionHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Badge component to indicate if a tool is broken (import or execution failed)
|
||||
*/
|
||||
export function ToolHealthBadge({
|
||||
importHealth,
|
||||
executionHealth,
|
||||
size = 'sm',
|
||||
className,
|
||||
}: ToolHealthBadgeProps): React.ReactElement | null {
|
||||
const isBroken = importHealth === 'BROKEN' || executionHealth === 'BROKEN';
|
||||
|
||||
if (!isBroken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge variant="error" size={size} className={className}>
|
||||
<Icon icon="x" size="sm" className="mr-1" />
|
||||
Broken
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
86
packages/ui/src/ToolHealthBanner/ToolHealthBanner.tsx
Normal file
86
packages/ui/src/ToolHealthBanner/ToolHealthBanner.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
'use client';
|
||||
|
||||
import type React from 'react';
|
||||
import { Badge } from '../Badge/Badge';
|
||||
|
||||
export interface ToolHealthBannerProps {
|
||||
importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||
executionHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
|
||||
healthCheckError?: string | null;
|
||||
lastHealthCheck?: string | null;
|
||||
onRecheck?: () => void;
|
||||
recheckLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Banner component to display detailed health status for broken tools
|
||||
*/
|
||||
export function ToolHealthBanner({
|
||||
importHealth,
|
||||
executionHealth,
|
||||
healthCheckError,
|
||||
lastHealthCheck,
|
||||
onRecheck,
|
||||
recheckLoading = false,
|
||||
className,
|
||||
}: ToolHealthBannerProps): React.ReactElement | null {
|
||||
const isBroken = importHealth === 'BROKEN' || executionHealth === 'BROKEN';
|
||||
|
||||
if (!isBroken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`p-4 rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900 ${className || ''}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-xl mt-0.5">⚠️</span>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-semibold text-red-800 dark:text-red-300 mb-1">
|
||||
This tool is currently broken
|
||||
</h3>
|
||||
<div className="space-y-1 text-sm text-red-700 dark:text-red-400">
|
||||
{importHealth === 'BROKEN' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="error" size="sm">
|
||||
Import Failed
|
||||
</Badge>
|
||||
<span className="text-xs">Cannot load from Railway service</span>
|
||||
</div>
|
||||
)}
|
||||
{executionHealth === 'BROKEN' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="error" size="sm">
|
||||
Execution Failed
|
||||
</Badge>
|
||||
<span className="text-xs">Runtime error with test parameters</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{healthCheckError && (
|
||||
<pre className="mt-2 p-2 rounded bg-red-100 dark:bg-red-900/30 text-xs font-mono text-red-800 dark:text-red-300 overflow-x-auto whitespace-pre-wrap">
|
||||
{healthCheckError}
|
||||
</pre>
|
||||
)}
|
||||
{lastHealthCheck && (
|
||||
<p className="text-xs text-red-600 dark:text-red-500 mt-2">
|
||||
Last checked: {new Date(lastHealthCheck).toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
{onRecheck && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRecheck}
|
||||
disabled={recheckLoading}
|
||||
className="mt-3 text-sm font-medium text-red-700 dark:text-red-400 hover:underline disabled:opacity-50"
|
||||
>
|
||||
{recheckLoading ? 'Rechecking...' : 'Recheck health →'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@ export const formInputBase = [
|
|||
'font-sans text-base',
|
||||
// Borders and radius
|
||||
'rounded-md border border-border',
|
||||
// Colors
|
||||
'bg-background text-foreground',
|
||||
// Colors - Pure white background to stand out
|
||||
'bg-surface text-foreground',
|
||||
// Placeholder
|
||||
'placeholder:text-foreground-tertiary',
|
||||
// Transitions
|
||||
|
|
|
|||
253
pnpm-lock.yaml
generated
253
pnpm-lock.yaml
generated
|
|
@ -131,10 +131,10 @@ importers:
|
|||
version: 10.4.22(postcss@8.5.6)
|
||||
eslint:
|
||||
specifier: ^9.39.1
|
||||
version: 9.39.1(jiti@1.21.7)
|
||||
version: 9.39.1(jiti@2.6.1)
|
||||
eslint-config-next:
|
||||
specifier: ^16.0.4
|
||||
version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||
postcss:
|
||||
specifier: ^8.5.1
|
||||
version: 8.5.6
|
||||
|
|
@ -571,6 +571,9 @@ importers:
|
|||
'@tpmjs/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../utils
|
||||
react-syntax-highlighter:
|
||||
specifier: ^16.1.0
|
||||
version: 16.1.0(react@19.2.0)
|
||||
zod:
|
||||
specifier: ^4.1.13
|
||||
version: 4.1.13
|
||||
|
|
@ -599,6 +602,9 @@ importers:
|
|||
'@types/react-dom':
|
||||
specifier: ^19.0.2
|
||||
version: 19.2.3(@types/react@19.2.7)
|
||||
'@types/react-syntax-highlighter':
|
||||
specifier: ^15.5.13
|
||||
version: 15.5.13
|
||||
eslint:
|
||||
specifier: ^9.39.1
|
||||
version: 9.39.1(jiti@2.6.1)
|
||||
|
|
@ -6882,11 +6888,6 @@ snapshots:
|
|||
'@esbuild/win32-x64@0.27.0':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@1.21.7))':
|
||||
dependencies:
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))':
|
||||
dependencies:
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
|
|
@ -7940,23 +7941,6 @@ snapshots:
|
|||
|
||||
'@types/uuid@9.0.8': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.48.0
|
||||
'@typescript-eslint/type-utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.48.0
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
graphemer: 1.4.0
|
||||
ignore: 7.0.5
|
||||
natural-compare: 1.4.0
|
||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
|
|
@ -7974,18 +7958,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.48.0
|
||||
'@typescript-eslint/types': 8.48.0
|
||||
'@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.48.0
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.48.0
|
||||
|
|
@ -8016,18 +7988,6 @@ snapshots:
|
|||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.48.0
|
||||
'@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/type-utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.48.0
|
||||
|
|
@ -8057,17 +8017,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
|
||||
'@typescript-eslint/scope-manager': 8.48.0
|
||||
'@typescript-eslint/types': 8.48.0
|
||||
'@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
|
||||
|
|
@ -9246,32 +9195,12 @@ snapshots:
|
|||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
eslint-config-next@16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 16.0.4
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7))
|
||||
eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@1.21.7))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7))
|
||||
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@1.21.7))
|
||||
globals: 16.4.0
|
||||
typescript-eslint: 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-webpack
|
||||
- eslint-plugin-import-x
|
||||
- supports-color
|
||||
|
||||
eslint-config-next@16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 16.0.4
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
|
||||
|
|
@ -9294,22 +9223,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
get-tsconfig: 4.13.0
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
|
|
@ -9334,23 +9248,13 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -9383,33 +9287,6 @@ snapshots:
|
|||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
array.prototype.findlastindex: 1.2.6
|
||||
array.prototype.flat: 1.3.3
|
||||
array.prototype.flatmap: 1.3.3
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
object.fromentries: 2.0.8
|
||||
object.groupby: 1.0.3
|
||||
object.values: 1.2.1
|
||||
semver: 6.3.1
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
|
|
@ -9421,7 +9298,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -9437,25 +9314,6 @@ snapshots:
|
|||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@1.21.7)):
|
||||
dependencies:
|
||||
aria-query: 5.3.2
|
||||
array-includes: 3.1.9
|
||||
array.prototype.flatmap: 1.3.3
|
||||
ast-types-flow: 0.0.8
|
||||
axe-core: 4.11.0
|
||||
axobject-query: 4.1.0
|
||||
damerau-levenshtein: 1.0.8
|
||||
emoji-regex: 9.2.2
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
hasown: 2.0.2
|
||||
jsx-ast-utils: 3.3.5
|
||||
language-tags: 1.0.9
|
||||
minimatch: 3.1.2
|
||||
object.fromentries: 2.0.8
|
||||
safe-regex-test: 1.1.0
|
||||
string.prototype.includes: 2.0.1
|
||||
|
||||
eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
aria-query: 5.3.2
|
||||
|
|
@ -9479,17 +9337,6 @@ snapshots:
|
|||
dependencies:
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
|
||||
eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@1.21.7)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@babel/parser': 7.28.5
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
hermes-parser: 0.25.1
|
||||
zod: 4.1.13
|
||||
zod-validation-error: 4.0.2(zod@4.1.13)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
|
|
@ -9501,28 +9348,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)):
|
||||
dependencies:
|
||||
array-includes: 3.1.9
|
||||
array.prototype.findlast: 1.2.5
|
||||
array.prototype.flatmap: 1.3.3
|
||||
array.prototype.tosorted: 1.1.4
|
||||
doctrine: 2.1.0
|
||||
es-iterator-helpers: 1.2.1
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
estraverse: 5.3.0
|
||||
hasown: 2.0.2
|
||||
jsx-ast-utils: 3.3.5
|
||||
minimatch: 3.1.2
|
||||
object.entries: 1.1.9
|
||||
object.fromentries: 2.0.8
|
||||
object.values: 1.2.1
|
||||
prop-types: 15.8.1
|
||||
resolve: 2.0.0-next.5
|
||||
semver: 6.3.1
|
||||
string.prototype.matchall: 4.0.12
|
||||
string.prototype.repeat: 1.0.0
|
||||
|
||||
eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
array-includes: 3.1.9
|
||||
|
|
@ -9554,47 +9379,6 @@ snapshots:
|
|||
|
||||
eslint-visitor-keys@4.2.1: {}
|
||||
|
||||
eslint@9.39.1(jiti@1.21.7):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.1
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.1
|
||||
'@eslint/js': 9.39.1
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@humanfs/node': 0.16.7
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.6.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 8.0.0
|
||||
find-up: 5.0.0
|
||||
glob-parent: 6.0.2
|
||||
ignore: 5.3.2
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.2
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
jiti: 1.21.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint@9.39.1(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
|
||||
|
|
@ -12448,17 +12232,6 @@ snapshots:
|
|||
possible-typed-array-names: 1.1.0
|
||||
reflect.getprototypeof: 1.0.10
|
||||
|
||||
typescript-eslint@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.48.0(typescript@5.9.3)
|
||||
'@typescript-eslint/utils': 8.48.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
typescript-eslint@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { prisma } from './packages/db/src/index.js';
|
||||
import { manualTools } from './manual-tools.js';
|
||||
import { prisma } from './packages/db/src/index.js';
|
||||
import { fetchLatestPackageWithMetadata } from './packages/npm-client/src/package.js';
|
||||
|
||||
async function syncManualTools() {
|
||||
console.log('\n🔧 Starting manual tools sync...\n');
|
||||
|
||||
let processed = 0;
|
||||
let skipped = 0;
|
||||
const skipped = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const manualTool of manualTools) {
|
||||
|
|
@ -26,7 +26,8 @@ async function syncManualTools() {
|
|||
const version = manualTool.npmVersion || npmData.version;
|
||||
|
||||
// Get published date
|
||||
const publishedAt = npmData.time?.[version] || npmData.time?.modified || new Date().toISOString();
|
||||
const publishedAt =
|
||||
npmData.time?.[version] || npmData.time?.modified || new Date().toISOString();
|
||||
|
||||
// Upsert Package record
|
||||
const packageRecord = await prisma.package.upsert({
|
||||
|
|
@ -119,7 +120,7 @@ async function syncManualTools() {
|
|||
console.log(` Total manual tools: ${manualTools.length}\n`);
|
||||
}
|
||||
|
||||
function calculateTier(tool: typeof manualTools[0]): 'minimal' | 'rich' {
|
||||
function calculateTier(tool: (typeof manualTools)[0]): 'minimal' | 'rich' {
|
||||
// Tier is 'rich' if tool has parameters OR returns OR aiAgent
|
||||
if (tool.parameters || tool.returns || tool.aiAgent) {
|
||||
return 'rich';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue