refactor: rename exportName to name across entire codebase

- Database: Migrate column export_name to name in tools table
- Prisma schema: Update Tool model to use name field
- Sync routes: Update keyword and changes sync to use name
- Railway executor: Update API endpoints to use name parameter
- API routes: Update all tool routes to use name field
- Web app: Update all pages and components
- Playground: Update tool loader and sidebar
- create-basic-tools: Update types and generators
- Scripts: Update sync and test scripts

Database migration was done via direct SQL:
  ALTER TABLE tools RENAME COLUMN export_name TO name;

The unique constraint remains on (package_id, name).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-17 17:19:21 +10:00
parent deb9e3ae06
commit e84eda7525
44 changed files with 222 additions and 233 deletions

View file

@ -144,7 +144,7 @@ export async function POST(request: NextRequest) {
if (searchResult.tools && searchResult.tools.length > 0) { if (searchResult.tools && searchResult.tools.length > 0) {
console.log( console.log(
'🔧 Tools found:', '🔧 Tools found:',
searchResult.tools.map((t: any) => `${t.packageName}/${t.exportName}`) searchResult.tools.map((t: any) => `${t.packageName}/${t.name}`)
); );
// Dynamically load tools from esm.sh // Dynamically load tools from esm.sh
@ -152,7 +152,7 @@ export async function POST(request: NextRequest) {
const toolsToLoad = searchResult.tools.map((meta: any) => ({ const toolsToLoad = searchResult.tools.map((meta: any) => ({
packageName: meta.packageName, packageName: meta.packageName,
exportName: meta.exportName, name: meta.name,
version: meta.version, version: meta.version,
importUrl: meta.importUrl, importUrl: meta.importUrl,
})); }));

View file

@ -5,7 +5,7 @@ export const dynamic = 'force-dynamic';
interface RawTool { interface RawTool {
id: string; id: string;
exportName: string; name: string;
description: string; description: string;
qualityScore: number; qualityScore: number;
importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
@ -25,7 +25,7 @@ function transformTool(tool: RawTool) {
return { return {
toolId: tool.id, toolId: tool.id,
packageName: tool.package?.npmPackageName, packageName: tool.package?.npmPackageName,
exportName: tool.exportName, name: tool.name,
description: tool.description, description: tool.description,
category: tool.package?.category, category: tool.package?.category,
version: tool.package?.npmVersion, version: tool.package?.npmVersion,

View file

@ -10,7 +10,7 @@ import { useEffect, useState } from 'react';
interface Tool { interface Tool {
toolId?: string; toolId?: string;
packageName: string; packageName: string;
exportName: string; name: string;
description: string; description: string;
category: string; category: string;
version: string; version: string;
@ -62,7 +62,7 @@ export function ToolsSidebar(): React.ReactElement {
// Filter by search text // Filter by search text
const matchesFilter = const matchesFilter =
tool.packageName?.toLowerCase().includes(filter.toLowerCase()) || tool.packageName?.toLowerCase().includes(filter.toLowerCase()) ||
tool.exportName?.toLowerCase().includes(filter.toLowerCase()) || tool.name?.toLowerCase().includes(filter.toLowerCase()) ||
tool.description?.toLowerCase().includes(filter.toLowerCase()) || tool.description?.toLowerCase().includes(filter.toLowerCase()) ||
tool.category?.toLowerCase().includes(filter.toLowerCase()); tool.category?.toLowerCase().includes(filter.toLowerCase());
@ -124,7 +124,7 @@ export function ToolsSidebar(): React.ReactElement {
filteredTools.map((tool) => ( filteredTools.map((tool) => (
// biome-ignore lint/a11y/useSemanticElements: Custom styled card with complex layout // biome-ignore lint/a11y/useSemanticElements: Custom styled card with complex layout
<div <div
key={`${tool.packageName}-${tool.exportName}`} key={`${tool.packageName}-${tool.name}`}
className="cursor-pointer rounded-lg border border-border bg-background p-3 transition-all hover:border-foreground-tertiary hover:shadow-sm" className="cursor-pointer rounded-lg border border-border bg-background p-3 transition-all hover:border-foreground-tertiary hover:shadow-sm"
onClick={() => setSelectedTool(tool)} onClick={() => setSelectedTool(tool)}
onKeyDown={(e) => e.key === 'Enter' && setSelectedTool(tool)} onKeyDown={(e) => e.key === 'Enter' && setSelectedTool(tool)}
@ -132,7 +132,7 @@ export function ToolsSidebar(): React.ReactElement {
tabIndex={0} tabIndex={0}
> >
<div className="mb-1 flex items-start justify-between gap-2"> <div className="mb-1 flex items-start justify-between gap-2">
<h3 className="text-sm font-medium leading-tight">{tool.exportName}</h3> <h3 className="text-sm font-medium leading-tight">{tool.name}</h3>
<ToolHealthBadge <ToolHealthBadge
importHealth={tool.importHealth} importHealth={tool.importHealth}
executionHealth={tool.executionHealth} executionHealth={tool.executionHealth}
@ -200,7 +200,7 @@ export function ToolsSidebar(): React.ReactElement {
{/* Tool header */} {/* Tool header */}
<div className="mb-6 border-b border-border pb-4"> <div className="mb-6 border-b border-border pb-4">
<h2 className="mb-2 text-2xl font-bold text-foreground">{selectedTool.exportName}</h2> <h2 className="mb-2 text-2xl font-bold text-foreground">{selectedTool.name}</h2>
<p className="mb-2 text-sm text-foreground-secondary">{selectedTool.packageName}</p> <p className="mb-2 text-sm text-foreground-secondary">{selectedTool.packageName}</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge variant="secondary">{selectedTool.category}</Badge> <Badge variant="secondary">{selectedTool.category}</Badge>

View file

@ -17,8 +17,8 @@ const RAILWAY_SERVICE_URL =
/** /**
* Generate cache key for a tool * Generate cache key for a tool
*/ */
function getCacheKey(packageName: string, exportName: string): string { function getCacheKey(packageName: string, name: string): string {
return `${packageName}::${exportName}`; return `${packageName}::${name}`;
} }
/** /**
@ -43,14 +43,14 @@ function getConversationEnv(conversationId: string): Record<string, string> {
*/ */
export async function loadToolDynamically( export async function loadToolDynamically(
packageName: string, packageName: string,
exportName: string, name: string,
version: string, version: string,
conversationId: string, conversationId: string,
importUrl?: string, importUrl?: string,
env?: Record<string, string> env?: Record<string, string>
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex // biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex
): Promise<any | null> { ): Promise<any | null> {
const cacheKey = getCacheKey(packageName, exportName); const cacheKey = getCacheKey(packageName, name);
// Check cache first // Check cache first
if (moduleCache.has(cacheKey)) { if (moduleCache.has(cacheKey)) {
@ -59,7 +59,7 @@ export async function loadToolDynamically(
} }
try { try {
console.log(`📦 Loading from Railway: ${packageName}/${exportName}`); console.log(`📦 Loading from Railway: ${packageName}/${name}`);
console.log(`🔗 Railway URL: ${RAILWAY_SERVICE_URL}`); console.log(`🔗 Railway URL: ${RAILWAY_SERVICE_URL}`);
// Call Railway service to load and describe tool // Call Railway service to load and describe tool
@ -78,7 +78,7 @@ export async function loadToolDynamically(
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName, packageName,
exportName, name,
version, version,
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`, importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
env: env || {}, env: env || {},
@ -105,7 +105,7 @@ export async function loadToolDynamically(
clearTimeout(timeout); clearTimeout(timeout);
if (fetchError instanceof Error && fetchError.name === 'AbortError') { if (fetchError instanceof Error && fetchError.name === 'AbortError') {
console.error(`❌ Railway request timeout after 120s for ${packageName}/${exportName}`); console.error(`❌ Railway request timeout after 120s for ${packageName}/${name}`);
return null; return null;
} }
@ -130,7 +130,7 @@ export async function loadToolDynamically(
: jsonSchema({ type: 'object', properties: {}, additionalProperties: false }), : jsonSchema({ type: 'object', properties: {}, additionalProperties: false }),
// biome-ignore lint/suspicious/noExplicitAny: Tool params are dynamic // biome-ignore lint/suspicious/noExplicitAny: Tool params are dynamic
execute: async (params: any) => { execute: async (params: any) => {
console.log(`🚀 Executing ${packageName}/${exportName} remotely with params:`, params); console.log(`🚀 Executing ${packageName}/${name} remotely with params:`, params);
// Get the latest env vars for this conversation (not from closure!) // Get the latest env vars for this conversation (not from closure!)
const currentEnv = getConversationEnv(conversationId); const currentEnv = getConversationEnv(conversationId);
@ -144,7 +144,7 @@ export async function loadToolDynamically(
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName, packageName,
exportName, name,
version, version,
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`, importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
params, params,
@ -171,7 +171,7 @@ export async function loadToolDynamically(
return toolWrapper; return toolWrapper;
} catch (error) { } catch (error) {
console.error(`❌ Failed to load ${packageName}#${exportName}:`, error); console.error(`❌ Failed to load ${packageName}#${name}:`, error);
console.error(' Stack:', error instanceof Error ? error.stack : 'No stack trace'); console.error(' Stack:', error instanceof Error ? error.stack : 'No stack trace');
return null; return null;
} }
@ -183,7 +183,7 @@ export async function loadToolDynamically(
export async function loadToolsBatch( export async function loadToolsBatch(
toolMetadata: Array<{ toolMetadata: Array<{
packageName: string; packageName: string;
exportName: string; name: string;
version: string; version: string;
importUrl?: string; importUrl?: string;
}>, }>,
@ -197,15 +197,15 @@ export async function loadToolsBatch(
const promises = toolMetadata.map((meta) => const promises = toolMetadata.map((meta) =>
loadToolDynamically( loadToolDynamically(
meta.packageName, meta.packageName,
meta.exportName, meta.name,
meta.version, meta.version,
conversationId, conversationId,
meta.importUrl, meta.importUrl,
env env
).then((tool) => ({ ).then((tool) => ({
packageName: meta.packageName, packageName: meta.packageName,
exportName: meta.exportName, name: meta.name,
key: getCacheKey(meta.packageName, meta.exportName), key: getCacheKey(meta.packageName, meta.name),
tool, tool,
success: tool !== null, success: tool !== null,
})) }))
@ -232,7 +232,7 @@ export async function loadToolsBatch(
if (failed.length > 0) { if (failed.length > 0) {
console.log('\n❌ Failed Tools:'); console.log('\n❌ Failed Tools:');
for (const result of failed) { for (const result of failed) {
console.log(` - ${result.packageName}/${result.exportName}`); console.log(` - ${result.packageName}/${result.name}`);
} }
console.log('\n💡 Note: Tool failures have been reported to the health service.'); console.log('\n💡 Note: Tool failures have been reported to the health service.');
} }

View file

@ -17,7 +17,7 @@ const TOOL_REGISTRY: Record<string, Record<string, any>> = {
* Uses static imports to work with Next.js/webpack bundling * Uses static imports to work with Next.js/webpack bundling
*/ */
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here // biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
export async function loadTpmjsTool(packageName: string, exportName: string): Promise<any | null> { export async function loadTpmjsTool(packageName: string, name: string): Promise<any | null> {
try { try {
// Look up the package in the registry // Look up the package in the registry
const packageTools = TOOL_REGISTRY[packageName]; const packageTools = TOOL_REGISTRY[packageName];
@ -27,10 +27,10 @@ export async function loadTpmjsTool(packageName: string, exportName: string): Pr
} }
// Look up the specific tool export // Look up the specific tool export
const tool = packageTools[exportName]; const tool = packageTools[name];
if (!tool) { if (!tool) {
console.warn( console.warn(
`Export '${exportName}' not found in package ${packageName}. Available exports:`, `Export '${name}' not found in package ${packageName}. Available exports:`,
Object.keys(packageTools) Object.keys(packageTools)
); );
return null; return null;
@ -38,7 +38,7 @@ export async function loadTpmjsTool(packageName: string, exportName: string): Pr
return tool; return tool;
} catch (error) { } catch (error) {
console.error(`Failed to load tool ${packageName}/${exportName}:`, error); console.error(`Failed to load tool ${packageName}/${name}:`, error);
return null; return null;
} }
} }
@ -56,7 +56,7 @@ export function sanitizeToolName(name: string): string {
/** /**
* Load all installed TPMJS tools * Load all installed TPMJS tools
* Returns a flat object with all tools keyed by sanitized packageName-exportName * Returns a flat object with all tools keyed by sanitized packageName-name
*/ */
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here // biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex and using any is appropriate here
export async function loadAllTools(): Promise<Record<string, any>> { export async function loadAllTools(): Promise<Record<string, any>> {
@ -65,9 +65,9 @@ export async function loadAllTools(): Promise<Record<string, any>> {
// Iterate through all registered packages // Iterate through all registered packages
for (const [packageName, packageTools] of Object.entries(TOOL_REGISTRY)) { for (const [packageName, packageTools] of Object.entries(TOOL_REGISTRY)) {
for (const [exportName, tool] of Object.entries(packageTools)) { for (const [name, tool] of Object.entries(packageTools)) {
// Create a unique, sanitized key for this tool // Create a unique, sanitized key for this tool
const toolKey = sanitizeToolName(`${packageName}-${exportName}`); const toolKey = sanitizeToolName(`${packageName}-${name}`);
tools[toolKey] = tool; tools[toolKey] = tool;
} }
} }

View file

@ -78,7 +78,7 @@ const TPMJS_API_URL = Deno.env.get('TPMJS_API_URL') || 'https://tpmjs.com';
*/ */
async function reportToolHealth( async function reportToolHealth(
packageName: string, packageName: string,
exportName: string, name: string,
success: boolean, success: boolean,
error?: string error?: string
): Promise<void> { ): Promise<void> {
@ -88,7 +88,7 @@ async function reportToolHealth(
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName, packageName,
exportName, name,
success, success,
error, error,
}), }),
@ -96,7 +96,7 @@ async function reportToolHealth(
if (response.ok) { if (response.ok) {
console.log( console.log(
`📊 Health reported for ${packageName}/${exportName}: ${success ? 'SUCCESS' : 'FAILURE'}` `📊 Health reported for ${packageName}/${name}: ${success ? 'SUCCESS' : 'FAILURE'}`
); );
} else { } else {
console.warn(`⚠️ Failed to report health: ${response.status}`); console.warn(`⚠️ Failed to report health: ${response.status}`);
@ -113,7 +113,7 @@ async function reportToolHealth(
*/ */
async function updateToolSchema( async function updateToolSchema(
packageName: string, packageName: string,
exportName: string, name: string,
description: string, description: string,
// biome-ignore lint/suspicious/noExplicitAny: JSON Schema can have any structure // biome-ignore lint/suspicious/noExplicitAny: JSON Schema can have any structure
inputSchema: any inputSchema: any
@ -124,7 +124,7 @@ async function updateToolSchema(
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName, packageName,
exportName, name,
description, description,
inputSchema, inputSchema,
}), }),
@ -133,7 +133,7 @@ async function updateToolSchema(
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
console.log( console.log(
`📋 Schema updated for ${packageName}/${exportName}:`, `📋 Schema updated for ${packageName}/${name}:`,
data.updated ? 'UPDATED' : 'NO CHANGE' data.updated ? 'UPDATED' : 'NO CHANGE'
); );
} else { } else {
@ -216,19 +216,19 @@ function sanitizeJsonSchema(schema: any): any {
async function loadAndDescribe(req: Request): Promise<Response> { async function loadAndDescribe(req: Request): Promise<Response> {
try { try {
const body = await req.json(); const body = await req.json();
const { packageName, exportName, version, importUrl, env } = body; const { packageName, name, version, importUrl, env } = body;
if (!packageName || !exportName || !version) { if (!packageName || !name || !version) {
return Response.json( return Response.json(
{ {
success: false, success: false,
error: 'Missing required fields: packageName, exportName, version', error: 'Missing required fields: packageName, name, version',
}, },
{ status: 400 } { status: 400 }
); );
} }
const cacheKey = `${packageName}::${exportName}`; const cacheKey = `${packageName}::${name}`;
// biome-ignore lint/suspicious/noImplicitAnyLet: Tool type is determined dynamically after import // biome-ignore lint/suspicious/noImplicitAnyLet: Tool type is determined dynamically after import
let toolModule; let toolModule;
@ -244,14 +244,14 @@ async function loadAndDescribe(req: Request): Promise<Response> {
console.log(`📦 Importing: ${url}`); console.log(`📦 Importing: ${url}`);
const module = await import(url); const module = await import(url);
let rawExport = module[exportName]; let rawExport = module[name];
if (!rawExport) { if (!rawExport) {
console.error(`❌ Export "${exportName}" not found. Available:`, Object.keys(module)); console.error(`❌ Export "${name}" not found. Available:`, Object.keys(module));
return Response.json( return Response.json(
{ {
success: false, success: false,
error: `Export "${exportName}" not found in module`, error: `Export "${name}" not found in module`,
availableExports: Object.keys(module), availableExports: Object.keys(module),
}, },
{ status: 404 } { status: 404 }
@ -266,7 +266,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
// Strategy 1: Try calling with no arguments // Strategy 1: Try calling with no arguments
try { try {
console.log(` Trying: ${exportName}()`); console.log(` Trying: ${name}()`);
factoryResult = rawExport(); factoryResult = rawExport();
if (factoryResult?.description && factoryResult?.execute) { if (factoryResult?.description && factoryResult?.execute) {
console.log(' ✅ Success with no-args factory'); console.log(' ✅ Success with no-args factory');
@ -300,7 +300,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
// Try each config variation // Try each config variation
for (const config of configVariations) { for (const config of configVariations) {
try { try {
console.log(` Trying: ${exportName}(`, Object.keys(config), ')'); console.log(` Trying: ${name}(`, Object.keys(config), ')');
factoryResult = rawExport(config); factoryResult = rawExport(config);
if (factoryResult?.description && factoryResult?.execute) { if (factoryResult?.description && factoryResult?.execute) {
console.log(' ✅ Success with config:', Object.keys(config)); console.log(' ✅ Success with config:', Object.keys(config));
@ -318,7 +318,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
try { try {
const firstValue = Object.values(env)[0]; const firstValue = Object.values(env)[0];
if (firstValue) { if (firstValue) {
console.log(` Trying: ${exportName}(firstEnvValue)`); console.log(` Trying: ${name}(firstEnvValue)`);
factoryResult = rawExport(firstValue); factoryResult = rawExport(firstValue);
if (factoryResult?.description && factoryResult?.execute) { if (factoryResult?.description && factoryResult?.execute) {
console.log(' ✅ Success with single-arg factory'); console.log(' ✅ Success with single-arg factory');
@ -336,7 +336,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
return Response.json( return Response.json(
{ {
success: false, success: false,
error: `Tool "${exportName}" is a factory function but couldn't be initialized. Tried: no-args, config object, and single-arg patterns.`, error: `Tool "${name}" is a factory function but couldn't be initialized. Tried: no-args, config object, and single-arg patterns.`,
hint: 'This tool may require specific configuration. Check package documentation.', hint: 'This tool may require specific configuration. Check package documentation.',
}, },
{ status: 400 } { status: 400 }
@ -436,7 +436,7 @@ async function loadAndDescribe(req: Request): Promise<Response> {
return Response.json( return Response.json(
{ {
success: false, success: false,
error: `Tool "${exportName}" has no valid inputSchema. Tools must use AI SDK jsonSchema(), Zod v4 toJSONSchema(), or Zod v3 schemas.`, error: `Tool "${name}" has no valid inputSchema. Tools must use AI SDK jsonSchema(), Zod v4 toJSONSchema(), or Zod v3 schemas.`,
debug: { debug: {
hasInputSchema: !!toolModule.inputSchema, hasInputSchema: !!toolModule.inputSchema,
availableMethods: toolModule.inputSchema ? Object.keys(toolModule.inputSchema) : [], availableMethods: toolModule.inputSchema ? Object.keys(toolModule.inputSchema) : [],
@ -453,16 +453,14 @@ async function loadAndDescribe(req: Request): Promise<Response> {
const sanitizedSchema = sanitizeJsonSchema(rawJsonSchema); const sanitizedSchema = sanitizeJsonSchema(rawJsonSchema);
// Update TPM.js database with the schema (async, non-blocking) // Update TPM.js database with the schema (async, non-blocking)
updateToolSchema(packageName, exportName, toolModule.description, sanitizedSchema).catch( updateToolSchema(packageName, name, toolModule.description, sanitizedSchema).catch((err) => {
(err) => { console.warn('⚠️ Failed to update schema in database:', err);
console.warn('⚠️ Failed to update schema in database:', err); });
}
);
return Response.json({ return Response.json({
success: true, success: true,
tool: { tool: {
exportName, name,
description: toolModule.description, description: toolModule.description,
inputSchema: sanitizedSchema, // Plain JSON Schema - fully serializable inputSchema: sanitizedSchema, // Plain JSON Schema - fully serializable
}, },
@ -486,32 +484,32 @@ async function executeTool(req: Request): Promise<Response> {
const startTime = Date.now(); const startTime = Date.now();
// Declare these before try block so they're available in catch for error reporting // Declare these before try block so they're available in catch for error reporting
let packageName = 'unknown'; let packageName = 'unknown';
let exportName = 'unknown'; let toolName = 'unknown';
try { try {
const body = await req.json(); const body = await req.json();
const { packageName: pkg, exportName: exp, version, importUrl, params, env } = body; const { packageName: pkg, name, version, importUrl, params, env } = body;
packageName = pkg || 'unknown'; packageName = pkg || 'unknown';
exportName = exp || 'unknown'; toolName = name || 'unknown';
console.log('📥 Execute request:', { console.log('📥 Execute request:', {
packageName, packageName,
exportName, name: toolName,
version, version,
envKeys: env ? Object.keys(env) : [], envKeys: env ? Object.keys(env) : [],
envValues: env || {}, envValues: env || {},
}); });
if (!packageName || !exportName || !version) { if (!packageName || !toolName || !version) {
return Response.json( return Response.json(
{ {
success: false, success: false,
error: 'Missing required fields: packageName, exportName, version', error: 'Missing required fields: packageName, name, version',
}, },
{ status: 400 } { status: 400 }
); );
} }
const cacheKey = `${packageName}::${exportName}`; const cacheKey = `${packageName}::${toolName}`;
// Inject environment variables FIRST - before cache check and factory calls // Inject environment variables FIRST - before cache check and factory calls
// This ensures process.env is set when factory functions read from it // This ensures process.env is set when factory functions read from it
@ -561,7 +559,7 @@ async function executeTool(req: Request): Promise<Response> {
console.log(`📦 Importing for execution: ${url}`); console.log(`📦 Importing for execution: ${url}`);
const module = await import(url); const module = await import(url);
let rawExport = module[exportName]; let rawExport = module[toolName];
if (!rawExport) { if (!rawExport) {
return Response.json( return Response.json(
@ -587,7 +585,7 @@ async function executeTool(req: Request): Promise<Response> {
// Strategy 1: Try calling with no arguments // Strategy 1: Try calling with no arguments
try { try {
console.log(` Trying: ${exportName}()`); console.log(` Trying: ${toolName}()`);
factoryResult = rawExport(); factoryResult = rawExport();
if (factoryResult?.execute) { if (factoryResult?.execute) {
console.log(' ✅ Success with no-args factory'); console.log(' ✅ Success with no-args factory');
@ -621,7 +619,7 @@ async function executeTool(req: Request): Promise<Response> {
// Try each config variation // Try each config variation
for (const config of configVariations) { for (const config of configVariations) {
try { try {
console.log(` Trying: ${exportName}(`, Object.keys(config), ')'); console.log(` Trying: ${toolName}(`, Object.keys(config), ')');
factoryResult = rawExport(config); factoryResult = rawExport(config);
if (factoryResult?.execute) { if (factoryResult?.execute) {
console.log(' ✅ Success with config:', Object.keys(config)); console.log(' ✅ Success with config:', Object.keys(config));
@ -639,7 +637,7 @@ async function executeTool(req: Request): Promise<Response> {
try { try {
const firstValue = Object.values(env)[0]; const firstValue = Object.values(env)[0];
if (firstValue) { if (firstValue) {
console.log(` Trying: ${exportName}(firstEnvValue)`); console.log(` Trying: ${toolName}(firstEnvValue)`);
factoryResult = rawExport(firstValue); factoryResult = rawExport(firstValue);
if (factoryResult?.execute) { if (factoryResult?.execute) {
console.log(' ✅ Success with single-arg factory'); console.log(' ✅ Success with single-arg factory');
@ -655,7 +653,7 @@ async function executeTool(req: Request): Promise<Response> {
return Response.json( return Response.json(
{ {
success: false, success: false,
error: `Tool "${exportName}" is a factory function but couldn't be initialized`, error: `Tool "${toolName}" is a factory function but couldn't be initialized`,
executionTimeMs: Date.now() - startTime, executionTimeMs: Date.now() - startTime,
}, },
{ status: 400 } { status: 400 }
@ -700,7 +698,7 @@ async function executeTool(req: Request): Promise<Response> {
console.log(`✅ Execution complete in ${executionTimeMs}ms`); console.log(`✅ Execution complete in ${executionTimeMs}ms`);
// Report successful execution to health service (non-blocking) // Report successful execution to health service (non-blocking)
reportToolHealth(packageName, exportName, true).catch(() => {}); reportToolHealth(packageName, toolName, true).catch(() => {});
return Response.json({ return Response.json({
success: true, success: true,
@ -712,7 +710,7 @@ async function executeTool(req: Request): Promise<Response> {
console.error('❌ Tool execution failed:', error); console.error('❌ Tool execution failed:', error);
// Report failed execution to health service (non-blocking) // Report failed execution to health service (non-blocking)
reportToolHealth(packageName, exportName, false, error.message).catch(() => {}); reportToolHealth(packageName, toolName, false, error.message).catch(() => {});
return Response.json( return Response.json(
{ {

View file

@ -176,14 +176,14 @@ export async function POST(request: NextRequest) {
const upsertedTool = await prisma.tool.upsert({ const upsertedTool = await prisma.tool.upsert({
where: { where: {
packageId_exportName: { packageId_name: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: toolName, name: toolName,
}, },
}, },
create: { create: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: toolName, name: toolName,
description: toolDef.description || 'No description provided', description: toolDef.description || 'No description provided',
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround // biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined, parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
@ -254,8 +254,7 @@ export async function POST(request: NextRequest) {
// Delete orphaned tools (tools removed from package.json) // Delete orphaned tools (tools removed from package.json)
const orphanedTools = existingTools.filter( const orphanedTools = existingTools.filter(
(existingTool) => (existingTool) => !toolsToProcess.some((toolDef) => toolDef.name === existingTool.name)
!toolsToProcess.some((toolDef) => toolDef.name === existingTool.exportName)
); );
if (orphanedTools.length > 0) { if (orphanedTools.length > 0) {

View file

@ -195,14 +195,14 @@ export async function POST(request: NextRequest) {
const upsertedTool = await prisma.tool.upsert({ const upsertedTool = await prisma.tool.upsert({
where: { where: {
packageId_exportName: { packageId_name: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: toolName, name: toolName,
}, },
}, },
create: { create: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: toolName, name: toolName,
description: toolDef.description || 'No description provided', description: toolDef.description || 'No description provided',
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround // biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility workaround
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined, parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
@ -273,8 +273,7 @@ export async function POST(request: NextRequest) {
// Delete orphaned tools (tools removed from package.json) // Delete orphaned tools (tools removed from package.json)
const orphanedTools = existingTools.filter( const orphanedTools = existingTools.filter(
(existingTool) => (existingTool) => !toolsToProcess.some((toolDef) => toolDef.name === existingTool.name)
!toolsToProcess.some((toolDef) => toolDef.name === existingTool.exportName)
); );
if (orphanedTools.length > 0) { if (orphanedTools.length > 0) {

View file

@ -10,30 +10,30 @@ export const maxDuration = 60;
/** /**
* Parse tool slug to extract package name and export name * Parse tool slug to extract package name and export name
*/ */
function parseSlug(slug: string[]): { packageName: string; exportName: string | undefined } { function parseSlug(slug: string[]): { packageName: string; name: string | undefined } {
let packageName: string; let packageName: string;
let exportName: string | undefined; let name: string | undefined;
if (slug.length === 1) { if (slug.length === 1) {
// Single slug - package name without scope // Single slug - package name without scope
packageName = slug[0] || ''; packageName = slug[0] || '';
} else if (slug.length === 2) { } else if (slug.length === 2) {
// Could be: @scope/package OR package/exportName // Could be: @scope/package OR package/name
if (slug[0]?.startsWith('@')) { if (slug[0]?.startsWith('@')) {
// @scope/package // @scope/package
packageName = slug.join('/'); packageName = slug.join('/');
} else { } else {
// package + exportName // package + name
packageName = slug[0] || ''; packageName = slug[0] || '';
exportName = slug[1]; name = slug[1];
} }
} else { } else {
// 3+ slugs: @scope/package/exportName // 3+ slugs: @scope/package/name
packageName = slug.slice(0, slug[0]?.startsWith('@') ? 2 : 1).join('/'); packageName = slug.slice(0, slug[0]?.startsWith('@') ? 2 : 1).join('/');
exportName = slug[slug.length - 1]; name = slug[slug.length - 1];
} }
return { packageName, exportName }; return { packageName, name };
} }
/** /**
@ -54,14 +54,14 @@ export async function GET(
try { try {
const { slug } = await params; const { slug } = await params;
const { packageName, exportName } = parseSlug(slug); const { packageName, name } = parseSlug(slug);
if (exportName) { if (name) {
// Find specific tool by package name and export name // Find specific tool by package name and export name
const tool = await prisma.tool.findFirst({ const tool = await prisma.tool.findFirst({
where: { where: {
package: { npmPackageName: packageName }, package: { npmPackageName: packageName },
exportName: exportName, name: name,
}, },
include: { package: true }, include: { package: true },
}); });
@ -138,10 +138,10 @@ export async function POST(
try { try {
const { slug } = await params; const { slug } = await params;
const { packageName, exportName } = parseSlug(slug); const { packageName, name } = parseSlug(slug);
// Health checks require export name // Health checks require export name
if (!exportName) { if (!name) {
return NextResponse.json( return NextResponse.json(
{ {
success: false, success: false,
@ -155,7 +155,7 @@ export async function POST(
const tool = await prisma.tool.findFirst({ const tool = await prisma.tool.findFirst({
where: { where: {
package: { npmPackageName: packageName }, package: { npmPackageName: packageName },
exportName: exportName, name: name,
}, },
select: { select: {
id: true, id: true,
@ -190,7 +190,7 @@ export async function POST(
} }
// Perform health check // Perform health check
console.log(`🏥 Manual health check triggered for ${packageName}/${exportName}`); console.log(`🏥 Manual health check triggered for ${packageName}/${name}`);
const result = await performHealthCheck(tool.id, 'manual'); const result = await performHealthCheck(tool.id, 'manual');
return NextResponse.json({ return NextResponse.json({
@ -198,7 +198,7 @@ export async function POST(
data: { data: {
toolId: result.toolId, toolId: result.toolId,
packageName: packageName, packageName: packageName,
exportName: exportName, name: name,
importStatus: result.importStatus, importStatus: result.importStatus,
importError: result.importError, importError: result.importError,
importTimeMs: result.importTimeMs, importTimeMs: result.importTimeMs,

View file

@ -21,7 +21,7 @@ interface ExecuteRequest {
* POST /api/tools/execute/[...slug] * POST /api/tools/execute/[...slug]
* Executes a tool with an AI agent and streams the response via SSE * Executes a tool with an AI agent and streams the response via SSE
* *
* Slug format: [toolId] or [packageName, exportName] * Slug format: [toolId] or [packageName, name]
* Examples: * Examples:
* /api/tools/execute/clx123abc (by tool ID) * /api/tools/execute/clx123abc (by tool ID)
* /api/tools/execute/@tpmjs/hello/helloWorldTool (by package and export name) * /api/tools/execute/@tpmjs/hello/helloWorldTool (by package and export name)
@ -68,7 +68,7 @@ export async function POST(
} }
// Fetch tool from database with package relation // Fetch tool from database with package relation
// Support both ID-based lookup and packageName/exportName lookup // Support both ID-based lookup and packageName/name lookup
const tool = const tool =
slug.length === 1 slug.length === 1
? // Single slug - treat as tool ID ? // Single slug - treat as tool ID
@ -76,11 +76,11 @@ export async function POST(
where: { id: slug[0] || '' }, where: { id: slug[0] || '' },
include: { package: true }, include: { package: true },
}) })
: // Multiple slugs - treat as packageName/exportName : // Multiple slugs - treat as packageName/name
await prisma.tool.findFirst({ await prisma.tool.findFirst({
where: { where: {
package: { npmPackageName: decodeURIComponent(slug.slice(0, -1).join('/')) }, package: { npmPackageName: decodeURIComponent(slug.slice(0, -1).join('/')) },
exportName: decodeURIComponent(slug[slug.length - 1] || ''), name: decodeURIComponent(slug[slug.length - 1] || ''),
}, },
include: { package: true }, include: { package: true },
}); });

View file

@ -11,28 +11,28 @@ export const dynamic = 'force-dynamic';
* *
* Body: * Body:
* - packageName: npm package name * - packageName: npm package name
* - exportName: exported function name * - name: exported function name
* *
* Rate limited to 1 extraction per minute per tool * Rate limited to 1 extraction per minute per tool
*/ */
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json(); const body = await request.json();
const { packageName, exportName } = body; const { packageName, name } = body;
if (!packageName || !exportName) { if (!packageName || !name) {
return NextResponse.json( return NextResponse.json(
{ success: false, error: 'packageName and exportName are required' }, { success: false, error: 'packageName and name are required' },
{ status: 400 } { status: 400 }
); );
} }
console.log('[Extract Schema] Looking up tool:', { packageName, exportName }); console.log('[Extract Schema] Looking up tool:', { packageName, name });
// Find the tool by package name and export name // Find the tool by package name and export name
const tool = await prisma.tool.findFirst({ const tool = await prisma.tool.findFirst({
where: { where: {
exportName, name,
package: { package: {
npmPackageName: packageName, npmPackageName: packageName,
}, },
@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
}); });
if (!tool) { if (!tool) {
console.log('[Extract Schema] Tool not found:', { packageName, exportName }); console.log('[Extract Schema] Tool not found:', { packageName, name });
return NextResponse.json({ success: false, error: 'Tool not found' }, { status: 404 }); return NextResponse.json({ success: false, error: 'Tool not found' }, { status: 404 });
} }
@ -74,14 +74,14 @@ export async function POST(request: NextRequest) {
console.log('[Extract Schema] Extracting schema for:', { console.log('[Extract Schema] Extracting schema for:', {
packageName: tool.package.npmPackageName, packageName: tool.package.npmPackageName,
exportName: tool.exportName, name: tool.name,
version: tool.package.npmVersion, version: tool.package.npmVersion,
}); });
// Extract schema from executor // Extract schema from executor
const schemaResult = await extractToolSchema( const schemaResult = await extractToolSchema(
tool.package.npmPackageName, tool.package.npmPackageName,
tool.exportName, tool.name,
tool.package.npmVersion, tool.package.npmVersion,
tool.package.env as Record<string, unknown> | null tool.package.env as Record<string, unknown> | null
); );
@ -101,7 +101,7 @@ export async function POST(request: NextRequest) {
}, },
select: { select: {
id: true, id: true,
exportName: true, name: true,
inputSchema: true, inputSchema: true,
parameters: true, parameters: true,
schemaSource: true, schemaSource: true,
@ -111,7 +111,7 @@ export async function POST(request: NextRequest) {
console.log('[Extract Schema] Schema extracted successfully:', { console.log('[Extract Schema] Schema extracted successfully:', {
toolId: updatedTool.id, toolId: updatedTool.id,
exportName: updatedTool.exportName, name: updatedTool.name,
schemaSource: updatedTool.schemaSource, schemaSource: updatedTool.schemaSource,
}); });
@ -126,7 +126,7 @@ export async function POST(request: NextRequest) {
// Extraction failed // Extraction failed
console.log('[Extract Schema] Extraction failed:', { console.log('[Extract Schema] Extraction failed:', {
packageName, packageName,
exportName, name,
error: schemaResult.error, error: schemaResult.error,
}); });

View file

@ -64,7 +64,7 @@ function isNonBreakingError(error: string): boolean {
interface ReportHealthRequest { interface ReportHealthRequest {
packageName: string; packageName: string;
exportName: string; name: string;
success: boolean; success: boolean;
error?: string; error?: string;
} }
@ -87,11 +87,11 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
try { try {
const body: ReportHealthRequest = await request.json(); const body: ReportHealthRequest = await request.json();
const { packageName, exportName, success, error } = body; const { packageName, name, success, error } = body;
if (!packageName || !exportName) { if (!packageName || !name) {
return NextResponse.json( return NextResponse.json(
{ success: false, error: 'packageName and exportName are required' }, { success: false, error: 'packageName and name are required' },
{ status: 400 } { status: 400 }
); );
} }
@ -99,7 +99,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
// Find the tool // Find the tool
const tool = await prisma.tool.findFirst({ const tool = await prisma.tool.findFirst({
where: { where: {
exportName, name,
package: { npmPackageName: packageName }, package: { npmPackageName: packageName },
}, },
select: { id: true }, select: { id: true },
@ -119,9 +119,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
} else if (error && isNonBreakingError(error)) { } else if (error && isNonBreakingError(error)) {
// Failed due to config/validation = HEALTHY (tool works, just needs setup) // Failed due to config/validation = HEALTHY (tool works, just needs setup)
healthStatus = 'HEALTHY'; healthStatus = 'HEALTHY';
console.log( console.log(` ${packageName}/${name} failed due to config issue (not broken): ${error}`);
` ${packageName}/${exportName} failed due to config issue (not broken): ${error}`
);
} else { } else {
// Real failure = BROKEN // Real failure = BROKEN
healthStatus = 'BROKEN'; healthStatus = 'BROKEN';
@ -138,7 +136,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
}, },
}); });
console.log(`🏥 Health updated for ${packageName}/${exportName}: ${healthStatus}`); console.log(`🏥 Health updated for ${packageName}/${name}: ${healthStatus}`);
return NextResponse.json({ return NextResponse.json({
success: true, success: true,

View file

@ -100,7 +100,7 @@ export async function GET(request: NextRequest) {
tool, tool,
text: [ text: [
tool.description, tool.description,
tool.exportName, tool.name,
tool.package.npmPackageName, tool.package.npmPackageName,
tool.package.npmDescription || '', tool.package.npmDescription || '',
...(tool.package.npmKeywords || []), ...(tool.package.npmKeywords || []),
@ -157,7 +157,7 @@ export async function GET(request: NextRequest) {
returned: results.length, returned: results.length,
tools: results.map(({ tool }) => ({ tools: results.map(({ tool }) => ({
id: tool.id, id: tool.id,
exportName: tool.exportName, name: tool.name,
description: tool.description, description: tool.description,
qualityScore: tool.qualityScore, qualityScore: tool.qualityScore,
importHealth: tool.importHealth, importHealth: tool.importHealth,

View file

@ -30,14 +30,14 @@ export async function GET(
select: { id: true }, select: { id: true },
}); });
} else { } else {
// Multiple slugs - treat as packageName/exportName // Multiple slugs - treat as packageName/name
const packageName = decodeURIComponent(slug.slice(0, -1).join('/')); const packageName = decodeURIComponent(slug.slice(0, -1).join('/'));
const exportName = decodeURIComponent(slug[slug.length - 1] || ''); const name = decodeURIComponent(slug[slug.length - 1] || '');
tool = await prisma.tool.findFirst({ tool = await prisma.tool.findFirst({
where: { where: {
package: { npmPackageName: packageName }, package: { npmPackageName: packageName },
exportName: exportName, name: name,
}, },
select: { id: true }, select: { id: true },
}); });

View file

@ -10,23 +10,23 @@ export const dynamic = 'force-dynamic';
* Update a tool's input schema * Update a tool's input schema
* *
* Called by the executor when it loads a tool and discovers its schema. * Called by the executor when it loads a tool and discovers its schema.
* Looks up tool by packageName + exportName (unique constraint). * Looks up tool by packageName + name (unique constraint).
* Stores the full JSON Schema and also converts to parameters array for backward compatibility. * Stores the full JSON Schema and also converts to parameters array for backward compatibility.
* *
* Body: * Body:
* - packageName: npm package name * - packageName: npm package name
* - exportName: exported function name * - name: exported function name
* - inputSchema: The JSON Schema for the tool's input parameters * - inputSchema: The JSON Schema for the tool's input parameters
* - description: Optional updated description from the tool * - description: Optional updated description from the tool
*/ */
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json(); const body = await request.json();
const { packageName, exportName, inputSchema, description } = body; const { packageName, name, inputSchema, description } = body;
if (!packageName || !exportName) { if (!packageName || !name) {
return NextResponse.json( return NextResponse.json(
{ success: false, error: 'packageName and exportName are required' }, { success: false, error: 'packageName and name are required' },
{ status: 400 } { status: 400 }
); );
} }
@ -38,12 +38,12 @@ export async function POST(request: NextRequest) {
); );
} }
console.log('[Update Schema] Looking up tool:', { packageName, exportName }); console.log('[Update Schema] Looking up tool:', { packageName, name });
// Find the tool by package name and export name // Find the tool by package name and name
const tool = await prisma.tool.findFirst({ const tool = await prisma.tool.findFirst({
where: { where: {
exportName, name,
package: { package: {
npmPackageName: packageName, npmPackageName: packageName,
}, },
@ -56,7 +56,7 @@ export async function POST(request: NextRequest) {
}); });
if (!tool) { if (!tool) {
console.log('[Update Schema] Tool not found:', { packageName, exportName }); console.log('[Update Schema] Tool not found:', { packageName, name });
return NextResponse.json( return NextResponse.json(
{ success: false, error: 'Tool not found', updated: false }, { success: false, error: 'Tool not found', updated: false },
{ status: 404 } { status: 404 }
@ -73,7 +73,7 @@ export async function POST(request: NextRequest) {
tool.schemaSource === 'extracted' && tool.schemaSource === 'extracted' &&
JSON.stringify(existingSchema) === JSON.stringify(inputSchema) JSON.stringify(existingSchema) === JSON.stringify(inputSchema)
) { ) {
console.log('[Update Schema] Schema already up to date:', { packageName, exportName }); console.log('[Update Schema] Schema already up to date:', { packageName, name });
return NextResponse.json({ return NextResponse.json({
success: true, success: true,
updated: false, updated: false,
@ -107,7 +107,7 @@ export async function POST(request: NextRequest) {
data: updateData, data: updateData,
select: { select: {
id: true, id: true,
exportName: true, name: true,
description: true, description: true,
parameters: true, parameters: true,
inputSchema: true, inputSchema: true,
@ -118,7 +118,7 @@ export async function POST(request: NextRequest) {
console.log('[Update Schema] Tool updated:', { console.log('[Update Schema] Tool updated:', {
id: updatedTool.id, id: updatedTool.id,
exportName: updatedTool.exportName, name: updatedTool.name,
parameterCount: parameters.length, parameterCount: parameters.length,
parameterNames: parameters.map((p) => p.name), parameterNames: parameters.map((p) => p.name),
schemaSource: updatedTool.schemaSource, schemaSource: updatedTool.schemaSource,

View file

@ -492,7 +492,7 @@ Use registrySearch to find tools, then registryExecute to run them.\`,
name: 'toolId', name: 'toolId',
type: 'string', type: 'string',
required: true, required: true,
description: 'Tool identifier (format: package::exportName)', description: 'Tool identifier (format: package::name)',
}, },
{ {
name: 'params', name: 'params',
@ -1146,7 +1146,7 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
<DocSubSection title="Execution failing"> <DocSubSection title="Execution failing">
<ul className="list-disc list-inside space-y-2 text-foreground-secondary"> <ul className="list-disc list-inside space-y-2 text-foreground-secondary">
<li>Check that required environment variables are passed</li> <li>Check that required environment variables are passed</li>
<li>Verify the toolId format is correct (package::exportName)</li> <li>Verify the toolId format is correct (package::name)</li>
<li>Check the tool&apos;s health status on tpmjs.com</li> <li>Check the tool&apos;s health status on tpmjs.com</li>
</ul> </ul>
</DocSubSection> </DocSubSection>

View file

@ -138,7 +138,7 @@ export default function FAQPage(): React.ReactElement {
</ul> </ul>
<p> <p>
This simplifies publishing - you only need to provide category, description, and This simplifies publishing - you only need to provide category, description, and
exportName. See our{' '} name. See our{' '}
<Link href="/spec" className="text-primary hover:underline font-medium"> <Link href="/spec" className="text-primary hover:underline font-medium">
specification specification
</Link>{' '} </Link>{' '}

View file

@ -97,7 +97,7 @@ export default function HowItWorksPage(): React.ReactElement {
"category": "text-analysis", "category": "text-analysis",
"frameworks": ["vercel-ai"], "frameworks": ["vercel-ai"],
"tools": [{ "tools": [{
"exportName": "analyzeSentiment", "name": "analyzeSentiment",
"description": "Analyze sentiment of text and return positive/negative/neutral" "description": "Analyze sentiment of text and return positive/negative/neutral"
}] }]
} }

View file

@ -24,7 +24,7 @@ async function getHomePageData() {
take: 6, take: 6,
select: { select: {
id: true, id: true,
exportName: true, name: true,
description: true, description: true,
qualityScore: true, qualityScore: true,
package: { package: {
@ -100,7 +100,7 @@ export default async function HomePage(): Promise<React.ReactElement> {
{data.featuredTools.map((tool) => ( {data.featuredTools.map((tool) => (
<Link <Link
key={tool.id} key={tool.id}
href={`/tool/${tool.package.npmPackageName}/${tool.exportName}`} href={`/tool/${tool.package.npmPackageName}/${tool.name}`}
className="group" className="group"
> >
<div className="p-6 border border-border rounded-lg bg-surface hover:border-foreground transition-colors h-full flex flex-col"> <div className="p-6 border border-border rounded-lg bg-surface hover:border-foreground transition-colors h-full flex flex-col">
@ -108,7 +108,7 @@ export default async function HomePage(): Promise<React.ReactElement> {
<h3 className="text-lg font-semibold text-foreground group-hover:text-brutalist-accent transition-colors"> <h3 className="text-lg font-semibold text-foreground group-hover:text-brutalist-accent transition-colors">
{tool.package.npmPackageName} {tool.package.npmPackageName}
<span className="text-xs text-foreground-tertiary ml-2"> <span className="text-xs text-foreground-tertiary ml-2">
({tool.exportName}) ({tool.name})
</span> </span>
</h3> </h3>
{tool.package.isOfficial && ( {tool.package.isOfficial && (

View file

@ -352,7 +352,7 @@ Use registrySearch to find tools, then registryExecute to run them.\`,
<td className="py-2 pr-4">string</td> <td className="py-2 pr-4">string</td>
<td className="py-2 pr-4">Yes</td> <td className="py-2 pr-4">Yes</td>
<td className="py-2"> <td className="py-2">
Tool identifier (format: <code>package::exportName</code>) Tool identifier (format: <code>package::name</code>)
</td> </td>
</tr> </tr>
<tr className="border-b border-border/50"> <tr className="border-b border-border/50">

View file

@ -67,7 +67,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
updatedAt: true, updatedAt: true,
}, },
}, },
exportName: true, name: true,
updatedAt: true, updatedAt: true,
}, },
orderBy: { orderBy: {
@ -83,7 +83,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
tool.updatedAt > tool.package.updatedAt ? tool.updatedAt : tool.package.updatedAt; tool.updatedAt > tool.package.updatedAt ? tool.updatedAt : tool.package.updatedAt;
return { return {
url: `${baseUrl}/tool/${tool.package.npmPackageName}/${tool.exportName}`, url: `${baseUrl}/tool/${tool.package.npmPackageName}/${tool.name}`,
lastModified, lastModified,
changeFrequency: 'weekly' as const, changeFrequency: 'weekly' as const,
priority: 0.7, priority: 0.7,

View file

@ -38,7 +38,7 @@ interface Package {
interface Tool { interface Tool {
id: string; id: string;
exportName: string; name: string;
description: string; description: string;
parameters: Array<{ parameters: Array<{
name: string; name: string;
@ -152,7 +152,7 @@ export default function ToolDetailPage({
const softwareApplicationSchema = { const softwareApplicationSchema = {
'@context': 'https://schema.org', '@context': 'https://schema.org',
'@type': 'SoftwareApplication', '@type': 'SoftwareApplication',
name: tool.exportName, name: tool.name,
description: tool.description, description: tool.description,
applicationCategory: 'DeveloperApplication', applicationCategory: 'DeveloperApplication',
operatingSystem: 'Any', operatingSystem: 'Any',
@ -165,7 +165,7 @@ export default function ToolDetailPage({
'@type': authorName ? 'Person' : 'Organization', '@type': authorName ? 'Person' : 'Organization',
name: authorName || 'Unknown', name: authorName || 'Unknown',
}, },
url: `https://tpmjs.com/tool/${pkg.npmPackageName}/${tool.exportName}`, url: `https://tpmjs.com/tool/${pkg.npmPackageName}/${tool.name}`,
softwareVersion: pkg.npmVersion, softwareVersion: pkg.npmVersion,
...(pkg.npmHomepage && { mainEntityOfPage: pkg.npmHomepage }), ...(pkg.npmHomepage && { mainEntityOfPage: pkg.npmHomepage }),
...(pkg.npmRepository && ...(pkg.npmRepository &&
@ -223,7 +223,7 @@ export default function ToolDetailPage({
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName: tool.package.npmPackageName, packageName: tool.package.npmPackageName,
exportName: tool.exportName, name: tool.name,
}), }),
}); });
@ -274,7 +274,7 @@ export default function ToolDetailPage({
<div className="mb-8"> <div className="mb-8">
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">
<div> <div>
<h1 className="text-4xl font-bold text-foreground mb-2">{tool.exportName}</h1> <h1 className="text-4xl font-bold text-foreground mb-2">{tool.name}</h1>
<p className="text-sm text-foreground-tertiary font-mono mb-2"> <p className="text-sm text-foreground-tertiary font-mono mb-2">
{pkg.npmPackageName} {pkg.npmPackageName}
</p> </p>
@ -409,7 +409,7 @@ export default function ToolDetailPage({
<div> <div>
<h4 className="text-sm font-semibold text-foreground mb-3">2. Import the tool</h4> <h4 className="text-sm font-semibold text-foreground mb-3">2. Import the tool</h4>
<CodeBlock <CodeBlock
code={`import { ${tool.exportName} } from '${pkg.npmPackageName}';`} code={`import { ${tool.name} } from '${pkg.npmPackageName}';`}
language="typescript" language="typescript"
showCopy={true} showCopy={true}
/> />
@ -420,11 +420,11 @@ export default function ToolDetailPage({
<CodeBlock <CodeBlock
code={`import { generateText } from 'ai'; code={`import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai'; import { openai } from '@ai-sdk/openai';
import { ${tool.exportName} } from '${pkg.npmPackageName}'; import { ${tool.name} } from '${pkg.npmPackageName}';
const result = await generateText({ const result = await generateText({
model: openai('gpt-4o'), model: openai('gpt-4o'),
tools: { ${tool.exportName} }, tools: { ${tool.name} },
prompt: 'Your prompt here...', prompt: 'Your prompt here...',
}); });

View file

@ -20,7 +20,7 @@ import { AppHeader } from '~/components/AppHeader';
interface BrokenTool { interface BrokenTool {
id: string; id: string;
exportName: string; name: string;
description: string; description: string;
importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
executionHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; executionHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
@ -142,7 +142,7 @@ export default function BrokenToolsPage(): React.ReactElement {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Broken tools page requires conditional rendering for health status */} {/* biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Broken tools page requires conditional rendering for health status */}
{tools.map((tool) => { {tools.map((tool) => {
const toolUrl = `/tool/${tool.package.npmPackageName}/${tool.exportName}`; const toolUrl = `/tool/${tool.package.npmPackageName}/${tool.name}`;
const lastCheckedDate = tool.lastHealthCheck const lastCheckedDate = tool.lastHealthCheck
? new Date(tool.lastHealthCheck) ? new Date(tool.lastHealthCheck)
: null; : null;
@ -153,9 +153,7 @@ export default function BrokenToolsPage(): React.ReactElement {
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="flex-1"> <div className="flex-1">
<CardTitle> <CardTitle>
{tool.exportName !== 'default' {tool.name !== 'default' ? tool.name : tool.package.npmPackageName}
? tool.exportName
: tool.package.npmPackageName}
</CardTitle> </CardTitle>
<div className="text-sm text-foreground-secondary mt-1"> <div className="text-sm text-foreground-secondary mt-1">
{tool.package.npmPackageName} {tool.package.npmPackageName}

View file

@ -17,7 +17,7 @@ import { AppHeader } from '~/components/AppHeader';
interface Tool { interface Tool {
id: string; id: string;
exportName: string; name: string;
description: string; description: string;
qualityScore: string; qualityScore: string;
importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; importHealth?: 'HEALTHY' | 'BROKEN' | 'UNKNOWN';
@ -245,7 +245,7 @@ export default function ToolSearchPage(): React.ReactElement {
return ( return (
<Link <Link
key={tool.id} key={tool.id}
href={`/tool/${tool.package.npmPackageName}/${tool.exportName}`} href={`/tool/${tool.package.npmPackageName}/${tool.name}`}
className="block select-text" className="block select-text"
> >
<Card className="flex flex-col h-full hover:border-foreground-tertiary transition-colors cursor-pointer select-text"> <Card className="flex flex-col h-full hover:border-foreground-tertiary transition-colors cursor-pointer select-text">
@ -254,9 +254,7 @@ export default function ToolSearchPage(): React.ReactElement {
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<CardTitle className="truncate"> <CardTitle className="truncate">
{tool.exportName !== 'default' {tool.name !== 'default' ? tool.name : tool.package.npmPackageName}
? tool.exportName
: tool.package.npmPackageName}
</CardTitle> </CardTitle>
<div className="text-sm text-foreground-secondary mt-1 truncate"> <div className="text-sm text-foreground-secondary mt-1 truncate">
{tool.package.npmPackageName} {tool.package.npmPackageName}

View file

@ -49,7 +49,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
try { try {
const response = await fetch( const response = await fetch(
`/api/tools/execute/${encodeURIComponent(tool.package.npmPackageName)}/${encodeURIComponent(tool.exportName)}`, `/api/tools/execute/${encodeURIComponent(tool.package.npmPackageName)}/${encodeURIComponent(tool.name)}`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -196,7 +196,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
<div> <div>
<h2 className="text-xl font-semibold text-foreground">Interactive Playground</h2> <h2 className="text-xl font-semibold text-foreground">Interactive Playground</h2>
<p className="text-sm text-foreground-secondary mt-1"> <p className="text-sm text-foreground-secondary mt-1">
Test {tool.package.npmPackageName} ({tool.exportName}) with AI-powered execution Test {tool.package.npmPackageName} ({tool.name}) with AI-powered execution
</p> </p>
</div> </div>
{rateLimitInfo && ( {rateLimitInfo && (

View file

@ -97,7 +97,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
? (tool.parameters as unknown as TPMJSParameter[]) ? (tool.parameters as unknown as TPMJSParameter[])
: []; : [];
console.log('[createToolDefinition] Tool:', tool.package.npmPackageName, '/', tool.exportName); console.log('[createToolDefinition] Tool:', tool.package.npmPackageName, '/', tool.name);
console.log('[createToolDefinition] Parameters array:', JSON.stringify(parameters)); console.log('[createToolDefinition] Parameters array:', JSON.stringify(parameters));
console.log('[createToolDefinition] Parameters length:', parameters.length); console.log('[createToolDefinition] Parameters length:', parameters.length);
@ -109,7 +109,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
console.log('[createToolDefinition] Created Zod schema:', inputSchema); console.log('[createToolDefinition] Created Zod schema:', inputSchema);
const sanitizedName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.exportName}`); const sanitizedName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.name}`);
// AI SDK v6 tool definition // AI SDK v6 tool definition
return { return {
@ -122,7 +122,7 @@ export function createToolDefinition(tool: Tool & { package: Package }) {
// Use the actual export name from the Tool record // Use the actual export name from the Tool record
const result = await executePackage( const result = await executePackage(
tool.package.npmPackageName, tool.package.npmPackageName,
tool.exportName, // Use actual export name (e.g., "helloWorldTool", "default") tool.name, // Use actual export name (e.g., "helloWorldTool", "default")
params, params,
{ timeout: 5000 } { timeout: 5000 }
); );
@ -197,7 +197,7 @@ export async function executeToolWithAgent(
onTokenUpdate?: (tokens: Partial<TokenBreakdown>) => void onTokenUpdate?: (tokens: Partial<TokenBreakdown>) => void
) { ) {
const toolDef = createToolDefinition(tool); const toolDef = createToolDefinition(tool);
const sanitizedToolName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.exportName}`); const sanitizedToolName = sanitizeToolName(`${tool.package.npmPackageName}-${tool.name}`);
console.log('[executeToolWithAgent] Tool name:', sanitizedToolName); console.log('[executeToolWithAgent] Tool name:', sanitizedToolName);

View file

@ -35,7 +35,7 @@ async function checkImportHealth(tool: Tool & { package: Package }): Promise<{
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName: tool.package.npmPackageName, packageName: tool.package.npmPackageName,
exportName: tool.exportName, name: tool.name,
version: tool.package.npmVersion, version: tool.package.npmVersion,
env: tool.package.env || {}, env: tool.package.env || {},
}), }),
@ -118,7 +118,7 @@ async function checkExecutionHealth(tool: Tool & { package: Package }): Promise<
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName: tool.package.npmPackageName, packageName: tool.package.npmPackageName,
exportName: tool.exportName, name: tool.name,
version: tool.package.npmVersion, version: tool.package.npmVersion,
params: testParams, params: testParams,
env: tool.package.env || {}, env: tool.package.env || {},
@ -284,7 +284,7 @@ export async function performHealthCheck(
throw new Error(`Tool not found: ${toolId}`); throw new Error(`Tool not found: ${toolId}`);
} }
console.log(`🏥 Health check starting for ${tool.package.npmPackageName}/${tool.exportName}`); console.log(`🏥 Health check starting for ${tool.package.npmPackageName}/${tool.name}`);
// Check import health // Check import health
const importResult = await checkImportHealth(tool); const importResult = await checkImportHealth(tool);

View file

@ -107,14 +107,14 @@ export type SchemaExtractionResult = SchemaExtractionSuccess | SchemaExtractionF
* Extract inputSchema from a tool by calling the executor's /load-and-describe endpoint * Extract inputSchema from a tool by calling the executor's /load-and-describe endpoint
* *
* @param packageName - NPM package name (e.g., "@tpmjs/hello-world") * @param packageName - NPM package name (e.g., "@tpmjs/hello-world")
* @param exportName - Export name (e.g., "helloWorldTool" or "default") * @param name - Export name (e.g., "helloWorldTool" or "default")
* @param version - Package version (e.g., "1.0.0") * @param version - Package version (e.g., "1.0.0")
* @param packageEnv - Package-level environment variables (optional) * @param packageEnv - Package-level environment variables (optional)
* @returns Schema extraction result with inputSchema or error * @returns Schema extraction result with inputSchema or error
*/ */
export async function extractToolSchema( export async function extractToolSchema(
packageName: string, packageName: string,
exportName: string, name: string,
version: string, version: string,
packageEnv?: Record<string, unknown> | null packageEnv?: Record<string, unknown> | null
): Promise<SchemaExtractionResult> { ): Promise<SchemaExtractionResult> {
@ -124,7 +124,7 @@ export async function extractToolSchema(
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName, packageName,
exportName, name,
version, version,
env: packageEnv || {}, env: packageEnv || {},
}), }),

View file

@ -767,7 +767,7 @@ export const manualTools: ManualTool[] = [
returns: { returns: {
type: 'object', type: 'object',
description: description:
'Search results with tool metadata including packageName, exportName, version, importUrl', 'Search results with tool metadata including packageName, name, version, importUrl',
}, },
aiAgent: { aiAgent: {
useCase: useCase:
@ -786,7 +786,7 @@ export const manualTools: ManualTool[] = [
npmPackageName: '@airweave/vercel-ai-sdk', npmPackageName: '@airweave/vercel-ai-sdk',
category: 'search', category: 'search',
frameworks: ['vercel-ai'], frameworks: ['vercel-ai'],
exportName: 'airweaveSearch', name: 'airweaveSearch',
description: 'Provides unified search across all connected data sources using semantic search.', description: 'Provides unified search across all connected data sources using semantic search.',
tags: ['search', 'rag', 'data-sources', 'semantic-search'], tags: ['search', 'rag', 'data-sources', 'semantic-search'],
env: [ env: [
@ -794,7 +794,7 @@ export const manualTools: ManualTool[] = [
name: 'AIRWEAVE_API_KEY', name: 'AIRWEAVE_API_KEY',
description: 'API key for authenticating with Airweave services.', description: 'API key for authenticating with Airweave services.',
required: true, required: true,
} },
], ],
parameters: [ parameters: [
{ {
@ -802,7 +802,7 @@ export const manualTools: ManualTool[] = [
type: 'string', type: 'string',
description: 'The default collection to search within.', description: 'The default collection to search within.',
required: true, required: true,
} },
], ],
returns: { returns: {
type: 'object', type: 'object',

View file

@ -62,7 +62,7 @@ model Tool {
package Package @relation(fields: [packageId], references: [id], onDelete: Cascade) package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
// Tool Identity // Tool Identity
exportName String @map("export_name") @db.VarChar(100) // e.g., "helloWorldTool", "default" name String @db.VarChar(100) // e.g., "helloWorldTool", "default"
// Tool Metadata // Tool Metadata
description String @db.Text description String @db.Text
@ -95,7 +95,7 @@ model Tool {
simulations Simulation[] simulations Simulation[]
healthChecks HealthCheck[] healthChecks HealthCheck[]
@@unique([packageId, exportName]) @@unique([packageId, name])
@@index([qualityScore]) @@index([qualityScore])
@@index([importHealth]) @@index([importHealth])
@@index([executionHealth]) @@index([executionHealth])

View file

@ -61,11 +61,11 @@ export async function runInteractiveCLI(): Promise<GenerationResult> {
const tools = [ const tools = [
{ {
exportName: 'exampleTool', name: 'exampleTool',
description: 'An example tool - customize this for your use case', description: 'An example tool - customize this for your use case',
}, },
{ {
exportName: 'anotherTool', name: 'anotherTool',
description: 'Another example tool - add your implementation here', description: 'Another example tool - add your implementation here',
}, },
]; ];
@ -124,9 +124,9 @@ async function generatePackage(config: GeneratorConfig): Promise<GenerationResul
// Generate tool files // Generate tool files
for (const tool of tools) { for (const tool of tools) {
const toolPath = path.join(outputPath, 'src', 'tools', `${tool.exportName}.ts`); const toolPath = path.join(outputPath, 'src', 'tools', `${tool.name}.ts`);
await writeFile(toolPath, generateToolFile(tool)); await writeFile(toolPath, generateToolFile(tool));
filesCreated.push(`src/tools/${tool.exportName}.ts`); filesCreated.push(`src/tools/${tool.name}.ts`);
} }
// Generate index.ts // Generate index.ts

View file

@ -24,7 +24,7 @@ export function generatePackageJson(config: GeneratorConfig): string {
tpmjs: { tpmjs: {
category: packageInfo.category, category: packageInfo.category,
tools: tools.map((tool) => ({ tools: tools.map((tool) => ({
name: tool.exportName, name: tool.name,
description: tool.description, description: tool.description,
})), })),
...(tools.some((t) => t.env) && { ...(tools.some((t) => t.env) && {

View file

@ -6,7 +6,7 @@ import type { GeneratorConfig } from '../types.js';
export function generateReadme(config: GeneratorConfig): string { export function generateReadme(config: GeneratorConfig): string {
const { packageInfo, tools } = config; const { packageInfo, tools } = config;
const toolsList = tools.map((tool) => `- **${tool.exportName}**: ${tool.description}`).join('\n'); const toolsList = tools.map((tool) => `- **${tool.name}**: ${tool.description}`).join('\n');
const usageExample = tools[0]; const usageExample = tools[0];
if (!usageExample) { if (!usageExample) {
@ -32,7 +32,7 @@ ${toolsList}
## Usage ## Usage
\`\`\`typescript \`\`\`typescript
import { ${usageExample.exportName} } from '${packageInfo.name}'; import { ${usageExample.name} } from '${packageInfo.name}';
import { generateText } from 'ai'; import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai'; import { openai } from '@ai-sdk/openai';
@ -40,7 +40,7 @@ const result = await generateText({
model: openai('gpt-4'), model: openai('gpt-4'),
prompt: 'Process this text for me', prompt: 'Process this text for me',
tools: { tools: {
${usageExample.exportName}, ${usageExample.name},
}, },
}); });

View file

@ -4,10 +4,10 @@ import type { ToolDefinition } from '../types.js';
* Generates a single tool file with Zod schema * Generates a single tool file with Zod schema
*/ */
export function generateToolFile(tool: ToolDefinition): string { export function generateToolFile(tool: ToolDefinition): string {
const { exportName, description } = tool; const { name, description } = tool;
// Generate schema name (capitalize first letter + "Schema") // Generate schema name (capitalize first letter + "Schema")
const schemaName = `${exportName.charAt(0).toUpperCase()}${exportName.slice(1)}Schema`; const schemaName = `${name.charAt(0).toUpperCase()}${name.slice(1)}Schema`;
// Generate simple Zod schema (can be enhanced in advanced mode) // Generate simple Zod schema (can be enhanced in advanced mode)
const schemaContent = generateSimpleSchema(); const schemaContent = generateSimpleSchema();
@ -19,7 +19,7 @@ const ${schemaName} = z.object({
${schemaContent} ${schemaContent}
}); });
export const ${exportName} = tool({ export const ${name} = tool({
description: '${description}', description: '${description}',
inputSchema: ${schemaName}, inputSchema: ${schemaName},
async execute(input: z.infer<typeof ${schemaName}>) { async execute(input: z.infer<typeof ${schemaName}>) {
@ -33,7 +33,7 @@ export const ${exportName} = tool({
} }
// TODO: Implement the tool logic here // TODO: Implement the tool logic here
console.log('${exportName} called with:', input); console.log('${name} called with:', input);
return { return {
success: true, success: true,
@ -61,7 +61,7 @@ function generateSimpleSchema(): string {
*/ */
export function generateIndexFile(tools: ToolDefinition[]): string { export function generateIndexFile(tools: ToolDefinition[]): string {
const exports = tools const exports = tools
.map((tool) => `export { ${tool.exportName} } from './tools/${tool.exportName}.js';`) .map((tool) => `export { ${tool.name} } from './tools/${tool.name}.js';`)
.join('\n'); .join('\n');
return `/** return `/**

View file

@ -10,7 +10,7 @@ program
.option('--name <name>', 'Package name (e.g., @myorg/content-tools)') .option('--name <name>', 'Package name (e.g., @myorg/content-tools)')
.option('--description <description>', 'Package description') .option('--description <description>', 'Package description')
.option('--category <category>', 'Tool category') .option('--category <category>', 'Tool category')
.option('--tool <tool...>', 'Tool definition (format: "exportName:description")') .option('--tool <tool...>', 'Tool definition (format: "name:description")')
.option('--output <path>', 'Output path') .option('--output <path>', 'Output path')
.option('--yes', 'Skip confirmation prompt') .option('--yes', 'Skip confirmation prompt')
.action(async (options) => { .action(async (options) => {

View file

@ -55,7 +55,7 @@ export async function promptTools(): Promise<ToolDefinition[] | null> {
* Prompts for a single tool definition * Prompts for a single tool definition
*/ */
async function promptSingleTool(number: number): Promise<ToolDefinition | null> { async function promptSingleTool(number: number): Promise<ToolDefinition | null> {
const exportName = await clack.text({ const name = await clack.text({
message: `Tool #${number} export name`, message: `Tool #${number} export name`,
placeholder: number === 1 ? 'summarizeText' : number === 2 ? 'extractKeywords' : 'myTool', placeholder: number === 1 ? 'summarizeText' : number === 2 ? 'extractKeywords' : 'myTool',
validate: (value) => { validate: (value) => {
@ -67,7 +67,7 @@ async function promptSingleTool(number: number): Promise<ToolDefinition | null>
}, },
}); });
if (clack.isCancel(exportName)) { if (clack.isCancel(name)) {
return null; return null;
} }
@ -93,7 +93,7 @@ async function promptSingleTool(number: number): Promise<ToolDefinition | null>
} }
return { return {
exportName: exportName as string, name: name as string,
description: description as string, description: description as string,
}; };
} }

View file

@ -3,7 +3,7 @@
*/ */
export interface ToolDefinition { export interface ToolDefinition {
exportName: string; name: string;
description: string; description: string;
parameters?: ParameterDefinition[]; parameters?: ParameterDefinition[];
returns?: ReturnDefinition; returns?: ReturnDefinition;

View file

@ -30,7 +30,7 @@ export const registryExecuteTool = tool({
properties: { properties: {
toolId: { toolId: {
type: 'string', type: 'string',
description: "Tool identifier from registrySearchTool (format: 'package::exportName')", description: "Tool identifier from registrySearchTool (format: 'package::name')",
}, },
params: { params: {
type: 'object', type: 'object',
@ -49,22 +49,22 @@ export const registryExecuteTool = tool({
additionalProperties: false, additionalProperties: false,
}), }),
async execute({ toolId, params, env }) { async execute({ toolId, params, env }) {
// Parse toolId format: "package::exportName" // Parse toolId format: "package::name"
const separatorIndex = toolId.lastIndexOf('::'); const separatorIndex = toolId.lastIndexOf('::');
if (separatorIndex === -1) { if (separatorIndex === -1) {
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`); throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
} }
const packageName = toolId.substring(0, separatorIndex); const packageName = toolId.substring(0, separatorIndex);
const exportName = toolId.substring(separatorIndex + 2); const name = toolId.substring(separatorIndex + 2);
if (!packageName || !exportName) { if (!packageName || !name) {
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`); throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
} }
// Fetch tool metadata to get version and importUrl // Fetch tool metadata to get version and importUrl
const metaParams = new URLSearchParams({ const metaParams = new URLSearchParams({
q: exportName, q: name,
limit: '10', limit: '10',
}); });
const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`); const metaResponse = await fetch(`${TPMJS_API_URL}/api/tools/search?${metaParams}`);
@ -80,7 +80,7 @@ export const registryExecuteTool = tool({
// Find the exact tool match // Find the exact tool match
// biome-ignore lint/suspicious/noExplicitAny: API response types vary // biome-ignore lint/suspicious/noExplicitAny: API response types vary
const toolMeta = toolsArray.find( const toolMeta = toolsArray.find(
(t: any) => t.package.npmPackageName === packageName && t.exportName === exportName (t: any) => t.package.npmPackageName === packageName && t.name === name
); );
if (!toolMeta) { if (!toolMeta) {
@ -96,7 +96,7 @@ export const registryExecuteTool = tool({
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
packageName, packageName,
exportName, name,
version, version,
importUrl, importUrl,
params, params,

View file

@ -81,10 +81,10 @@ export const registrySearchTool = tool({
// biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary // biome-ignore lint/suspicious/noExplicitAny: Tool types from API vary
tools: toolsArray.map((t: any) => ({ tools: toolsArray.map((t: any) => ({
// Unique identifier for registryExecuteTool // Unique identifier for registryExecuteTool
toolId: `${t.package.npmPackageName}::${t.exportName}`, toolId: `${t.package.npmPackageName}::${t.name}`,
// Human-readable info // Human-readable info
name: t.exportName, name: t.name,
package: t.package.npmPackageName, package: t.package.npmPackageName,
description: t.description, description: t.description,
category: t.package.category, category: t.package.category,

View file

@ -110,7 +110,7 @@ export const searchTpmjsToolsTool = tool({
tools: toolsArray.map((tool: any) => ({ tools: toolsArray.map((tool: any) => ({
toolId: tool.id, toolId: tool.id,
packageName: tool.package.npmPackageName, packageName: tool.package.npmPackageName,
exportName: tool.exportName, name: tool.name,
description: tool.description, description: tool.description,
category: tool.package.category, category: tool.package.category,
qualityScore: tool.qualityScore, qualityScore: tool.qualityScore,

View file

@ -27,7 +27,7 @@ async function main() {
const tools = await prisma.tool.findMany({ const tools = await prisma.tool.findMany({
select: { select: {
id: true, id: true,
exportName: true, name: true,
package: { package: {
select: { select: {
npmPackageName: true, npmPackageName: true,
@ -97,7 +97,7 @@ async function main() {
OR: [{ importHealth: 'BROKEN' }, { executionHealth: 'BROKEN' }], OR: [{ importHealth: 'BROKEN' }, { executionHealth: 'BROKEN' }],
}, },
select: { select: {
exportName: true, name: true,
package: { package: {
select: { select: {
npmPackageName: true, npmPackageName: true,
@ -113,7 +113,7 @@ async function main() {
console.log(`\n⚠ Broken Tools (${brokenTools.length}):`); console.log(`\n⚠ Broken Tools (${brokenTools.length}):`);
for (const tool of brokenTools) { for (const tool of brokenTools) {
console.log( console.log(
` - ${tool.package.npmPackageName}/${tool.exportName} (Import: ${tool.importHealth}, Execution: ${tool.executionHealth})` ` - ${tool.package.npmPackageName}/${tool.name} (Import: ${tool.importHealth}, Execution: ${tool.executionHealth})`
); );
if (tool.healthCheckError) { if (tool.healthCheckError) {
console.log(` Error: ${tool.healthCheckError.slice(0, 100)}...`); console.log(` Error: ${tool.healthCheckError.slice(0, 100)}...`);

View file

@ -66,14 +66,14 @@ async function syncHello() {
for (const toolDef of validation.tools || []) { for (const toolDef of validation.tools || []) {
const tool = await prisma.tool.upsert({ const tool = await prisma.tool.upsert({
where: { where: {
packageId_exportName: { packageId_name: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: toolDef.exportName, name: toolDef.name,
}, },
}, },
create: { create: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: toolDef.exportName, name: toolDef.name,
description: toolDef.description, description: toolDef.description,
parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined, parameters: toolDef.parameters ? (toolDef.parameters as any) : undefined,
returns: toolDef.returns ? (toolDef.returns as any) : undefined, returns: toolDef.returns ? (toolDef.returns as any) : undefined,
@ -88,13 +88,12 @@ async function syncHello() {
}, },
}); });
console.log(`✅ Tool upserted: ${tool.exportName} (${tool.id})`); console.log(`✅ Tool upserted: ${tool.name} (${tool.id})`);
} }
// Delete orphaned tools // Delete orphaned tools
const orphanedTools = existingTools.filter( const orphanedTools = existingTools.filter(
(existingTool) => (existingTool) => !validation.tools?.some((toolDef) => toolDef.name === existingTool.name)
!validation.tools?.some((toolDef) => toolDef.exportName === existingTool.exportName)
); );
if (orphanedTools.length > 0) { if (orphanedTools.length > 0) {

View file

@ -83,14 +83,14 @@ async function syncManualTools() {
// Upsert the tool // Upsert the tool
const tool = await prisma.tool.upsert({ const tool = await prisma.tool.upsert({
where: { where: {
packageId_exportName: { packageId_name: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: manualTool.name, name: manualTool.name,
}, },
}, },
create: { create: {
packageId: packageRecord.id, packageId: packageRecord.id,
exportName: manualTool.name, name: manualTool.name,
description: manualTool.description, description: manualTool.description,
// biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility // biome-ignore lint/suspicious/noExplicitAny: Prisma Json type compatibility
parameters: manualTool.parameters ? (manualTool.parameters as any) : null, parameters: manualTool.parameters ? (manualTool.parameters as any) : null,
@ -110,7 +110,7 @@ async function syncManualTools() {
}, },
}); });
console.log(` ✅ Tool upserted: ${tool.exportName} (${tool.id})`); console.log(` ✅ Tool upserted: ${tool.name} (${tool.id})`);
processed++; processed++;
} catch (error) { } catch (error) {
console.error( console.error(

View file

@ -26,20 +26,20 @@ async function testSchema() {
const tool1 = await prisma.tool.create({ const tool1 = await prisma.tool.create({
data: { data: {
packageId: pkg.id, packageId: pkg.id,
exportName: 'helloWorldTool', name: 'helloWorldTool',
description: 'Returns a simple Hello World greeting', description: 'Returns a simple Hello World greeting',
}, },
}); });
console.log(`✅ Tool 1 created: ${tool1.exportName}`); console.log(`✅ Tool 1 created: ${tool1.name}`);
const tool2 = await prisma.tool.create({ const tool2 = await prisma.tool.create({
data: { data: {
packageId: pkg.id, packageId: pkg.id,
exportName: 'helloNameTool', name: 'helloNameTool',
description: 'Returns a personalized greeting with name', description: 'Returns a personalized greeting with name',
}, },
}); });
console.log(`✅ Tool 2 created: ${tool2.exportName}\n`); console.log(`✅ Tool 2 created: ${tool2.name}\n`);
// Test 3: Query package with tools // Test 3: Query package with tools
console.log('3. Querying package with tools...'); console.log('3. Querying package with tools...');
@ -49,7 +49,7 @@ async function testSchema() {
}); });
console.log(`✅ Found package with ${packageWithTools?.tools.length} tools:`); console.log(`✅ Found package with ${packageWithTools?.tools.length} tools:`);
packageWithTools?.tools.forEach((t) => { packageWithTools?.tools.forEach((t) => {
console.log(` - ${t.exportName}: ${t.description}`); console.log(` - ${t.name}: ${t.description}`);
}); });
console.log(); console.log();
@ -58,11 +58,11 @@ async function testSchema() {
const toolWithPackage = await prisma.tool.findFirst({ const toolWithPackage = await prisma.tool.findFirst({
where: { where: {
package: { npmPackageName: '@test/hello' }, package: { npmPackageName: '@test/hello' },
exportName: 'helloWorldTool', name: 'helloWorldTool',
}, },
include: { package: true }, include: { package: true },
}); });
console.log(`✅ Found tool: ${toolWithPackage?.exportName}`); console.log(`✅ Found tool: ${toolWithPackage?.name}`);
console.log(` Package: ${toolWithPackage?.package.npmPackageName}`); console.log(` Package: ${toolWithPackage?.package.npmPackageName}`);
console.log(` Category: ${toolWithPackage?.package.category}\n`); console.log(` Category: ${toolWithPackage?.package.category}\n`);