refactor: replace exportName with name throughout codebase

- Update TpmjsToolDefinitionSchema to only use 'name' field
- Add 'sandbox' as valid category for sprites tools
- Update all package.json files to use 'name' instead of 'exportName'
- Update documentation and source files accordingly
- Add 11 new sprites tools for sandbox/code-execution
This commit is contained in:
Ajax Davis 2026-01-14 14:18:36 +10:00
parent b1dd3371cd
commit 2cd2b10cd0
91 changed files with 4019 additions and 195 deletions

View file

@ -0,0 +1,16 @@
---
description: Cancel the active Ralph loop
command: rm -f .claude/ralph-loop.local.md && echo "Ralph loop cancelled"
---
# Cancel Ralph Loop
Immediately cancel any active Ralph loop and allow normal session exit.
## Usage
```
/cancel-ralph
```
This removes the state file that drives the loop, allowing the session to exit normally.

View file

@ -0,0 +1,43 @@
---
description: Start Ralph Wiggum loop in current session
command: "${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
---
# Ralph Loop
Start an iterative development loop that continues until the task is complete.
## Usage
```
/ralph-loop "Your task description" [--max-iterations N] [--validation-script PATH] [--completion-promise TEXT]
```
## How It Works
1. You provide a task and optional validation criteria
2. Claude works on the task
3. When Claude tries to exit, the stop hook intercepts
4. If validation fails OR completion promise not met, the loop continues
5. Claude sees previous work and continues iterating
6. Loop ends when validation passes or max iterations reached
## Important Rules
- If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE
- Do NOT use false completion promises as an exit strategy
- The loop persists until genuine completion is achieved
- Use validation scripts for programmatic verification
## Examples
```bash
# With validation script only
/ralph-loop "Build the SDK package" --validation-script ./scripts/validate-sdk.sh
# With completion promise
/ralph-loop "Fix all type errors" --completion-promise "ALL_TYPES_PASS"
# With both
/ralph-loop "Complete feature X" --max-iterations 15 --validation-script ./validate.sh --completion-promise "FEATURE_COMPLETE"
```

View file

@ -0,0 +1,15 @@
{
"description": "Ralph Wiggum plugin stop hook for self-referential loops",
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
}
]
}
]
}
}

View file

@ -0,0 +1,111 @@
#!/bin/bash
# Ralph Wiggum Stop Hook - Self-referential loop for iterative development
# This hook intercepts the Stop event and decides whether to continue the loop
set -euo pipefail
STATE_FILE=".claude/ralph-loop.local.md"
TRANSCRIPT_FILE="${CLAUDE_TRANSCRIPT:-}"
# Check if ralph loop is active
if [[ ! -f "$STATE_FILE" ]]; then
# No active loop, allow normal exit
exit 0
fi
# Parse the state file frontmatter
parse_frontmatter() {
local key="$1"
sed -n '/^---$/,/^---$/p' "$STATE_FILE" | grep "^${key}:" | sed "s/^${key}: *//" | tr -d '"'
}
iteration=$(parse_frontmatter "iteration")
max_iterations=$(parse_frontmatter "max_iterations")
completion_promise=$(parse_frontmatter "completion_promise")
prompt=$(parse_frontmatter "prompt")
validation_script=$(parse_frontmatter "validation_script")
# Validate numeric fields
if ! [[ "$iteration" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid iteration count in state file" >&2
rm -f "$STATE_FILE"
exit 0
fi
if ! [[ "$max_iterations" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid max_iterations in state file" >&2
rm -f "$STATE_FILE"
exit 0
fi
# Check if max iterations reached
if [[ "$max_iterations" -gt 0 ]] && [[ "$iteration" -ge "$max_iterations" ]]; then
echo "Ralph loop reached max iterations ($max_iterations). Exiting." >&2
rm -f "$STATE_FILE"
exit 0
fi
# Run validation script if provided
validation_passed=false
if [[ -n "$validation_script" ]] && [[ -f "$validation_script" ]]; then
echo "Running validation script: $validation_script" >&2
if bash "$validation_script" 2>&1; then
validation_passed=true
echo "Validation PASSED!" >&2
else
echo "Validation FAILED. Continuing loop..." >&2
fi
fi
# Check for completion promise in transcript
if [[ -n "$completion_promise" ]] && [[ -n "$TRANSCRIPT_FILE" ]] && [[ -f "$TRANSCRIPT_FILE" ]]; then
# Get the last assistant message
last_message=$(tail -100 "$TRANSCRIPT_FILE" | grep -o '<promise>[^<]*</promise>' | tail -1 | sed 's/<promise>\(.*\)<\/promise>/\1/' || true)
if [[ "$last_message" == "$completion_promise" ]]; then
# Also check if validation passed (if validation script exists)
if [[ -z "$validation_script" ]] || [[ "$validation_passed" == "true" ]]; then
echo "Completion promise matched and validation passed. Ralph loop complete!" >&2
rm -f "$STATE_FILE"
exit 0
else
echo "Completion promise matched but validation failed. Continuing..." >&2
fi
fi
fi
# If validation passed without explicit promise, we can exit
if [[ "$validation_passed" == "true" ]] && [[ -z "$completion_promise" ]]; then
echo "Validation passed. Ralph loop complete!" >&2
rm -f "$STATE_FILE"
exit 0
fi
# Increment iteration
new_iteration=$((iteration + 1))
# Update state file
sed -i.bak "s/^iteration: .*/iteration: $new_iteration/" "$STATE_FILE"
rm -f "${STATE_FILE}.bak"
# Build the continuation message
cat << EOF
{
"decision": "block",
"reason": "Ralph loop iteration $new_iteration of $max_iterations",
"message": "
---
RALPH LOOP - Iteration $new_iteration / $max_iterations
---
Continue working on the task. Your previous iteration's work is preserved in the codebase.
TASK: $prompt
$(if [[ -n "$validation_script" ]]; then echo "VALIDATION: Run the validation to check progress. Script: $validation_script"; fi)
$(if [[ -n "$completion_promise" ]]; then echo "COMPLETION: Output <promise>$completion_promise</promise> ONLY when the task is completely done AND validation passes."; fi)
Review what you've done so far and continue from where you left off.
"
}
EOF

View file

@ -0,0 +1,120 @@
#!/bin/bash
# Setup Ralph Loop - Initialize the iterative development loop
# Usage: setup-ralph-loop.sh "PROMPT" [--max-iterations N] [--completion-promise TEXT] [--validation-script PATH]
set -euo pipefail
STATE_FILE=".claude/ralph-loop.local.md"
# Default values
MAX_ITERATIONS=20
COMPLETION_PROMISE=""
VALIDATION_SCRIPT=""
PROMPT=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--max-iterations)
MAX_ITERATIONS="$2"
shift 2
;;
--completion-promise)
COMPLETION_PROMISE="$2"
shift 2
;;
--validation-script)
VALIDATION_SCRIPT="$2"
shift 2
;;
--help|-h)
cat << EOF
Ralph Loop Setup
Usage: setup-ralph-loop.sh "PROMPT" [OPTIONS]
Options:
--max-iterations N Maximum iterations before stopping (default: 20, 0 = unlimited)
--completion-promise TXT Phrase to output when complete (use <promise>TXT</promise>)
--validation-script PATH Script to run for validation (exit 0 = pass)
--help, -h Show this help
Example:
setup-ralph-loop.sh "Build the SDK package" --max-iterations 10 --validation-script ./validate.sh
EOF
exit 0
;;
*)
if [[ -z "$PROMPT" ]]; then
PROMPT="$1"
else
PROMPT="$PROMPT $1"
fi
shift
;;
esac
done
# Validate prompt
if [[ -z "$PROMPT" ]]; then
echo "Error: PROMPT is required" >&2
exit 1
fi
# Validate max iterations
if ! [[ "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then
echo "Error: --max-iterations must be a number" >&2
exit 1
fi
# Validate validation script exists if provided
if [[ -n "$VALIDATION_SCRIPT" ]] && [[ ! -f "$VALIDATION_SCRIPT" ]]; then
echo "Error: Validation script not found: $VALIDATION_SCRIPT" >&2
exit 1
fi
# Create state directory
mkdir -p "$(dirname "$STATE_FILE")"
# Create state file
cat << EOF > "$STATE_FILE"
---
iteration: 1
max_iterations: $MAX_ITERATIONS
completion_promise: "$COMPLETION_PROMISE"
validation_script: "$VALIDATION_SCRIPT"
prompt: "$PROMPT"
started_at: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
---
# Ralph Loop State
This file tracks the state of an active Ralph loop. DO NOT DELETE while loop is running.
## Configuration
- **Task**: $PROMPT
- **Max Iterations**: $MAX_ITERATIONS
- **Validation Script**: ${VALIDATION_SCRIPT:-"None"}
- **Completion Promise**: ${COMPLETION_PROMISE:-"None (validation only)"}
## Progress Log
Iteration logs will be appended below as the loop progresses.
---
EOF
echo "Ralph loop initialized!"
echo " Task: $PROMPT"
echo " Max iterations: $MAX_ITERATIONS"
echo " Validation: ${VALIDATION_SCRIPT:-"None"}"
echo " Completion promise: ${COMPLETION_PROMISE:-"None"}"
echo ""
echo "The loop will continue until:"
if [[ -n "$VALIDATION_SCRIPT" ]]; then
echo " - Validation script passes ($VALIDATION_SCRIPT returns exit code 0)"
fi
if [[ -n "$COMPLETION_PROMISE" ]]; then
echo " - You output: <promise>$COMPLETION_PROMISE</promise>"
fi
echo " - OR max iterations ($MAX_ITERATIONS) is reached"

View file

@ -154,7 +154,7 @@ automation, ai-ml, security, monitoring
- NPM_MIRROR: Different formula entirely
### Field Names Inconsistent
- `exportName` used in MANUAL_TOOLS but not in HOW_TO_PUBLISH
- `name` used in MANUAL_TOOLS but not in HOW_TO_PUBLISH
- Deprecated fields (`parameters`, `returns`) mentioned but unclear when deprecated
---

View file

@ -45,7 +45,7 @@ Edit `manual-tools.ts` and add a new entry:
npmPackageName: 'example-package',
category: 'search',
frameworks: ['vercel-ai'],
exportName: 'exampleTool',
name: 'exampleTool',
description: 'A clear, concise description of what this tool does',
// Optional but recommended for 'rich' tier
@ -116,24 +116,24 @@ pnpm dev --filter=@tpmjs/web
## Multi-Tool Packages
If a package exports multiple tools, add multiple entries with the same `npmPackageName` but different `exportName`:
If a package exports multiple tools, add multiple entries with the same `npmPackageName` but different `name`:
```typescript
{
npmPackageName: 'firecrawl-aisdk',
exportName: 'scrapeTool',
name: 'scrapeTool',
description: 'Scrape websites...',
// ...
},
{
npmPackageName: 'firecrawl-aisdk',
exportName: 'searchTool',
name: 'searchTool',
description: 'Search the web...',
// ...
},
{
npmPackageName: 'firecrawl-aisdk',
exportName: 'crawlTool',
name: 'crawlTool',
description: 'Crawl entire websites...',
// ...
},

149
TOP_5_PRIORITIES.md Normal file
View file

@ -0,0 +1,149 @@
# Top 5 Priorities for TPMJS to Become Production-Ready
> Based on comprehensive codebase analysis - January 2026
TPMJS is approximately 70-75% towards being production-ready for widespread use. The platform has solid fundamentals: a well-architected monorepo, robust npm sync system, working MCP integration, and professional code quality standards. However, five critical gaps need addressing before TPMJS can become the "npm for AI tools" it aspires to be.
---
## 1. Complete the Developer SDK Packages
**The Problem:**
Developers can't easily integrate TPMJS tools into their applications. The SDK packages `@tpmjs/registry-search` and `@tpmjs/registry-execute` are either incomplete or missing. This defeats the core value proposition - if AI agents can't programmatically discover and execute tools from TPMJS, the registry is just a website, not an ecosystem.
**What's Needed:**
- `@tpmjs/registry-search` - TypeScript client for searching tools with full type safety
- `@tpmjs/registry-execute` - Execute any TPMJS tool from any Node.js application
- `@tpmjs/agent-toolkit` - Pre-built integration for popular agent frameworks (LangChain, AutoGPT, CrewAI)
- Clear examples showing integration with Claude, GPT-4, and other LLMs
**Impact:**
Without these SDKs, TPMJS is just a directory. With them, every AI developer can instantly access 100+ tools with a single `npm install`. This is the difference between a catalog and a platform.
**Effort:** 2-4 weeks of focused development
---
## 2. Add Social Proof and Discovery Features
**The Problem:**
Users have no way to evaluate tool quality beyond download counts. There's no star rating, no reviews, no "trending this week," and no recommendations. When browsing tools, users can't distinguish battle-tested tools from abandoned experiments.
**What's Needed:**
- **5-star rating system** with verified user ratings
- **User reviews** with upvoting and author responses
- **Trending tools** algorithm (based on recent usage, not just total downloads)
- **"Staff Picks"** or curated collections for common use cases
- **Similar tools** recommendations on each tool page
- **Usage statistics** - "Used in 50 agents" or "10,000 executions this month"
**Impact:**
Social proof is essential for adoption. GitHub has stars, npm has weekly downloads prominently displayed, Product Hunt has upvotes. TPMJS needs its own trust signals. Without them, users default to building their own tools or using alternatives they can evaluate.
**Effort:** 3-4 weeks including UI/UX design
---
## 3. Build Comprehensive Documentation and Onboarding
**The Problem:**
The publishing guide exists but there's no interactive tutorial for new users. API documentation is schema-only with no examples. Developers looking to build tools, create agents, or integrate TPMJS into their workflow face a steep learning curve with limited guidance.
**What's Needed:**
- **Interactive onboarding flow** - Guided first-time experience creating an agent with tools
- **API documentation** with copy-paste examples for every endpoint
- **Video tutorials** - 5-minute quickstarts for common tasks
- **Example agents** - Pre-built agents demonstrating best practices (research agent, coding assistant, data analyst)
- **Tool development guide** - Step-by-step from `npm init` to published tool
- **Troubleshooting guide** - Common errors and solutions
**Impact:**
Documentation is a product feature. Every hour spent on docs saves thousands of hours of user frustration. LangChain succeeded partly because of excellent docs. TPMJS needs the same investment.
**Effort:** 4-6 weeks for comprehensive documentation overhaul
---
## 4. Build Observability and Platform Trust
**The Problem:**
There's no public status page, no platform-wide health dashboard, and limited visibility into what's working. Users can't answer basic questions: "Is TPMJS up?", "How reliable is this tool?", "What's the average response time?"
**What's Needed:**
- **Public status page** (status.tpmjs.com) showing real-time platform health
- **Tool health dashboard** - Aggregate view of which tools are healthy/broken
- **Response time metrics** - P50/P95/P99 latency for tool executions
- **Uptime guarantees** - Published SLA (even informal "99.9% target")
- **Incident history** - Transparent communication about outages
- **Usage analytics dashboard** - For tool authors to see how their tools are used
**Impact:**
Trust is earned through transparency. AWS publishes their health dashboard. GitHub has status.github.com. Enterprises won't adopt platforms they can't monitor. Even individual developers want to know if their agent's failures are their code or the platform.
**Effort:** 2-3 weeks for MVP status page and health dashboard
---
## 5. Add Team and Enterprise Features
**The Problem:**
TPMJS is individual-only. There's no way to share collections within a team, manage API keys across an organization, or implement approval workflows. This blocks enterprise adoption where multiple developers need to collaborate on agent tooling.
**What's Needed:**
- **Organizations** - Create teams with shared collections and agents
- **Role-based access control (RBAC)** - Admin, Developer, Viewer roles
- **Shared API keys** - Organization-scoped keys with usage attribution
- **Audit logging** - Who did what, when (required for compliance)
- **Private tools** - Organization-only tool publishing
- **SSO/SAML** - Enterprise identity provider integration
- **Usage quotas** - Set limits per team member or project
**Impact:**
Enterprise customers pay for tools. They also require these features for security and compliance. One enterprise contract can fund months of development. More importantly, enterprise adoption validates the platform and attracts more developers.
**Effort:** 6-8 weeks for core team features, 3-6 months for full enterprise suite
---
## Summary
| Priority | Impact | Effort | Recommended Order |
|----------|--------|--------|-------------------|
| 1. Complete SDK Packages | Critical | 2-4 weeks | First |
| 2. Social Proof/Discovery | High | 3-4 weeks | Second |
| 3. Documentation | High | 4-6 weeks | Parallel with #2 |
| 4. Observability/Trust | Medium-High | 2-3 weeks | Third |
| 5. Enterprise Features | Medium | 6-8 weeks | Fourth |
**Recommended approach:**
1. **Weeks 1-4:** Complete SDK packages (unlocks programmatic adoption)
2. **Weeks 2-6:** Build ratings/reviews and documentation in parallel
3. **Weeks 7-9:** Add status page and health dashboard
4. **Weeks 10+:** Begin enterprise features based on customer demand
---
## Current Strengths to Leverage
TPMJS already has strong foundations:
- Robust npm sync system (tools auto-discovered)
- Working MCP protocol integration
- Clean monorepo architecture
- Good authentication system
- Solid database design
- Quality coding standards
These investments mean the platform can scale. The gaps identified above are about adoption and trust, not technical architecture.
---
## The Bottom Line
TPMJS has built a good tool registry. To become **the** AI tools platform, it needs to:
1. Make tools easy to use programmatically (SDKs)
2. Help users find good tools (social proof)
3. Help developers build tools (documentation)
4. Build platform confidence (observability)
5. Enable team adoption (enterprise features)
With focused effort on these five areas over the next 3-6 months, TPMJS can establish itself as the definitive platform for AI agent tooling.

View file

@ -21,7 +21,7 @@ Load a tool from esm.sh and return its schema
```json
{
"packageName": "firecrawl-aisdk",
"exportName": "webSearchTool",
"name": "webSearchTool",
"version": "0.7.2",
"importUrl": "https://esm.sh/firecrawl-aisdk@0.7.2"
}
@ -32,7 +32,7 @@ Load a tool from esm.sh and return its schema
{
"success": true,
"tool": {
"exportName": "webSearchTool",
"name": "webSearchTool",
"description": "Search the web using Firecrawl",
"inputSchema": { ... }
}
@ -46,7 +46,7 @@ Execute a tool with parameters
```json
{
"packageName": "firecrawl-aisdk",
"exportName": "webSearchTool",
"name": "webSearchTool",
"version": "0.7.2",
"params": {
"query": "latest AI news"
@ -128,7 +128,7 @@ curl -X POST http://localhost:3001/load-and-describe \
-H "Content-Type: application/json" \
-d '{
"packageName": "@tpmjs/hello",
"exportName": "helloWorldTool",
"name": "helloWorldTool",
"version": "0.1.0"
}'
```
@ -139,7 +139,7 @@ curl -X POST http://localhost:3001/execute-tool \
-H "Content-Type: application/json" \
-d '{
"packageName": "@tpmjs/hello",
"exportName": "helloWorldTool",
"name": "helloWorldTool",
"version": "0.1.0",
"params": {}
}'

View file

@ -34,16 +34,16 @@ app.get('/health', (req, res) => {
* Returns tool metadata (description, schema) without executing
*/
app.post('/load-and-describe', async (req, res) => {
const { packageName, exportName, version, importUrl } = req.body;
const { packageName, name, version, importUrl } = req.body;
if (!packageName || !exportName || !version) {
if (!packageName || !name || !version) {
return res.status(400).json({
success: false,
error: 'Missing required fields: packageName, exportName, version',
error: 'Missing required fields: packageName, name, version',
});
}
const cacheKey = `${packageName}::${exportName}`;
const cacheKey = `${packageName}::${name}`;
try {
let toolModule;
@ -72,13 +72,13 @@ app.post('/load-and-describe', async (req, res) => {
// esm.sh returns ES modules, try to get default or named export
const module = moduleExports.default || moduleExports;
toolModule = module[exportName] || module;
toolModule = module[name] || module;
if (!toolModule) {
console.error(`❌ Export "${exportName}" not found. Available:`, Object.keys(module));
console.error(`❌ Export "${name}" not found. Available:`, Object.keys(module));
return res.status(404).json({
success: false,
error: `Export "${exportName}" not found in module`,
error: `Export "${name}" not found in module`,
availableExports: Object.keys(module),
});
}
@ -108,7 +108,7 @@ app.post('/load-and-describe', async (req, res) => {
res.json({
success: true,
tool: {
exportName,
name,
description: toolModule.description,
inputSchema: toolModule.inputSchema || toolModule.parameters?.shape || {},
},
@ -127,16 +127,16 @@ app.post('/load-and-describe', async (req, res) => {
* Execute a dynamically loaded tool with parameters
*/
app.post('/execute-tool', async (req, res) => {
const { packageName, exportName, version, importUrl, params } = req.body;
const { packageName, name, version, importUrl, params } = req.body;
if (!packageName || !exportName || !version) {
if (!packageName || !name || !version) {
return res.status(400).json({
success: false,
error: 'Missing required fields: packageName, exportName, version',
error: 'Missing required fields: packageName, name, version',
});
}
const cacheKey = `${packageName}::${exportName}`;
const cacheKey = `${packageName}::${name}`;
const startTime = Date.now();
try {
@ -151,7 +151,7 @@ app.post('/execute-tool', async (req, res) => {
console.log(`📦 Importing for execution: ${url}`);
const module = await import(url);
toolModule = module[exportName];
toolModule = module[name];
if (!toolModule || !toolModule.execute) {
return res.status(404).json({

View file

@ -550,9 +550,9 @@ async function executeTool(req: Request): Promise<Response> {
Deno.env.set(key, stringValue);
// ALSO set in Node.js process.env (for npm: imports)
// @ts-ignore - process is available in Node.js compatibility mode
// @ts-expect-error - process is available in Node.js compatibility mode
if (typeof globalThis.process !== 'undefined' && globalThis.process.env) {
// @ts-ignore - process.env exists in Node compat mode
// @ts-expect-error - process.env exists in Node compat mode
globalThis.process.env[key] = stringValue;
}
@ -782,10 +782,10 @@ async function listExports(req: Request): Promise<Response> {
error?: string;
}> = [];
for (const exportName of allExports) {
if (exportName === 'default') continue;
for (const exportKey of allExports) {
if (exportKey === 'default') continue;
let rawExport = module[exportName];
let rawExport = module[exportKey];
// Check if it's a factory function
if (typeof rawExport === 'function' && !rawExport.description && !rawExport.execute) {
@ -809,14 +809,14 @@ async function listExports(req: Request): Promise<Response> {
// Check if it's a valid AI SDK tool
if (rawExport?.description && rawExport?.execute) {
tools.push({
name: exportName,
name: exportKey,
isValidTool: true,
description: rawExport.description,
});
} else if (typeof rawExport === 'object' && rawExport !== null) {
// It's an object but not a valid tool - might be a factory that needs specific config
tools.push({
name: exportName,
name: exportKey,
isValidTool: false,
error: 'Not a valid AI SDK tool (missing description or execute)',
});

View file

@ -10,27 +10,27 @@ interface ToolDetailPageProps {
}
/**
* Parse the URL slug to extract package name and optional export name
* Parse the URL slug to extract package name and optional tool name
*/
function parseSlug(slug: string[]): { packageName: string; exportName?: string } {
function parseSlug(slug: string[]): { packageName: string; toolName?: string } {
// URL-decode slug components (@ comes as %40)
const decodedSlug = slug.map((s) => decodeURIComponent(s));
if (decodedSlug[0]?.startsWith('@')) {
// Scoped package: ['@scope', 'package', 'exportName?']
// Scoped package: ['@scope', 'package', 'toolName?']
const packageName = decodedSlug.slice(0, 2).join('/');
const exportName = decodedSlug[2];
return { packageName, exportName };
const toolName = decodedSlug[2];
return { packageName, toolName };
}
// Unscoped: ['package', 'exportName?']
return { packageName: decodedSlug[0] || '', exportName: decodedSlug[1] };
// Unscoped: ['package', 'toolName?']
return { packageName: decodedSlug[0] || '', toolName: decodedSlug[1] };
}
/**
* Fetch tool data from database
*/
async function getTool(slug: string[]): Promise<Tool | null> {
const { packageName, exportName } = parseSlug(slug);
const { packageName, toolName } = parseSlug(slug);
if (!packageName) {
return null;
@ -49,7 +49,7 @@ async function getTool(slug: string[]): Promise<Tool | null> {
const tool = await prisma.tool.findFirst({
where: {
packageId: pkg.id,
...(exportName && { name: exportName }),
...(toolName && { name: toolName }),
},
include: {
package: true,
@ -122,9 +122,9 @@ export async function generateMetadata({ params }: ToolDetailPageProps): Promise
};
}
const { packageName, exportName } = parseSlug(slug);
const ogPath = exportName
? `/api/og/tool/${encodeURIComponent(packageName)}/${encodeURIComponent(exportName)}`
const { packageName, toolName } = parseSlug(slug);
const ogPath = toolName
? `/api/og/tool/${encodeURIComponent(packageName)}/${encodeURIComponent(toolName)}`
: `/api/og/tool/${encodeURIComponent(packageName)}`;
return {

View file

@ -102,41 +102,41 @@ export function normalizePath(path: string): string {
}
/**
* Parse tool path to extract package name and export name
* Parse tool path to extract package name and tool name
*/
function parseToolPath(path: string): { packageName: string; exportName?: string } {
function parseToolPath(path: string): { packageName: string; toolName?: string } {
// Remove /tool/ prefix
const segments = path.replace(/^\/tool\//, '').split('/');
let packageName: string;
let exportName: string | undefined;
let toolName: string | undefined;
if (segments[0]?.startsWith('@')) {
// Scoped package: @scope/package/export
// Scoped package: @scope/package/toolName
packageName = segments.slice(0, 2).join('/');
exportName = segments[2];
toolName = segments[2];
} else {
// Unscoped: package/export
// Unscoped: package/toolName
packageName = segments[0] || '';
exportName = segments[1];
toolName = segments[1];
}
return { packageName, exportName };
return { packageName, toolName };
}
/**
* Fetch tool data from internal API
*/
async function fetchToolContent(path: string): Promise<PageContent> {
const { packageName, exportName } = parseToolPath(path);
const { packageName, toolName } = parseToolPath(path);
// Build API URL
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
const apiPath = exportName
? `/api/tools/${encodeURIComponent(packageName)}/${encodeURIComponent(exportName)}`
const apiPath = toolName
? `/api/tools/${encodeURIComponent(packageName)}/${encodeURIComponent(toolName)}`
: `/api/tools/${encodeURIComponent(packageName)}`;
try {
@ -157,11 +157,11 @@ async function fetchToolContent(path: string): Promise<PageContent> {
return {
pageType: 'tool',
title: tool.name || exportName || packageName,
title: tool.name || toolName || packageName,
description: tool.description || `AI tool from ${packageName}`,
keywords: [tool.package?.category || 'tool', 'AI', 'npm', packageName],
tool: {
name: tool.name || exportName || 'Tool',
name: tool.name || toolName || 'Tool',
packageName: tool.package?.npmPackageName || packageName,
category: tool.package?.category || 'other',
description: tool.description || '',
@ -175,11 +175,11 @@ async function fetchToolContent(path: string): Promise<PageContent> {
// Return basic content on failure
return {
pageType: 'tool',
title: exportName || packageName,
title: toolName || packageName,
description: `AI tool from ${packageName}`,
keywords: ['tool', 'AI', 'npm'],
tool: {
name: exportName || 'Tool',
name: toolName || 'Tool',
packageName,
category: 'other',
description: '',

View file

@ -122,7 +122,7 @@ TPMJS provides two main tools:
```typescript
import { registrySearchTool } from '@tpmjs/registry-search';
// Returns tools with toolIds in format: "package::exportName"
// Returns tools with toolIds in format: "package::name"
const result = await registrySearchTool.execute({
query: 'web scraping',
category: 'web-scraping',
@ -203,9 +203,9 @@ const myRegistryExecuteTool = tool({
// YOUR CUSTOM EXECUTE FUNCTION
async execute({ toolId, params, env }) {
// Option 1: Execute locally instead of sandbox
const [packageName, exportName] = toolId.split('::');
const [packageName, name] = toolId.split('::');
const pkg = await import(packageName);
const toolFn = pkg[exportName];
const toolFn = pkg[name];
return toolFn.execute(params);
// Option 2: Route to your own executor
@ -700,7 +700,7 @@ async function executeSandbox(
timeout: number,
abortSignal?: AbortSignal
) {
const [packageName, exportName] = toolId.split('::');
const [packageName, name] = toolId.split('::');
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
@ -714,7 +714,7 @@ async function executeSandbox(
const response = await fetch(`${executorUrl}/execute-tool`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ packageName, exportName, params, env }),
body: JSON.stringify({ packageName, name, params, env }),
signal: combinedSignal,
});
@ -735,14 +735,14 @@ async function executeLocal(
params: any,
basePath?: string
) {
const [packageName, exportName] = toolId.split('::');
const [packageName, name] = toolId.split('::');
const modulePath = basePath ? `${basePath}/${packageName}` : packageName;
const module = await import(modulePath);
const toolFn = module[exportName] || module.default;
const toolFn = module[name] || module.default;
if (!toolFn?.execute) {
throw new Error(`Tool ${exportName} not found or missing execute function`);
throw new Error(`Tool ${name} not found or missing execute function`);
}
return toolFn.execute(params);
@ -1151,7 +1151,7 @@ type SearchOutput = {
query: string;
matchCount: number;
tools: Array<{
toolId: string; // "package::exportName"
toolId: string; // "package::name"
name: string;
package: string;
description: string;
@ -1170,7 +1170,7 @@ import { registryExecuteTool } from '@tpmjs/registry-execute';
// Input
type ExecuteInput = {
toolId: string; // "package::exportName"
toolId: string; // "package::name"
params: Record<string, unknown>; // Tool parameters
env?: Record<string, string>; // Environment variables
};

View file

@ -72,7 +72,7 @@ A **tool** in TPMJS is an atomic unit of computation that:
```typescript
// Tool metadata structure
interface Tool {
exportName: string;
name: string;
description: string;
parameters: Parameter[];
returns: ReturnType;
@ -336,7 +336,7 @@ interface ExecutionStep {
// Tool reference
tool: {
packageName: string;
exportName: string;
name: string;
version?: string;
};
@ -415,7 +415,7 @@ interface ExecutionStep {
"order": 1,
"tool": {
"packageName": "tpmjs-web-scraper",
"exportName": "scrapeUrl"
"name": "scrapeUrl"
},
"purpose": "Fetch the product listings page HTML",
"input": {
@ -448,7 +448,7 @@ interface ExecutionStep {
"order": 2,
"tool": {
"packageName": "tpmjs-html-parser",
"exportName": "extractElements"
"name": "extractElements"
},
"purpose": "Extract product cards from the HTML",
"input": {
@ -477,7 +477,7 @@ interface ExecutionStep {
"order": 3,
"tool": {
"packageName": "tpmjs-price-extractor",
"exportName": "extractPrices"
"name": "extractPrices"
},
"purpose": "Parse and normalize price values",
"input": {
@ -507,7 +507,7 @@ interface ExecutionStep {
"order": 3,
"tool": {
"packageName": "tpmjs-text-cleaner",
"exportName": "cleanProductNames"
"name": "cleanProductNames"
},
"purpose": "Clean and normalize product names",
"input": {
@ -533,7 +533,7 @@ interface ExecutionStep {
"order": 4,
"tool": {
"packageName": "tpmjs-data-merger",
"exportName": "mergeArrays"
"name": "mergeArrays"
},
"purpose": "Combine prices and names into product objects",
"input": {
@ -561,7 +561,7 @@ interface ExecutionStep {
"order": 5,
"tool": {
"packageName": "tpmjs-spreadsheet-generator",
"exportName": "createXlsx"
"name": "createXlsx"
},
"purpose": "Generate Excel spreadsheet with price comparison",
"input": {
@ -1587,7 +1587,7 @@ async function executeWithFallback(
const modifiedStep = { ...step, tool };
return await executeStep(modifiedStep, context);
} catch (error) {
console.log(`Tool ${tool.exportName} failed, trying fallback...`);
console.log(`Tool ${tool.name} failed, trying fallback...`);
}
}

View file

@ -25,7 +25,7 @@
* TOOL
*
* packageName: "tpmjs-web-scraper"
* exportName: "scrapeUrl"
* name: "scrapeUrl"
* description: "Fetches webpage..."
* parameters: [...]
* returns: { type: "string" }
@ -37,7 +37,7 @@
export interface Tool {
id: string;
packageName: string;
exportName: string;
name: string;
description: string;
parameters: ToolParameter[];
returns: ToolReturn;
@ -254,7 +254,7 @@ export interface PlanStep {
export interface ToolReference {
packageName: string;
exportName: string;
name: string;
version?: string;
}
@ -743,7 +743,7 @@ export interface ToolContext {
}
export interface ToolPreview {
exportName: string;
name: string;
purpose: string;
tokenEstimate: number;
}

View file

@ -112,10 +112,10 @@ Returns tool metadata including name, description, required env vars, and how to
matchCount: data.data.length,
tools: data.data.map((tool: any) => ({
// Unique identifier for registryExecuteTool
toolId: `${tool.package.npmPackageName}::${tool.exportName}`,
toolId: `${tool.package.npmPackageName}::${tool.name}`,
// Human-readable info
name: tool.exportName,
name: tool.name,
package: tool.package.npmPackageName,
description: tool.description,
category: tool.category,
@ -147,21 +147,21 @@ Use registrySearchTool first to find the toolId, then call this with the toolId
The tool runs in a secure sandbox - you don't need to install anything.`,
parameters: z.object({
toolId: z.string().describe('Tool identifier from registrySearchTool (format: "package::exportName")'),
toolId: z.string().describe('Tool identifier from registrySearchTool (format: "package::name")'),
params: z.record(z.any()).describe('Parameters to pass to the tool'),
env: z.record(z.string()).optional().describe('Environment variables (API keys) if required'),
}),
execute: async ({ toolId, params, env }) => {
const [packageName, exportName] = toolId.split('::');
const [packageName, name] = toolId.split('::');
if (!packageName || !exportName) {
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
if (!packageName || !name) {
throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
}
// Get tool metadata to find version and importUrl
const metaResponse = await fetch(
`${TPMJS_API_URL}/api/tools?package=${encodeURIComponent(packageName)}&export=${encodeURIComponent(exportName)}`
`${TPMJS_API_URL}/api/tools?package=${encodeURIComponent(packageName)}&export=${encodeURIComponent(name)}`
);
const metaData = await metaResponse.json();
const toolMeta = metaData.data?.[0];
@ -176,7 +176,7 @@ The tool runs in a secure sandbox - you don't need to install anything.`,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
name,
version: toolMeta.package.npmVersion,
importUrl: toolMeta.importUrl || `https://esm.sh/${packageName}@${toolMeta.package.npmVersion}`,
params,
@ -291,7 +291,7 @@ Response:
"data": [
{
"id": "...",
"exportName": "webSearch",
"name": "webSearch",
"description": "Search the web...",
"category": "search",
"executionHealth": "HEALTHY",
@ -319,7 +319,7 @@ Request:
```json
{
"packageName": "@exalabs/ai-sdk",
"exportName": "webSearch",
"name": "webSearch",
"version": "1.0.5",
"importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5",
"params": { "query": "latest AI news" },

View file

@ -132,10 +132,10 @@ After every tool execution, the executor reports the result:
```typescript
// On successful execution
reportToolHealth(packageName, exportName, true).catch(() => {});
reportToolHealth(packageName, name, true).catch(() => {});
// On failed execution
reportToolHealth(packageName, exportName, false, error.message).catch(() => {});
reportToolHealth(packageName, name, false, error.message).catch(() => {});
```
The reporting is non-blocking (fire-and-forget) to avoid slowing down tool execution.
@ -164,7 +164,7 @@ curl -s 'https://tpmjs.com/api/tools?limit=50' \
-H 'Authorization: Bearer tpmjs_sk_your_api_key_here' | \
jq '.data[] | select(.package.npmPackageName == "PACKAGE_NAME") | {
packageName: .package.npmPackageName,
exportName: .exportName,
name: .name,
importHealth: .importHealth,
executionHealth: .executionHealth,
healthCheckError: .healthCheckError,
@ -188,7 +188,7 @@ curl -X POST 'https://tpmjs.com/api/tools/report-health' \
-H 'Content-Type: application/json' \
-d '{
"packageName": "@scope/package",
"exportName": "toolName",
"name": "toolName",
"success": true
}'
```
@ -197,7 +197,7 @@ curl -X POST 'https://tpmjs.com/api/tools/report-health' \
### 1. Executor Bugs Masking Tool Errors
**Problem:** Our executor had variables like `startTime`, `packageName`, and `exportName` declared inside try blocks but referenced in catch blocks. When errors occurred early (like during JSON parsing), the catch block crashed first, showing errors like "startTime is not defined" or "packageName is not defined" instead of the actual tool error.
**Problem:** Our executor had variables like `startTime`, `packageName`, and `name` declared inside try blocks but referenced in catch blocks. When errors occurred early (like during JSON parsing), the catch block crashed first, showing errors like "startTime is not defined" or "packageName is not defined" instead of the actual tool error.
**Lesson:** Always ensure executor error handling is bulletproof. Any variable used in a catch block MUST be declared before the try block with sensible defaults:
@ -206,16 +206,16 @@ async function executeTool(req: Request): Promise<Response> {
const startTime = Date.now();
// Declare with defaults BEFORE try
let packageName = 'unknown';
let exportName = 'unknown';
let name = 'unknown';
try {
const body = await req.json();
const { packageName: pkg, exportName: exp, ... } = body;
const { packageName: pkg, name: exp, ... } = body;
packageName = pkg || 'unknown';
exportName = exp || 'unknown';
name = exp || 'unknown';
// ... rest of execution
} catch (error) {
// Now these are always in scope
reportToolHealth(packageName, exportName, false, error.message);
reportToolHealth(packageName, name, false, error.message);
return Response.json({
success: false,
error: error.message,

View file

@ -55,7 +55,7 @@ A tutorial on implementing a hierarchical planning agent that dynamically loads
interface Tool {
id: string;
packageName: string;
exportName: string;
name: string;
description: string;
parameters: Parameter[];
returns: ReturnType;
@ -447,7 +447,7 @@ function buildStepContext(
const parts: string[] = [];
// 1. Current tool description
parts.push(`## Current Tool: ${current.tool.exportName}`);
parts.push(`## Current Tool: ${current.tool.name}`);
parts.push(current.tool.description);
parts.push(formatParameters(current.tool.parameters));
@ -463,7 +463,7 @@ function buildStepContext(
// 3. Upcoming tools (just names, for continuity)
if (upcoming.length > 0) {
parts.push(`## Coming Next`);
parts.push(upcoming.map(s => `- ${s.tool.exportName}: ${s.purpose}`).join('\n'));
parts.push(upcoming.map(s => `- ${s.tool.name}: ${s.purpose}`).join('\n'));
}
// 4. Relevant prior results (summarized)
@ -500,7 +500,7 @@ async function executeWithFallbacks(
try {
return await executeTool(tool, input, context);
} catch (error) {
console.log(`Tool ${tool.exportName} failed, trying fallback...`);
console.log(`Tool ${tool.name} failed, trying fallback...`);
}
}
@ -516,7 +516,7 @@ async function executeTool(
context: string
): Promise<any> {
const response = await fetch(
`/api/tools/execute/${tool.packageName}/${tool.exportName}`,
`/api/tools/execute/${tool.packageName}/${tool.name}`,
{
method: 'POST',
body: JSON.stringify({ input, context }),
@ -973,7 +973,7 @@ export async function POST(req: Request) {
id: p.id,
steps: p.steps.map(s => ({
id: s.id,
tool: s.tool.exportName,
tool: s.tool.name,
purpose: s.purpose
})),
estimatedCost: p.estimatedCost,

View file

@ -111,7 +111,7 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
],
"tools": [
{
"exportName": "scrapeTool",
"name": "scrapeTool",
"description": "Scrape content from any webpage and return structured data",
"parameters": [
{
@ -149,15 +149,15 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
**1. Multi-tool packages**
One npm package can export multiple tools. Each has its own `exportName`:
One npm package can export multiple tools. Each has its own `name`:
```json
{
"tpmjs": {
"tools": [
{ "exportName": "scrapeTool", "description": "..." },
{ "exportName": "screenshotTool", "description": "..." },
{ "exportName": "pdfExtractTool", "description": "..." }
{ "name": "scrapeTool", "description": "..." },
{ "name": "screenshotTool", "description": "..." },
{ "name": "pdfExtractTool", "description": "..." }
]
}
}
@ -327,12 +327,12 @@ const conversationEnv = new Map<string, Record<string, string>>();
export async function loadToolDynamically(
packageName: string,
exportName: string,
name: string,
version: string,
conversationId: string,
env?: Record<string, string>
): Promise<Tool | null> {
const cacheKey = `${packageName}::${exportName}`;
const cacheKey = `${packageName}::${name}`;
// Return cached tool if available
if (moduleCache.has(cacheKey)) {
@ -354,7 +354,7 @@ export async function loadToolDynamically(
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
name,
version,
importUrl: `https://esm.sh/${packageName}@${version}`,
env: env || {},
@ -378,7 +378,7 @@ export async function loadToolDynamically(
method: 'POST',
body: JSON.stringify({
packageName,
exportName,
name,
version,
params,
env: currentEnv, // Fresh on every execution
@ -395,7 +395,7 @@ export async function loadToolDynamically(
return toolWrapper;
} catch (error) {
console.error(`Failed to load ${packageName}/${exportName}:`, error);
console.error(`Failed to load ${packageName}/${name}:`, error);
return null;
}
}
@ -469,7 +469,7 @@ export async function POST(request: Request) {
const loadPromises = searchResults.tools.map(meta =>
loadToolDynamically(
meta.packageName,
meta.exportName,
meta.name,
meta.version,
conversationId,
env
@ -481,7 +481,7 @@ export async function POST(request: Request) {
// 7. Add to toolset with sanitized names
searchResults.tools.forEach((meta, i) => {
if (loadedTools[i]) {
const key = sanitizeToolName(`${meta.packageName}-${meta.exportName}`);
const key = sanitizeToolName(`${meta.packageName}-${meta.name}`);
discoveredTools[key] = loadedTools[i];
}
});
@ -583,7 +583,7 @@ Run a separate service (Railway, Fly.io, AWS Lambda) that:
```typescript
// Sandbox service (runs on Railway/Fly.io)
app.post('/execute-tool', async (req, res) => {
const { packageName, exportName, version, params, env } = req.body;
const { packageName, name, version, params, env } = req.body;
// Set env vars for this execution only
const originalEnv = { ...process.env };
@ -594,9 +594,9 @@ app.post('/execute-tool', async (req, res) => {
const importUrl = `https://esm.sh/${packageName}@${version}`;
const module = await import(importUrl);
const tool = module[exportName] || module.default;
const tool = module[name] || module.default;
if (!tool?.execute) {
throw new Error(`No executable tool found at ${exportName}`);
throw new Error(`No executable tool found at ${name}`);
}
// Execute with timeout

View file

@ -22,11 +22,11 @@
],
"tools": [
{
"exportName": "textToEmoji",
"name": "textToEmoji",
"description": "Convert text into emoji representations - perfect for making messages more expressive!"
},
{
"exportName": "emojiMood",
"name": "emojiMood",
"description": "Detect the mood/sentiment and suggest appropriate emojis for the text"
}
]

View file

@ -23,7 +23,7 @@
],
"tools": [
{
"exportName": "helloWorldTool",
"name": "helloWorldTool",
"description": "Returns a simple 'Hello, World!' greeting with optional timestamp and customizable message",
"parameters": [
{
@ -39,7 +39,7 @@
}
},
{
"exportName": "helloNameTool",
"name": "helloNameTool",
"description": "Returns a personalized greeting with the provided name",
"parameters": [
{

View file

@ -22,11 +22,11 @@
],
"tools": [
{
"exportName": "markdownToPlainText",
"name": "markdownToPlainText",
"description": "Convert markdown to plain text by removing all formatting"
},
{
"exportName": "formatMarkdownTable",
"name": "formatMarkdownTable",
"description": "Format and align markdown table columns for better readability"
}
]

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "audiencePersonaTool",
"name": "audiencePersonaTool",
"description": "Creates detailed audience persona profiles from demographic and behavioral data. Generates personas with demographics, psychographics, goals, pain points, behaviors, and actionable marketing implications.",
"parameters": [
{

View file

@ -330,6 +330,37 @@ domain:
fields: [effect, standardError, assumptions, parallel]
description: "Difference-in-differences result"
# -------------------------------------------------------------------------
# Sandbox & execution entities
# -------------------------------------------------------------------------
sprite:
fields: [name, status, createdAt, runtime, filesystem]
description: "Isolated Linux sandbox environment"
sprite_list:
fields: [sprites, count]
description: "Collection of sprites with metadata"
exec_result:
fields: [exitCode, stdout, stderr, duration]
description: "Command execution result from a sprite"
exec_session:
fields: [id, status, startedAt, command]
description: "Active execution session in a sprite"
checkpoint:
fields: [id, name, createdAt, size]
description: "Point-in-time snapshot of sprite state"
checkpoint_list:
fields: [checkpoints, count]
description: "Collection of checkpoints for a sprite"
network_policy:
fields: [mode, allowedDomains, rules]
description: "DNS-based network filtering configuration"
# -------------------------------------------------------------------------
# Agent & workflow entities
# -------------------------------------------------------------------------
@ -521,10 +552,9 @@ domain:
severity: warning
# =============================================================================
# DOMAIN RULES - Enforce code quality across all blocks
# DOMAIN RULES - Enforce code quality across all blocks (documentation only)
# =============================================================================
blocks:
domain_rules:
domain_rules:
# -------------------------------------------------------------------------
# Core implementation rules (apply to ALL tools)
# -------------------------------------------------------------------------
@ -794,10 +824,10 @@ blocks:
- Handle missing/extra items
- Report confidence in scores
# ===========================================================================
# BLOCK DEFINITIONS - All 100 tools organized by category
# ===========================================================================
# ===========================================================================
# BLOCK DEFINITIONS - All 100 tools organized by category
# ===========================================================================
blocks:
# ---------------------------------------------------------------------------
# A) Web Research & Evidence (15 tools)
# ---------------------------------------------------------------------------
@ -4494,6 +4524,260 @@ blocks:
description: "Standards to activities mapping"
measures: [working_implementation, valid_output_structure, readme_documentation]
# ---------------------------------------------------------------------------
# K) Sandbox & Code Execution - Sprites API (11 tools)
# ---------------------------------------------------------------------------
sprites.createSprite:
description: "Creates a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API"
path: "sprites-create"
domain_rules:
- id: api_integration
description: |
Must call Sprites API POST /sprites endpoint:
- Use fetch with Authorization Bearer header
- Send name in request body
- Handle API errors with meaningful messages
- id: auth_handling
description: "Must use SPRITES_TOKEN environment variable for authentication"
inputs:
- name: name
type: string
description: "Unique name for the sprite (must be lowercase alphanumeric with hyphens)"
outputs:
- name: sprite
type: Sprite
description: "Created sprite with name, status, and metadata"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.listSprites:
description: "Lists all sprites in the account with their current status and metadata"
path: "sprites-list"
domain_rules:
- id: api_integration
description: "Must call Sprites API GET /sprites endpoint"
- id: auth_handling
description: "Must use SPRITES_TOKEN environment variable for authentication"
inputs: []
outputs:
- name: sprites
type: Sprite[]
description: "Array of sprites with name, status, and metadata"
- name: count
type: number
description: "Total number of sprites"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.getSprite:
description: "Retrieves details of a specific sprite by name including status and configuration"
path: "sprites-get"
domain_rules:
- id: api_integration
description: "Must call Sprites API GET /sprites/{name} endpoint"
- id: error_handling
description: "Must handle 404 for non-existent sprites gracefully"
inputs:
- name: name
type: string
description: "Name of the sprite to retrieve"
outputs:
- name: sprite
type: Sprite
description: "Sprite details including status and metadata"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.deleteSprite:
description: "Deletes a sprite and all its associated data including checkpoints"
path: "sprites-delete"
domain_rules:
- id: api_integration
description: "Must call Sprites API DELETE /sprites/{name} endpoint"
- id: confirmation
description: "Returns success status after deletion"
inputs:
- name: name
type: string
description: "Name of the sprite to delete"
outputs:
- name: deleted
type: boolean
description: "Whether the sprite was successfully deleted"
- name: name
type: string
description: "Name of the deleted sprite"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.execCommand:
description: "Executes a command inside a sprite and returns the output. Supports stdin input for interactive commands."
path: "sprites-exec"
domain_rules:
- id: api_integration
description: |
Must call Sprites API POST /sprites/{name}/exec endpoint:
- Send cmd and optional stdin in request body
- Handle command execution timeout
- Return stdout, stderr, and exit code
- id: timeout_handling
description: "Must implement configurable timeout (default 60s)"
inputs:
- name: name
type: string
description: "Name of the sprite to execute command in"
- name: cmd
type: string
description: "Command to execute (e.g., 'ls -la', 'python script.py')"
- name: stdin
type: string
optional: true
description: "Optional stdin input to pass to the command"
- name: timeoutMs
type: number
optional: true
description: "Execution timeout in milliseconds (default: 60000)"
outputs:
- name: exitCode
type: number
description: "Command exit code (0 for success)"
- name: stdout
type: string
description: "Standard output from the command"
- name: stderr
type: string
description: "Standard error output from the command"
- name: duration
type: number
description: "Execution duration in milliseconds"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.listSessions:
description: "Lists active execution sessions for a sprite"
path: "sprites-sessions"
domain_rules:
- id: api_integration
description: "Must call Sprites API GET /sprites/{name}/exec/sessions endpoint"
inputs:
- name: name
type: string
description: "Name of the sprite to list sessions for"
outputs:
- name: sessions
type: ExecSession[]
description: "Array of active execution sessions"
- name: count
type: number
description: "Total number of active sessions"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.createCheckpoint:
description: "Creates a point-in-time snapshot (checkpoint) of a sprite's filesystem state for later restoration"
path: "sprites-checkpoint-create"
domain_rules:
- id: api_integration
description: "Must call Sprites API POST /sprites/{name}/checkpoints endpoint"
- id: checkpoint_metadata
description: "Must return checkpoint ID and creation timestamp"
inputs:
- name: name
type: string
description: "Name of the sprite to checkpoint"
- name: checkpointName
type: string
optional: true
description: "Optional human-readable name for the checkpoint"
outputs:
- name: checkpoint
type: Checkpoint
description: "Created checkpoint with ID and metadata"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.listCheckpoints:
description: "Lists all checkpoints for a sprite ordered by creation time"
path: "sprites-checkpoint-list"
domain_rules:
- id: api_integration
description: "Must call Sprites API GET /sprites/{name}/checkpoints endpoint"
inputs:
- name: name
type: string
description: "Name of the sprite to list checkpoints for"
outputs:
- name: checkpoints
type: Checkpoint[]
description: "Array of checkpoints with IDs and metadata"
- name: count
type: number
description: "Total number of checkpoints"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.restoreCheckpoint:
description: "Restores a sprite to a previous checkpoint state, reverting all filesystem changes since that checkpoint"
path: "sprites-checkpoint-restore"
domain_rules:
- id: api_integration
description: "Must call Sprites API POST /sprites/{name}/checkpoints/{id}/restore endpoint"
- id: state_verification
description: "Must verify restoration completed successfully"
inputs:
- name: name
type: string
description: "Name of the sprite to restore"
- name: checkpointId
type: string
description: "ID of the checkpoint to restore to"
outputs:
- name: restored
type: boolean
description: "Whether the restoration was successful"
- name: checkpointId
type: string
description: "ID of the restored checkpoint"
- name: sprite
type: Sprite
description: "Sprite status after restoration"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.getPolicy:
description: "Retrieves the current network policy for a sprite including allowed domains and filtering rules"
path: "sprites-policy-get"
domain_rules:
- id: api_integration
description: "Must call Sprites API GET /sprites/{name}/policies endpoint"
inputs:
- name: name
type: string
description: "Name of the sprite to get policy for"
outputs:
- name: policy
type: NetworkPolicy
description: "Current network policy with allowed domains and rules"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
sprites.setPolicy:
description: "Updates the network policy for a sprite to control outbound network access with DNS-based filtering"
path: "sprites-policy-set"
domain_rules:
- id: api_integration
description: "Must call Sprites API POST /sprites/{name}/policies endpoint"
- id: policy_validation
description: "Must validate policy format before sending"
inputs:
- name: name
type: string
description: "Name of the sprite to update policy for"
- name: mode
type: "'allow' | 'deny'"
description: "Policy mode - 'allow' blocks all except listed, 'deny' allows all except listed"
- name: domains
type: string[]
description: "List of domains to allow or deny based on mode"
outputs:
- name: policy
type: NetworkPolicy
description: "Updated network policy"
- name: applied
type: boolean
description: "Whether the policy was successfully applied"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance, readme_documentation]
# =============================================================================
# VALIDATORS - Which validators to run against each block
# =============================================================================

View file

@ -48,7 +48,7 @@
],
"tools": [
{
"exportName": "churnRiskScoreTool",
"name": "churnRiskScoreTool",
"description": "Scores customer churn risk based on usage, engagement, and support signals. Provides risk score (0-100) with detailed contributing factors and recommendations.",
"parameters": [
{

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "contentCalendarPlanTool",
"name": "contentCalendarPlanTool",
"description": "Generates a structured content calendar with posting schedule, themes, topics, and content types. Organizes content by date, channel, and theme while maintaining consistent posting frequency.",
"parameters": [
{

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "createBlogPostTool",
"name": "createBlogPostTool",
"description": "Creates structured blog posts with customizable frontmatter, content sections, and SEO metadata. Supports multiple output formats including Markdown and MDX.",
"parameters": [
{

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "emailSubjectScoreTool",
"name": "emailSubjectScoreTool",
"description": "Scores email subject lines for open rate potential based on length, clarity, urgency, curiosity, and personalization. Provides detailed feedback and improvement suggestions.",
"parameters": [
{

View file

@ -48,7 +48,7 @@
],
"tools": [
{
"exportName": "feedbackThemesTool",
"name": "feedbackThemesTool",
"description": "Extracts themes and sentiment from customer feedback text. Identifies recurring themes, scores sentiment per theme, and provides frequency counts.",
"parameters": [
{

View file

@ -48,7 +48,7 @@
],
"tools": [
{
"exportName": "npsAnalysisTool",
"name": "npsAnalysisTool",
"description": "Analyzes NPS survey responses to categorize by promoter/passive/detractor and extract themes from comments. Provides NPS score, distribution, and actionable insights.",
"parameters": [
{

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "pricingPageCopyTool",
"name": "pricingPageCopyTool",
"description": "Generates comprehensive pricing page copy with tier names, headlines, benefit-oriented feature lists, CTAs, FAQs, and trust signals. Frames features as benefits and clearly differentiates tiers.",
"parameters": [
{

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "reconciliationMatchTool",
"name": "reconciliationMatchTool",
"description": "Matches bank transactions to ledger entries for reconciliation. Uses amount, date proximity, and description similarity to identify matches with confidence scoring.",
"parameters": [
{

View file

@ -47,7 +47,7 @@
],
"tools": [
{
"exportName": "socialPostDraftTool",
"name": "socialPostDraftTool",
"description": "Drafts social media posts optimized for specific platforms (Twitter, LinkedIn, Instagram, Facebook) with appropriate hashtags, CTAs, and platform-specific best practices. Respects character limits and engagement patterns.",
"parameters": [
{

View file

@ -0,0 +1,77 @@
{
"name": "@tpmjs/tools-sprites-checkpoint-create",
"version": "0.1.1",
"description": "Create a point-in-time snapshot of a sprite's filesystem state for later restoration",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"checkpoint",
"snapshot",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-checkpoint-create"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesCheckpointCreateTool",
"description": "Create a point-in-time snapshot of a sprite's filesystem state for later restoration",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to checkpoint",
"required": true
},
{
"name": "checkpointName",
"type": "string",
"description": "Optional name for the checkpoint",
"required": false
}
],
"returns": {
"type": "Checkpoint",
"description": "Created checkpoint with ID and metadata"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,124 @@
/**
* Sprites Checkpoint Create Tool for TPMJS
* Creates a point-in-time snapshot of a sprite's filesystem state for later restoration.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface Checkpoint {
id: string;
name?: string;
createdAt: string;
size?: number;
}
type SpritesCheckpointCreateInput = {
name: string;
checkpointName?: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesCheckpointCreateTool = tool({
description:
"Create a point-in-time snapshot (checkpoint) of a sprite's filesystem state for later restoration. Useful for saving state before risky operations.",
inputSchema: jsonSchema<SpritesCheckpointCreateInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to checkpoint',
},
checkpointName: {
type: 'string',
description: 'Optional human-readable name for the checkpoint',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name, checkpointName }): Promise<Checkpoint> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 120000); // 2 min for checkpoints
const body: Record<string, unknown> = {};
if (checkpointName) {
body.name = checkpointName;
}
response = await fetch(
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify(body),
signal: controller.signal,
}
);
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to create checkpoint for sprite "${name}" timed out`);
}
throw new Error(`Failed to create checkpoint for sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to create checkpoint for sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to create checkpoint for sprite "${name}": HTTP ${response.status} - ${errorText}`
);
}
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
return {
id: (data.id as string) || '',
name: (data.name as string) || checkpointName,
createdAt:
(data.createdAt as string) || (data.created_at as string) || new Date().toISOString(),
size: data.size as number | undefined,
};
},
});
export default spritesCheckpointCreateTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,71 @@
{
"name": "@tpmjs/tools-sprites-checkpoint-list",
"version": "0.1.1",
"description": "List all checkpoints for a sprite ordered by creation time",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"checkpoint",
"snapshot",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-checkpoint-list"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesCheckpointListTool",
"description": "List all checkpoints for a sprite ordered by creation time",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to list checkpoints for",
"required": true
}
],
"returns": {
"type": "{ checkpoints: Checkpoint[], count: number }",
"description": "Array of checkpoints with count"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,127 @@
/**
* Sprites Checkpoint List Tool for TPMJS
* Lists all checkpoints for a sprite ordered by creation time.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface Checkpoint {
id: string;
name?: string;
createdAt: string;
size?: number;
}
export interface SpritesCheckpointListResult {
checkpoints: Checkpoint[];
count: number;
}
type SpritesCheckpointListInput = {
name: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesCheckpointListTool = tool({
description:
'List all checkpoints for a sprite ordered by creation time. Use this to find checkpoint IDs for restoration.',
inputSchema: jsonSchema<SpritesCheckpointListInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to list checkpoints for',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name }): Promise<SpritesCheckpointListResult> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
}
);
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to list checkpoints for sprite "${name}" timed out`);
}
throw new Error(`Failed to list checkpoints for sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to list checkpoints for sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to list checkpoints for sprite "${name}": HTTP ${response.status} - ${errorText}`
);
}
let data: unknown;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
const checkpointsArray = Array.isArray(data)
? data
: (data as Record<string, unknown>).checkpoints;
const checkpoints: Checkpoint[] = (Array.isArray(checkpointsArray) ? checkpointsArray : []).map(
(c: Record<string, unknown>) => ({
id: c.id as string,
name: c.name as string | undefined,
createdAt: (c.createdAt as string) || (c.created_at as string) || '',
size: c.size as number | undefined,
})
);
return {
checkpoints,
count: checkpoints.length,
};
},
});
export default spritesCheckpointListTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,77 @@
{
"name": "@tpmjs/tools-sprites-checkpoint-restore",
"version": "0.1.1",
"description": "Restore a sprite to a previous checkpoint state",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"checkpoint",
"restore",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-checkpoint-restore"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesCheckpointRestoreTool",
"description": "Restore a sprite to a previous checkpoint state, reverting all filesystem changes",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to restore",
"required": true
},
{
"name": "checkpointId",
"type": "string",
"description": "ID of the checkpoint to restore to",
"required": true
}
],
"returns": {
"type": "{ restored: boolean, checkpointId: string, sprite: Sprite }",
"description": "Restoration result with sprite status"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,135 @@
/**
* Sprites Checkpoint Restore Tool for TPMJS
* Restores a sprite to a previous checkpoint state, reverting all filesystem changes.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface Sprite {
name: string;
status: 'creating' | 'running' | 'stopped' | 'error';
createdAt: string;
runtime?: string;
metadata?: Record<string, unknown>;
}
export interface SpritesCheckpointRestoreResult {
restored: boolean;
checkpointId: string;
sprite: Sprite;
}
type SpritesCheckpointRestoreInput = {
name: string;
checkpointId: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesCheckpointRestoreTool = tool({
description:
'Restore a sprite to a previous checkpoint state, reverting all filesystem changes since that checkpoint. Use this to undo changes or recover from errors.',
inputSchema: jsonSchema<SpritesCheckpointRestoreInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to restore',
},
checkpointId: {
type: 'string',
description: 'ID of the checkpoint to restore to',
},
},
required: ['name', 'checkpointId'],
additionalProperties: false,
}),
async execute({ name, checkpointId }): Promise<SpritesCheckpointRestoreResult> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
if (!checkpointId || typeof checkpointId !== 'string') {
throw new Error('Checkpoint ID is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 120000); // 2 min for restore
response = await fetch(
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints/${encodeURIComponent(checkpointId)}/restore`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
}
);
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to restore checkpoint "${checkpointId}" timed out`);
}
throw new Error(`Failed to restore checkpoint "${checkpointId}": ${error.message}`);
}
throw new Error(`Failed to restore checkpoint "${checkpointId}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" or checkpoint "${checkpointId}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to restore checkpoint "${checkpointId}": HTTP ${response.status} - ${errorText}`
);
}
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
// If no JSON response, assume success
data = {};
}
const spriteData = (data.sprite as Record<string, unknown>) || data;
return {
restored: true,
checkpointId,
sprite: {
name: (spriteData.name as string) || name,
status: (spriteData.status as Sprite['status']) || 'running',
createdAt: (spriteData.createdAt as string) || '',
runtime: spriteData.runtime as string | undefined,
metadata: spriteData.metadata as Record<string, unknown> | undefined,
},
};
},
});
export default spritesCheckpointRestoreTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,70 @@
{
"name": "@tpmjs/tools-sprites-create",
"version": "0.1.1",
"description": "Create a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"code-execution",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-create"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesCreateTool",
"description": "Create a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Unique name for the sprite (must be lowercase alphanumeric with hyphens)",
"required": true
}
],
"returns": {
"type": "Sprite",
"description": "Created sprite with name, status, and metadata"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,141 @@
/**
* Sprites Create Tool for TPMJS
* Creates a new isolated Linux sandbox environment (sprite) with persistent filesystem
* using the Sprites API.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
/**
* Output interface for a sprite
*/
export interface Sprite {
name: string;
status: 'creating' | 'running' | 'stopped' | 'error';
createdAt: string;
runtime?: string;
metadata?: Record<string, unknown>;
}
type SpritesCreateInput = {
name: string;
};
/**
* Validates that a sprite name is valid
* Must be lowercase alphanumeric with hyphens, 3-63 characters
*/
function isValidSpriteName(name: string): boolean {
return /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(name);
}
/**
* Gets the Sprites API token from environment
*/
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
/**
* Sprites Create Tool
* Creates a new sprite sandbox environment
*/
export const spritesCreateTool = tool({
description:
'Create a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API. Sprites are lightweight VMs for running code securely.',
inputSchema: jsonSchema<SpritesCreateInput>({
type: 'object',
properties: {
name: {
type: 'string',
description:
'Unique name for the sprite (must be lowercase alphanumeric with hyphens, 3-63 characters)',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name }): Promise<Sprite> {
// Validate input
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
if (!isValidSpriteName(name)) {
throw new Error(
`Invalid sprite name: ${name}. Must be lowercase alphanumeric with hyphens, 3-63 characters.`
);
}
const token = getSpritesToken();
// Create the sprite via API
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60s timeout for creation
response = await fetch(`${SPRITES_API_BASE}/sprites`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify({ name }),
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to create sprite "${name}" timed out after 60 seconds`);
}
throw new Error(`Failed to create sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to create sprite "${name}": Unknown network error`);
}
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
if (response.status === 409) {
throw new Error(`Sprite "${name}" already exists. Choose a different name.`);
}
throw new Error(`Failed to create sprite "${name}": HTTP ${response.status} - ${errorText}`);
}
// Parse response
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error(`Failed to parse response from Sprites API`);
}
const sprite: Sprite = {
name: (data.name as string) || name,
status: (data.status as Sprite['status']) || 'creating',
createdAt: (data.createdAt as string) || new Date().toISOString(),
runtime: data.runtime as string | undefined,
metadata: data.metadata as Record<string, unknown> | undefined,
};
return sprite;
},
});
export default spritesCreateTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,70 @@
{
"name": "@tpmjs/tools-sprites-delete",
"version": "0.1.1",
"description": "Delete a sprite and all its associated data including checkpoints",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"code-execution",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-delete"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesDeleteTool",
"description": "Delete a sprite and all its associated data including checkpoints",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to delete",
"required": true
}
],
"returns": {
"type": "{ deleted: boolean, name: string }",
"description": "Deletion result with success status"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,95 @@
/**
* Sprites Delete Tool for TPMJS
* Deletes a sprite and all its associated data including checkpoints.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface SpritesDeleteResult {
deleted: boolean;
name: string;
}
type SpritesDeleteInput = {
name: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesDeleteTool = tool({
description:
'Delete a sprite and all its associated data including checkpoints. This action is irreversible.',
inputSchema: jsonSchema<SpritesDeleteInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to delete',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name }): Promise<SpritesDeleteResult> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to delete sprite "${name}" timed out after 30 seconds`);
}
throw new Error(`Failed to delete sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to delete sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`Failed to delete sprite "${name}": HTTP ${response.status} - ${errorText}`);
}
return {
deleted: true,
name,
};
},
});
export default spritesDeleteTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,88 @@
{
"name": "@tpmjs/tools-sprites-exec",
"version": "0.1.1",
"description": "Execute a command inside a sprite and return the output with exit code",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"code-execution",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-exec"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesExecTool",
"description": "Execute a command inside a sprite and return the output with exit code",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to execute command in",
"required": true
},
{
"name": "cmd",
"type": "string",
"description": "Command to execute",
"required": true
},
{
"name": "stdin",
"type": "string",
"description": "Optional stdin input",
"required": false
},
{
"name": "timeoutMs",
"type": "number",
"description": "Execution timeout in milliseconds",
"required": false
}
],
"returns": {
"type": "ExecResult",
"description": "Command output with exitCode, stdout, stderr, and duration"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,138 @@
/**
* Sprites Exec Tool for TPMJS
* Executes a command inside a sprite and returns the output.
* Supports stdin input for interactive commands.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface ExecResult {
exitCode: number;
stdout: string;
stderr: string;
duration: number;
}
type SpritesExecInput = {
name: string;
cmd: string;
stdin?: string;
timeoutMs?: number;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesExecTool = tool({
description:
'Execute a command inside a sprite and return the output. Supports stdin input for interactive commands. Returns exit code, stdout, stderr, and execution duration.',
inputSchema: jsonSchema<SpritesExecInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to execute command in',
},
cmd: {
type: 'string',
description: "Command to execute (e.g., 'ls -la', 'python script.py')",
},
stdin: {
type: 'string',
description: 'Optional stdin input to pass to the command',
},
timeoutMs: {
type: 'number',
description: 'Execution timeout in milliseconds (default: 60000)',
},
},
required: ['name', 'cmd'],
additionalProperties: false,
}),
async execute({ name, cmd, stdin, timeoutMs }): Promise<ExecResult> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
if (!cmd || typeof cmd !== 'string') {
throw new Error('Command is required and must be a string');
}
const token = getSpritesToken();
const timeout = timeoutMs || 60000;
const startTime = Date.now();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const body: Record<string, unknown> = { cmd };
if (stdin) {
body.stdin = stdin;
}
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify(body),
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Command execution in sprite "${name}" timed out after ${timeout}ms`);
}
throw new Error(`Failed to execute command in sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to execute command in sprite "${name}": Unknown network error`);
}
const duration = Date.now() - startTime;
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to execute command in sprite "${name}": HTTP ${response.status} - ${errorText}`
);
}
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
return {
exitCode: (data.exitCode as number) ?? (data.exit_code as number) ?? 0,
stdout: (data.stdout as string) || '',
stderr: (data.stderr as string) || '',
duration: (data.duration as number) || duration,
};
},
});
export default spritesExecTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,70 @@
{
"name": "@tpmjs/tools-sprites-get",
"version": "0.1.1",
"description": "Retrieve details of a specific sprite by name including status and configuration",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"code-execution",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-get"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesGetTool",
"description": "Retrieve details of a specific sprite by name including status and configuration",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to retrieve",
"required": true
}
],
"returns": {
"type": "Sprite",
"description": "Sprite details including status and metadata"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,108 @@
/**
* Sprites Get Tool for TPMJS
* Retrieves details of a specific sprite by name including status and configuration.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface Sprite {
name: string;
status: 'creating' | 'running' | 'stopped' | 'error';
createdAt: string;
runtime?: string;
metadata?: Record<string, unknown>;
}
type SpritesGetInput = {
name: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesGetTool = tool({
description:
'Retrieve details of a specific sprite by name including status and configuration. Use this to check if a sprite exists and get its current state.',
inputSchema: jsonSchema<SpritesGetInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to retrieve',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name }): Promise<Sprite> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to get sprite "${name}" timed out after 30 seconds`);
}
throw new Error(`Failed to get sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to get sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`Failed to get sprite "${name}": HTTP ${response.status} - ${errorText}`);
}
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
return {
name: (data.name as string) || name,
status: (data.status as Sprite['status']) || 'running',
createdAt: (data.createdAt as string) || '',
runtime: data.runtime as string | undefined,
metadata: data.metadata as Record<string, unknown> | undefined,
};
},
});
export default spritesGetTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,63 @@
{
"name": "@tpmjs/tools-sprites-list",
"version": "0.1.1",
"description": "List all sprites in the account with their current status and metadata",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"code-execution",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-list"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesListTool",
"description": "List all sprites in the account with their current status and metadata",
"parameters": [],
"returns": {
"type": "{ sprites: Sprite[], count: number }",
"description": "Array of sprites with count"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,118 @@
/**
* Sprites List Tool for TPMJS
* Lists all sprites in the account with their current status and metadata.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface Sprite {
name: string;
status: 'creating' | 'running' | 'stopped' | 'error';
createdAt: string;
runtime?: string;
metadata?: Record<string, unknown>;
}
export interface SpritesListResult {
sprites: Sprite[];
count: number;
}
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesListTool = tool({
description:
'List all sprites in the account with their current status and metadata. Returns an array of sprites with their names, statuses, and creation times.',
inputSchema: jsonSchema<Record<string, never>>({
type: 'object',
properties: {},
additionalProperties: false,
}),
async execute(): Promise<SpritesListResult> {
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error('Request to list sprites timed out after 30 seconds');
}
throw new Error(`Failed to list sprites: ${error.message}`);
}
throw new Error('Failed to list sprites: Unknown network error');
}
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
throw new Error(`Failed to list sprites: HTTP ${response.status} - ${errorText}`);
}
let data: unknown;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
// Handle both array response and object with sprites property
const spritesArray = Array.isArray(data) ? data : (data as Record<string, unknown>).sprites;
if (!Array.isArray(spritesArray)) {
throw new Error('Invalid response from Sprites API: expected an array of sprites');
}
const sprites: Sprite[] = spritesArray.map((s: unknown) => {
const sprite = s as Record<string, unknown>;
const status = sprite.status as string | undefined;
// Validate status is a known value
if (status && !['creating', 'running', 'stopped', 'error'].includes(status)) {
throw new Error(`Invalid sprite status: ${status}`);
}
return {
name: sprite.name as string,
status: (status as Sprite['status']) || 'running',
createdAt: (sprite.createdAt as string) || '',
runtime: sprite.runtime as string | undefined,
metadata: sprite.metadata as Record<string, unknown> | undefined,
};
});
return {
sprites,
count: sprites.length,
};
},
});
export default spritesListTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,71 @@
{
"name": "@tpmjs/tools-sprites-policy-get",
"version": "0.1.1",
"description": "Retrieve the current network policy for a sprite including allowed domains",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"network",
"policy",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-policy-get"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesPolicyGetTool",
"description": "Retrieve the current network policy for a sprite including allowed domains",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to get policy for",
"required": true
}
],
"returns": {
"type": "NetworkPolicy",
"description": "Current network policy with allowed domains and rules"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,106 @@
/**
* Sprites Policy Get Tool for TPMJS
* Retrieves the current network policy for a sprite including allowed domains.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface NetworkPolicy {
mode: 'allow' | 'deny';
domains: string[];
rules?: Record<string, unknown>[];
}
type SpritesPolicyGetInput = {
name: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesPolicyGetTool = tool({
description:
'Retrieve the current network policy for a sprite including allowed domains and filtering rules. Use this to understand what network access a sprite has.',
inputSchema: jsonSchema<SpritesPolicyGetInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to get policy for',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name }): Promise<NetworkPolicy> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policies`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to get policy for sprite "${name}" timed out`);
}
throw new Error(`Failed to get policy for sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to get policy for sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to get policy for sprite "${name}": HTTP ${response.status} - ${errorText}`
);
}
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
return {
mode: (data.mode as NetworkPolicy['mode']) || 'deny',
domains: (data.domains as string[]) || (data.allowedDomains as string[]) || [],
rules: data.rules as Record<string, unknown>[] | undefined,
};
},
});
export default spritesPolicyGetTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,83 @@
{
"name": "@tpmjs/tools-sprites-policy-set",
"version": "0.1.1",
"description": "Update the network policy for a sprite to control outbound network access",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"network",
"policy",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-policy-set"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesPolicySetTool",
"description": "Update the network policy for a sprite to control outbound network access",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to update policy for",
"required": true
},
{
"name": "mode",
"type": "'allow' | 'deny'",
"description": "Policy mode",
"required": true
},
{
"name": "domains",
"type": "string[]",
"description": "List of domains to allow or deny",
"required": true
}
],
"returns": {
"type": "{ policy: NetworkPolicy, applied: boolean }",
"description": "Updated policy and application status"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,136 @@
/**
* Sprites Policy Set Tool for TPMJS
* Updates the network policy for a sprite to control outbound network access.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface NetworkPolicy {
mode: 'allow' | 'deny';
domains: string[];
rules?: Record<string, unknown>[];
}
export interface SpritesPolicySetResult {
policy: NetworkPolicy;
applied: boolean;
}
type SpritesPolicySetInput = {
name: string;
mode: 'allow' | 'deny';
domains: string[];
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesPolicySetTool = tool({
description:
"Update the network policy for a sprite to control outbound network access with DNS-based filtering. Use 'allow' mode to block all traffic except listed domains, or 'deny' mode to allow all traffic except listed domains.",
inputSchema: jsonSchema<SpritesPolicySetInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to update policy for',
},
mode: {
type: 'string',
enum: ['allow', 'deny'],
description:
"Policy mode - 'allow' blocks all except listed domains, 'deny' allows all except listed domains",
},
domains: {
type: 'array',
items: { type: 'string' },
description: 'List of domains to allow or deny based on mode',
},
},
required: ['name', 'mode', 'domains'],
additionalProperties: false,
}),
async execute({ name, mode, domains }): Promise<SpritesPolicySetResult> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
if (mode !== 'allow' && mode !== 'deny') {
throw new Error("Mode must be 'allow' or 'deny'");
}
if (!Array.isArray(domains)) {
throw new Error('Domains must be an array of strings');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policies`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'TPMJS/1.0',
},
body: JSON.stringify({ mode, domains }),
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to set policy for sprite "${name}" timed out`);
}
throw new Error(`Failed to set policy for sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to set policy for sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to set policy for sprite "${name}": HTTP ${response.status} - ${errorText}`
);
}
let data: Record<string, unknown>;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
// If no JSON response, assume policy was applied with our input
data = { mode, domains };
}
return {
policy: {
mode: (data.mode as NetworkPolicy['mode']) || mode,
domains: (data.domains as string[]) || domains,
rules: data.rules as Record<string, unknown>[] | undefined,
},
applied: true,
};
},
});
export default spritesPolicySetTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -0,0 +1,70 @@
{
"name": "@tpmjs/tools-sprites-sessions",
"version": "0.1.1",
"description": "List active execution sessions for a sprite",
"type": "module",
"keywords": [
"tpmjs",
"sprites",
"sandbox",
"code-execution",
"ai"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/anthropics/tpmjs.git",
"directory": "packages/tools/official/sprites-sessions"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "sandbox",
"frameworks": [
"vercel-ai"
],
"tools": [
{
"name": "spritesSessionsTool",
"description": "List active execution sessions for a sprite",
"parameters": [
{
"name": "name",
"type": "string",
"description": "Name of the sprite to list sessions for",
"required": true
}
],
"returns": {
"type": "{ sessions: ExecSession[], count: number }",
"description": "Array of active execution sessions with count"
}
}
]
},
"dependencies": {
"ai": "6.0.23"
}
}

View file

@ -0,0 +1,125 @@
/**
* Sprites Sessions Tool for TPMJS
* Lists active execution sessions for a sprite.
*
* @requires SPRITES_TOKEN environment variable
*/
import { jsonSchema, tool } from 'ai';
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
export interface ExecSession {
id: string;
status: 'active' | 'completed' | 'terminated';
startedAt: string;
command?: string;
}
export interface SpritesSessionsResult {
sessions: ExecSession[];
count: number;
}
type SpritesSessionsInput = {
name: string;
};
function getSpritesToken(): string {
const token = process.env.SPRITES_TOKEN;
if (!token) {
throw new Error(
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
);
}
return token;
}
export const spritesSessionsTool = tool({
description:
'List active execution sessions for a sprite. Useful for monitoring running commands or attaching to existing sessions.',
inputSchema: jsonSchema<SpritesSessionsInput>({
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the sprite to list sessions for',
},
},
required: ['name'],
additionalProperties: false,
}),
async execute({ name }): Promise<SpritesSessionsResult> {
if (!name || typeof name !== 'string') {
throw new Error('Sprite name is required and must be a string');
}
const token = getSpritesToken();
let response: Response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec/sessions`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'TPMJS/1.0',
},
signal: controller.signal,
}
);
clearTimeout(timeoutId);
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request to list sessions for sprite "${name}" timed out`);
}
throw new Error(`Failed to list sessions for sprite "${name}": ${error.message}`);
}
throw new Error(`Failed to list sessions for sprite "${name}": Unknown network error`);
}
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Sprite "${name}" not found`);
}
if (response.status === 401) {
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
}
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(
`Failed to list sessions for sprite "${name}": HTTP ${response.status} - ${errorText}`
);
}
let data: unknown;
try {
data = (await response.json()) as Record<string, unknown>;
} catch {
throw new Error('Failed to parse response from Sprites API');
}
const sessionsArray = Array.isArray(data) ? data : (data as Record<string, unknown>).sessions;
const sessions: ExecSession[] = (Array.isArray(sessionsArray) ? sessionsArray : []).map(
(s: Record<string, unknown>) => ({
id: s.id as string,
status: (s.status as ExecSession['status']) || 'active',
startedAt: (s.startedAt as string) || (s.started_at as string) || '',
command: s.command as string | undefined,
})
);
return {
sessions,
count: sessions.length,
};
},
});
export default spritesSessionsTool;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});

View file

@ -48,7 +48,7 @@
],
"tools": [
{
"exportName": "ticketCategorizeTool",
"name": "ticketCategorizeTool",
"description": "Categorizes support tickets by type, priority, and product area. Suggests routing based on category and identifies urgent issues requiring immediate attention.",
"parameters": [
{

View file

@ -42,7 +42,7 @@ Execute a tool from the TPMJS registry by its toolId.
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `toolId` | string | Yes | Tool identifier (format: `package::exportName`) |
| `toolId` | string | Yes | Tool identifier (format: `package::name`) |
| `params` | object | Yes | Parameters to pass to the tool |
| `env` | object | No | Environment variables (API keys) if required |

View file

@ -44,13 +44,13 @@
],
"tools": [
{
"exportName": "registryExecuteTool",
"name": "registryExecuteTool",
"description": "Execute a tool from the TPMJS registry by toolId. Use registrySearchTool first to find toolIds.",
"parameters": [
{
"name": "toolId",
"type": "string",
"description": "Tool identifier from registrySearchTool (format: 'package::exportName')",
"description": "Tool identifier from registrySearchTool (format: 'package::name')",
"required": true
},
{

View file

@ -44,7 +44,7 @@
],
"tools": [
{
"exportName": "registrySearchTool",
"name": "registrySearchTool",
"description": "Search the TPMJS tool registry to find AI SDK tools. Returns tool metadata including toolId for use with registryExecuteTool.",
"parameters": [
{
@ -68,7 +68,7 @@
],
"returns": {
"type": "object",
"description": "Search results with tool metadata including toolId, packageName, exportName, description, requiredEnvVars"
"description": "Search results with tool metadata including toolId, packageName, name, description, requiredEnvVars"
},
"aiAgent": {
"useCase": "Use when you need to find a tool for a specific task. Search returns toolIds that can be executed with registryExecuteTool.",

View file

@ -30,7 +30,7 @@
],
"tools": [
{
"exportName": "searchTpmjsToolsTool",
"name": "searchTpmjsToolsTool",
"description": "Search the TPMJS tool registry to find AI SDK tools by keyword, category, or description. Returns tool metadata for dynamic loading.",
"parameters": [
{
@ -54,7 +54,7 @@
],
"returns": {
"type": "object",
"description": "Search results with tool metadata including packageName, exportName, version, importUrl"
"description": "Search results with tool metadata including packageName, name, version, importUrl"
},
"aiAgent": {
"useCase": "Use when you need a tool that isn't currently available. Search for tools by keyword or domain.",

View file

@ -51,7 +51,7 @@
],
"tools": [
{
"exportName": "executeCode",
"name": "executeCode",
"description": "Execute code synchronously in a secure sandbox. Supports 42+ languages including Python, JavaScript, TypeScript, Go, Rust, and more.",
"parameters": [
{
@ -99,7 +99,7 @@
}
},
{
"exportName": "executeCodeAsync",
"name": "executeCodeAsync",
"description": "Execute code asynchronously in a secure sandbox. Returns a job_id immediately for tracking.",
"parameters": [
{
@ -128,7 +128,7 @@
}
},
{
"exportName": "runCode",
"name": "runCode",
"description": "Execute code with automatic language detection from shebang line (e.g., #!/usr/bin/env python3).",
"parameters": [
{
@ -151,7 +151,7 @@
}
},
{
"exportName": "runCodeAsync",
"name": "runCodeAsync",
"description": "Execute code asynchronously with automatic language detection from shebang.",
"parameters": [
{
@ -173,7 +173,7 @@
}
},
{
"exportName": "listJobs",
"name": "listJobs",
"description": "List all active jobs for your API key. Jobs are retained for 3 minutes after completion.",
"parameters": [],
"returns": {
@ -188,7 +188,7 @@
}
},
{
"exportName": "getJob",
"name": "getJob",
"description": "Get status and results of a specific async job.",
"parameters": [
{
@ -210,7 +210,7 @@
}
},
{
"exportName": "cancelJob",
"name": "cancelJob",
"description": "Cancel a running or pending async job.",
"parameters": [
{
@ -232,7 +232,7 @@
}
},
{
"exportName": "listLanguages",
"name": "listLanguages",
"description": "List all 42+ supported programming languages and their aliases.",
"parameters": [],
"returns": {

View file

@ -1,6 +1,6 @@
{
"name": "@tpmjs/types",
"version": "0.2.0",
"version": "0.2.1",
"description": "Shared TypeScript types and Zod schemas for TPMJS",
"author": "TPMJS",
"license": "MIT",

View file

@ -14,6 +14,7 @@ export const TPMJS_CATEGORIES = [
'statistics',
'ops',
'agent',
'sandbox',
'utilities',
'html',
'compliance',
@ -101,44 +102,23 @@ export type TpmjsAiAgent = z.infer<typeof TpmjsAiAgentSchema>;
* Optional fields (auto-extracted if not provided):
* - description: A description of what the tool does (20-500 chars) - auto-extracted from tool
*
* @deprecated fields (now auto-extracted, kept for backward compatibility):
* @deprecated fields (now auto-extracted):
* - parameters: Tool input parameters - auto-extracted from inputSchema
* - returns: Tool return type - auto-extracted from tool
* - aiAgent: AI agent guidance - auto-extracted from tool
* - exportName: Renamed to 'name' - kept for backward compatibility with published packages
*/
export const TpmjsToolDefinitionSchema = z
.object({
// Required: The export name of the tool from the package
// Accepts both 'name' and legacy 'exportName' field
name: z.string().min(1).optional(),
// @deprecated - renamed to 'name', kept for backward compatibility
exportName: z.string().min(1).optional(),
// Optional - auto-extracted from tool if not provided
description: z
.string()
.min(20, 'Description must be at least 20 characters')
.max(500)
.optional(),
// @deprecated - now auto-extracted from tool's inputSchema
parameters: z.array(TpmjsParameterSchema).optional(),
// @deprecated - now auto-extracted from tool
returns: TpmjsReturnsSchema.optional(),
// @deprecated - now auto-extracted from tool
aiAgent: TpmjsAiAgentSchema.optional(),
})
.transform((data) => ({
// Transform exportName to name for backward compatibility
name: data.name || data.exportName || '',
description: data.description,
parameters: data.parameters,
returns: data.returns,
aiAgent: data.aiAgent,
}))
.refine((data) => data.name.length > 0, {
message: 'Either name or exportName is required',
path: ['name'],
});
export const TpmjsToolDefinitionSchema = z.object({
// Required: The export name of the tool from the package
name: z.string().min(1, 'Tool name is required'),
// Optional - auto-extracted from tool if not provided
description: z.string().min(20, 'Description must be at least 20 characters').max(500).optional(),
// @deprecated - now auto-extracted from tool's inputSchema
parameters: z.array(TpmjsParameterSchema).optional(),
// @deprecated - now auto-extracted from tool
returns: TpmjsReturnsSchema.optional(),
// @deprecated - now auto-extracted from tool
aiAgent: TpmjsAiAgentSchema.optional(),
});
export type TpmjsToolDefinition = z.infer<typeof TpmjsToolDefinitionSchema>;

176
pnpm-lock.yaml generated
View file

@ -3011,6 +3011,182 @@ importers:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-checkpoint-create:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-checkpoint-list:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-checkpoint-restore:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-create:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-delete:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-exec:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-get:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-list:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-policy-get:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-policy-set:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/sprites-sessions:
dependencies:
ai:
specifier: 6.0.23
version: 6.0.23(zod@4.3.5)
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
version: link:../../../config/tsconfig
tsup:
specifier: ^8.5.1
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.9.3
version: 5.9.3
packages/tools/official/stacktrace-parse:
dependencies:
ai:

View file

@ -0,0 +1,64 @@
#!/bin/bash
# Master validation script for top 3 priorities
# Exit 0 = all validations pass, Exit 1 = any validation fails
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "========================================"
echo " TPMJS Top 3 Priorities Validation"
echo "========================================"
echo ""
FAILED=0
# Priority 1: SDK Packages
echo ">>> Priority 1: SDK Packages"
echo ""
if "$SCRIPT_DIR/validate-sdk-packages.sh"; then
echo ""
echo ">>> Priority 1: PASSED"
else
echo ""
echo ">>> Priority 1: FAILED"
FAILED=$((FAILED + 1))
fi
echo ""
# Priority 2: Social Features
echo ">>> Priority 2: Social Proof & Discovery"
echo ""
if "$SCRIPT_DIR/validate-social-features.sh"; then
echo ""
echo ">>> Priority 2: PASSED"
else
echo ""
echo ">>> Priority 2: FAILED"
FAILED=$((FAILED + 1))
fi
echo ""
# Priority 3: Documentation
echo ">>> Priority 3: Documentation & Onboarding"
echo ""
if "$SCRIPT_DIR/validate-documentation.sh"; then
echo ""
echo ">>> Priority 3: PASSED"
else
echo ""
echo ">>> Priority 3: FAILED"
FAILED=$((FAILED + 1))
fi
echo ""
echo "========================================"
echo " Final Summary"
echo "========================================"
if [[ $FAILED -eq 0 ]]; then
echo "✓ All 3 priorities complete!"
exit 0
else
echo "$FAILED priority/priorities still need work"
exit 1
fi

View file

@ -0,0 +1,160 @@
#!/bin/bash
# Validation script for Priority 3: Documentation & Onboarding
# Exit 0 = validation passes, Exit 1 = validation fails
set -e
echo "=== Validating Documentation & Onboarding ==="
echo ""
ERRORS=0
# Check 1: API documentation pages
echo "1. Checking API documentation..."
API_DOCS_DIR="apps/web/src/app/docs/api"
if [[ -d "$API_DOCS_DIR" ]]; then
# Check for key API doc pages
REQUIRED_DOCS=("tools" "agents" "collections" "authentication")
for doc in "${REQUIRED_DOCS[@]}"; do
if [[ -f "$API_DOCS_DIR/$doc/page.tsx" ]] || [[ -f "$API_DOCS_DIR/$doc/page.mdx" ]]; then
echo " ✓ API docs: $doc exists"
else
echo " ✗ API docs: $doc missing"
ERRORS=$((ERRORS + 1))
fi
done
else
echo " ✗ API documentation directory missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 2: Interactive tutorial/quickstart
echo "2. Checking interactive tutorial..."
if [[ -f "apps/web/src/app/docs/quickstart/page.tsx" ]] || \
[[ -f "apps/web/src/app/tutorial/page.tsx" ]] || \
[[ -f "apps/web/src/app/getting-started/page.tsx" ]]; then
echo " ✓ Tutorial/quickstart page exists"
else
echo " ✗ Interactive tutorial missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 3: Example code snippets
echo "3. Checking example code..."
EXAMPLES_COUNT=$(grep -r '\`\`\`typescript\|\`\`\`javascript\|\`\`\`bash' apps/web/src/app/docs/ 2>/dev/null | wc -l || echo "0")
if [[ "$EXAMPLES_COUNT" -gt 20 ]]; then
echo " ✓ Found $EXAMPLES_COUNT code examples in docs"
else
echo " ✗ Insufficient code examples (found: $EXAMPLES_COUNT, need: 20+)"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 4: SDK documentation
echo "4. Checking SDK documentation..."
if [[ -f "apps/web/src/app/docs/sdk/page.tsx" ]] || \
[[ -d "apps/web/src/app/docs/sdk" ]]; then
echo " ✓ SDK documentation exists"
else
echo " ✗ SDK documentation missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 5: Tool development guide
echo "5. Checking tool development guide..."
if [[ -f "apps/web/src/app/docs/publishing/page.tsx" ]] || \
[[ -f "apps/web/src/app/publish/page.tsx" ]]; then
# Check for comprehensive content
FILE=$(find apps/web/src/app -name "page.tsx" -path "*publish*" -o -name "page.tsx" -path "*docs/publishing*" 2>/dev/null | head -1)
if [[ -n "$FILE" ]] && [[ $(wc -l < "$FILE") -gt 100 ]]; then
echo " ✓ Tool development guide exists and is comprehensive"
else
echo " ⚠ Tool development guide exists but may be short"
fi
else
echo " ✗ Tool development guide missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 6: API endpoint documentation completeness
echo "6. Checking API endpoint coverage..."
API_ROUTES=$(find apps/web/src/app/api -name "route.ts" 2>/dev/null | wc -l)
DOCUMENTED_ROUTES=$(grep -r "@api\|@route\|endpoint" apps/web/src/app/docs/api/ 2>/dev/null | wc -l || echo "0")
echo " API routes: $API_ROUTES"
echo " Documented references: $DOCUMENTED_ROUTES"
if [[ "$DOCUMENTED_ROUTES" -gt "$((API_ROUTES / 2))" ]]; then
echo " ✓ Good documentation coverage"
else
echo " ✗ Insufficient API documentation coverage"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 7: Onboarding flow components
echo "7. Checking onboarding components..."
if grep -rq "onboarding\|firstTime\|welcome\|tutorial" apps/web/src/components/ 2>/dev/null || \
[[ -d "apps/web/src/components/onboarding" ]]; then
echo " ✓ Onboarding components exist"
else
echo " ✗ Onboarding components missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 8: Example agents/collections
echo "8. Checking example templates..."
if grep -rq "example\|template\|starter" apps/web/src/app/docs/ 2>/dev/null || \
[[ -d "apps/web/src/app/templates" ]]; then
echo " ✓ Example templates exist"
else
echo " ✗ Example templates missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 9: Search/navigation in docs
echo "9. Checking docs navigation..."
if [[ -f "apps/web/src/components/docs/DocsSidebar.tsx" ]] || \
[[ -f "apps/web/src/app/docs/layout.tsx" ]]; then
echo " ✓ Docs navigation exists"
else
echo " ✗ Docs navigation missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 10: Troubleshooting guide
echo "10. Checking troubleshooting guide..."
if [[ -f "apps/web/src/app/docs/troubleshooting/page.tsx" ]] || \
grep -rq "troubleshoot\|common errors\|FAQ" apps/web/src/app/docs/ 2>/dev/null; then
echo " ✓ Troubleshooting content exists"
else
echo " ✗ Troubleshooting guide missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
echo "=== Validation Summary ==="
if [[ $ERRORS -eq 0 ]]; then
echo "✓ All checks passed!"
exit 0
else
echo "$ERRORS check(s) failed"
exit 1
fi

View file

@ -0,0 +1,169 @@
#!/bin/bash
# Validation script for Priority 1: Complete SDK Packages
# Exit 0 = validation passes, Exit 1 = validation fails
set -e
echo "=== Validating SDK Packages ==="
echo ""
ERRORS=0
# Check 1: @tpmjs/registry-search package exists
echo "1. Checking @tpmjs/registry-search package..."
if [[ -d "packages/registry-search" ]] && [[ -f "packages/registry-search/package.json" ]]; then
echo " ✓ Package directory exists"
# Check for src/index.ts
if [[ -f "packages/registry-search/src/index.ts" ]]; then
echo " ✓ Source file exists"
else
echo " ✗ Missing src/index.ts"
ERRORS=$((ERRORS + 1))
fi
# Check package.json has correct name
if grep -q '"name": "@tpmjs/registry-search"' packages/registry-search/package.json; then
echo " ✓ Package name correct"
else
echo " ✗ Package name incorrect"
ERRORS=$((ERRORS + 1))
fi
else
echo " ✗ Package directory missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 2: @tpmjs/registry-execute package exists
echo "2. Checking @tpmjs/registry-execute package..."
if [[ -d "packages/registry-execute" ]] && [[ -f "packages/registry-execute/package.json" ]]; then
echo " ✓ Package directory exists"
if [[ -f "packages/registry-execute/src/index.ts" ]]; then
echo " ✓ Source file exists"
else
echo " ✗ Missing src/index.ts"
ERRORS=$((ERRORS + 1))
fi
if grep -q '"name": "@tpmjs/registry-execute"' packages/registry-execute/package.json; then
echo " ✓ Package name correct"
else
echo " ✗ Package name incorrect"
ERRORS=$((ERRORS + 1))
fi
else
echo " ✗ Package directory missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 3: TypeScript compiles
echo "3. Checking TypeScript compilation..."
if command -v pnpm &> /dev/null; then
if pnpm --filter=@tpmjs/registry-search type-check 2>/dev/null; then
echo " ✓ registry-search compiles"
else
echo " ✗ registry-search has type errors"
ERRORS=$((ERRORS + 1))
fi
if pnpm --filter=@tpmjs/registry-execute type-check 2>/dev/null; then
echo " ✓ registry-execute compiles"
else
echo " ✗ registry-execute has type errors"
ERRORS=$((ERRORS + 1))
fi
else
echo " ⚠ pnpm not available, skipping type check"
fi
echo ""
# Check 4: Packages have required exports
echo "4. Checking required exports..."
REQUIRED_SEARCH_EXPORTS=("searchTools" "getToolById" "getTrendingTools")
REQUIRED_EXECUTE_EXPORTS=("executeToolCall" "createToolClient")
if [[ -f "packages/registry-search/src/index.ts" ]]; then
for export in "${REQUIRED_SEARCH_EXPORTS[@]}"; do
if grep -q "export.*$export" packages/registry-search/src/index.ts 2>/dev/null || \
grep -q "export.*$export" packages/registry-search/src/*.ts 2>/dev/null; then
echo " ✓ registry-search exports $export"
else
echo " ✗ registry-search missing export: $export"
ERRORS=$((ERRORS + 1))
fi
done
fi
if [[ -f "packages/registry-execute/src/index.ts" ]]; then
for export in "${REQUIRED_EXECUTE_EXPORTS[@]}"; do
if grep -q "export.*$export" packages/registry-execute/src/index.ts 2>/dev/null || \
grep -q "export.*$export" packages/registry-execute/src/*.ts 2>/dev/null; then
echo " ✓ registry-execute exports $export"
else
echo " ✗ registry-execute missing export: $export"
ERRORS=$((ERRORS + 1))
fi
done
fi
echo ""
# Check 5: Tests exist and pass
echo "5. Checking tests..."
if [[ -d "packages/registry-search/src/__tests__" ]] || [[ -d "packages/registry-search/test" ]]; then
echo " ✓ registry-search has tests"
else
echo " ✗ registry-search missing tests"
ERRORS=$((ERRORS + 1))
fi
if [[ -d "packages/registry-execute/src/__tests__" ]] || [[ -d "packages/registry-execute/test" ]]; then
echo " ✓ registry-execute has tests"
else
echo " ✗ registry-execute missing tests"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 6: README documentation
echo "6. Checking documentation..."
if [[ -f "packages/registry-search/README.md" ]]; then
if [[ $(wc -l < packages/registry-search/README.md) -gt 20 ]]; then
echo " ✓ registry-search has README"
else
echo " ✗ registry-search README too short"
ERRORS=$((ERRORS + 1))
fi
else
echo " ✗ registry-search missing README"
ERRORS=$((ERRORS + 1))
fi
if [[ -f "packages/registry-execute/README.md" ]]; then
if [[ $(wc -l < packages/registry-execute/README.md) -gt 20 ]]; then
echo " ✓ registry-execute has README"
else
echo " ✗ registry-execute README too short"
ERRORS=$((ERRORS + 1))
fi
else
echo " ✗ registry-execute missing README"
ERRORS=$((ERRORS + 1))
fi
echo ""
echo "=== Validation Summary ==="
if [[ $ERRORS -eq 0 ]]; then
echo "✓ All checks passed!"
exit 0
else
echo "$ERRORS check(s) failed"
exit 1
fi

View file

@ -0,0 +1,143 @@
#!/bin/bash
# Validation script for Priority 2: Social Proof & Discovery Features
# Exit 0 = validation passes, Exit 1 = validation fails
set -e
echo "=== Validating Social Proof & Discovery Features ==="
echo ""
ERRORS=0
# Check 1: Rating system API routes
echo "1. Checking rating API routes..."
if [[ -f "apps/web/src/app/api/tools/[id]/rate/route.ts" ]]; then
echo " ✓ Tool rating route exists"
# Check for POST handler
if grep -q "export async function POST" "apps/web/src/app/api/tools/[id]/rate/route.ts"; then
echo " ✓ POST handler for rating exists"
else
echo " ✗ Missing POST handler for rating"
ERRORS=$((ERRORS + 1))
fi
else
echo " ✗ Tool rating route missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 2: Review system API routes
echo "2. Checking review API routes..."
if [[ -f "apps/web/src/app/api/tools/[id]/reviews/route.ts" ]]; then
echo " ✓ Tool reviews route exists"
if grep -q "export async function GET" "apps/web/src/app/api/tools/[id]/reviews/route.ts"; then
echo " ✓ GET handler for reviews exists"
else
echo " ✗ Missing GET handler for reviews"
ERRORS=$((ERRORS + 1))
fi
if grep -q "export async function POST" "apps/web/src/app/api/tools/[id]/reviews/route.ts"; then
echo " ✓ POST handler for reviews exists"
else
echo " ✗ Missing POST handler for reviews"
ERRORS=$((ERRORS + 1))
fi
else
echo " ✗ Tool reviews route missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 3: Database schema for ratings/reviews
echo "3. Checking database schema..."
if grep -q "model ToolRating" "packages/db/prisma/schema.prisma"; then
echo " ✓ ToolRating model exists"
else
echo " ✗ ToolRating model missing"
ERRORS=$((ERRORS + 1))
fi
if grep -q "model ToolReview" "packages/db/prisma/schema.prisma"; then
echo " ✓ ToolReview model exists"
else
echo " ✗ ToolReview model missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 4: Trending tools API
echo "4. Checking trending tools..."
if [[ -f "apps/web/src/app/api/tools/trending/route.ts" ]]; then
echo " ✓ Trending tools route exists"
else
echo " ✗ Trending tools route missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 5: UI Components for ratings/reviews
echo "5. Checking UI components..."
if [[ -f "packages/ui/src/Rating/Rating.tsx" ]] || [[ -f "apps/web/src/components/Rating.tsx" ]]; then
echo " ✓ Rating component exists"
else
echo " ✗ Rating component missing"
ERRORS=$((ERRORS + 1))
fi
if [[ -f "packages/ui/src/ReviewCard/ReviewCard.tsx" ]] || [[ -f "apps/web/src/components/ReviewCard.tsx" ]]; then
echo " ✓ ReviewCard component exists"
else
echo " ✗ ReviewCard component missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
# Check 6: Tool page shows ratings
echo "6. Checking tool page integration..."
if [[ -f "apps/web/src/app/tool/[slug]/page.tsx" ]]; then
if grep -q "Rating\|rating\|averageRating" "apps/web/src/app/tool/[slug]/page.tsx"; then
echo " ✓ Tool page shows ratings"
else
echo " ✗ Tool page missing rating display"
ERRORS=$((ERRORS + 1))
fi
if grep -q "Review\|review" "apps/web/src/app/tool/[slug]/page.tsx"; then
echo " ✓ Tool page shows reviews"
else
echo " ✗ Tool page missing reviews display"
ERRORS=$((ERRORS + 1))
fi
else
echo " ⚠ Tool page not found at expected location"
fi
echo ""
# Check 7: Average rating calculation
echo "7. Checking rating aggregation..."
if grep -q "averageRating" "packages/db/prisma/schema.prisma" || \
grep -rq "AVG.*rating\|averageRating" "apps/web/src/app/api/"; then
echo " ✓ Rating aggregation exists"
else
echo " ✗ Rating aggregation missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
echo "=== Validation Summary ==="
if [[ $ERRORS -eq 0 ]]; then
echo "✓ All checks passed!"
exit 0
else
echo "$ERRORS check(s) failed"
exit 1
fi