diff --git a/.claude/plugins/ralph-wiggum/commands/cancel-ralph.md b/.claude/plugins/ralph-wiggum/commands/cancel-ralph.md
new file mode 100644
index 0000000..471a78b
--- /dev/null
+++ b/.claude/plugins/ralph-wiggum/commands/cancel-ralph.md
@@ -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.
diff --git a/.claude/plugins/ralph-wiggum/commands/ralph-loop.md b/.claude/plugins/ralph-wiggum/commands/ralph-loop.md
new file mode 100644
index 0000000..ce3e94b
--- /dev/null
+++ b/.claude/plugins/ralph-wiggum/commands/ralph-loop.md
@@ -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"
+```
diff --git a/.claude/plugins/ralph-wiggum/hooks/hooks.json b/.claude/plugins/ralph-wiggum/hooks/hooks.json
new file mode 100644
index 0000000..2e5f697
--- /dev/null
+++ b/.claude/plugins/ralph-wiggum/hooks/hooks.json
@@ -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"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.claude/plugins/ralph-wiggum/hooks/stop-hook.sh b/.claude/plugins/ralph-wiggum/hooks/stop-hook.sh
new file mode 100755
index 0000000..bfbfece
--- /dev/null
+++ b/.claude/plugins/ralph-wiggum/hooks/stop-hook.sh
@@ -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 '[^<]*' | tail -1 | sed 's/\(.*\)<\/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 $completion_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
diff --git a/.claude/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh b/.claude/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh
new file mode 100755
index 0000000..8be7780
--- /dev/null
+++ b/.claude/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh
@@ -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 TXT)
+ --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: $COMPLETION_PROMISE"
+fi
+echo " - OR max iterations ($MAX_ITERATIONS) is reached"
diff --git a/LAUNCH_REVIEW.md b/LAUNCH_REVIEW.md
index 38d77b8..581a3f6 100644
--- a/LAUNCH_REVIEW.md
+++ b/LAUNCH_REVIEW.md
@@ -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
---
diff --git a/MANUAL_TOOLS.md b/MANUAL_TOOLS.md
index fe27e30..75b0853 100644
--- a/MANUAL_TOOLS.md
+++ b/MANUAL_TOOLS.md
@@ -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...',
// ...
},
diff --git a/TOP_5_PRIORITIES.md b/TOP_5_PRIORITIES.md
new file mode 100644
index 0000000..352f377
--- /dev/null
+++ b/TOP_5_PRIORITIES.md
@@ -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.
diff --git a/apps/railway-executor/README.md b/apps/railway-executor/README.md
index a34adf3..7cbc31a 100644
--- a/apps/railway-executor/README.md
+++ b/apps/railway-executor/README.md
@@ -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": {}
}'
diff --git a/apps/railway-executor/server.js b/apps/railway-executor/server.js
index 882fc3b..b24050b 100644
--- a/apps/railway-executor/server.js
+++ b/apps/railway-executor/server.js
@@ -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({
diff --git a/apps/railway-executor/server.ts b/apps/railway-executor/server.ts
index 66b536f..486b9e7 100644
--- a/apps/railway-executor/server.ts
+++ b/apps/railway-executor/server.ts
@@ -550,9 +550,9 @@ async function executeTool(req: Request): Promise {
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 {
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 {
// 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)',
});
diff --git a/apps/web/src/app/tool/[...slug]/page.tsx b/apps/web/src/app/tool/[...slug]/page.tsx
index cee4e3d..3cc1eeb 100644
--- a/apps/web/src/app/tool/[...slug]/page.tsx
+++ b/apps/web/src/app/tool/[...slug]/page.tsx
@@ -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 {
- const { packageName, exportName } = parseSlug(slug);
+ const { packageName, toolName } = parseSlug(slug);
if (!packageName) {
return null;
@@ -49,7 +49,7 @@ async function getTool(slug: string[]): Promise {
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 {
diff --git a/apps/web/src/lib/og/content-extractor.ts b/apps/web/src/lib/og/content-extractor.ts
index 8e443c9..8e302b2 100644
--- a/apps/web/src/lib/og/content-extractor.ts
+++ b/apps/web/src/lib/og/content-extractor.ts
@@ -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 {
- 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 {
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 {
// 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: '',
diff --git a/docs/AI_SDK_TOOL_EXECUTION.md b/docs/AI_SDK_TOOL_EXECUTION.md
index 030729f..a1bfde3 100644
--- a/docs/AI_SDK_TOOL_EXECUTION.md
+++ b/docs/AI_SDK_TOOL_EXECUTION.md
@@ -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; // Tool parameters
env?: Record; // Environment variables
};
diff --git a/docs/DYNAMIC_TOOL_ORCHESTRATION.md b/docs/DYNAMIC_TOOL_ORCHESTRATION.md
index 4f0129c..30ecb65 100644
--- a/docs/DYNAMIC_TOOL_ORCHESTRATION.md
+++ b/docs/DYNAMIC_TOOL_ORCHESTRATION.md
@@ -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...`);
}
}
diff --git a/docs/PLANNER_TYPE_DEFINITIONS.ts b/docs/PLANNER_TYPE_DEFINITIONS.ts
index cc7d824..1228e14 100644
--- a/docs/PLANNER_TYPE_DEFINITIONS.ts
+++ b/docs/PLANNER_TYPE_DEFINITIONS.ts
@@ -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;
}
diff --git a/docs/REGISTRY_SDK_DESIGN.md b/docs/REGISTRY_SDK_DESIGN.md
index cb140ff..8b98c1d 100644
--- a/docs/REGISTRY_SDK_DESIGN.md
+++ b/docs/REGISTRY_SDK_DESIGN.md
@@ -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" },
diff --git a/docs/TOOL_HEALTH_SYSTEM.md b/docs/TOOL_HEALTH_SYSTEM.md
index a6ea9b8..33da579 100644
--- a/docs/TOOL_HEALTH_SYSTEM.md
+++ b/docs/TOOL_HEALTH_SYSTEM.md
@@ -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 {
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,
diff --git a/docs/TPMJS_PLANNER_TUTORIAL.md b/docs/TPMJS_PLANNER_TUTORIAL.md
index e93cebe..0265676 100644
--- a/docs/TPMJS_PLANNER_TUTORIAL.md
+++ b/docs/TPMJS_PLANNER_TUTORIAL.md
@@ -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 {
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,
diff --git a/docs/building-dynamic-tool-systems.md b/docs/building-dynamic-tool-systems.md
index 6287897..b532d0e 100644
--- a/docs/building-dynamic-tool-systems.md
+++ b/docs/building-dynamic-tool-systems.md
@@ -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>();
export async function loadToolDynamically(
packageName: string,
- exportName: string,
+ name: string,
version: string,
conversationId: string,
env?: Record
): Promise {
- 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
diff --git a/packages/tools/emoji-magic/package.json b/packages/tools/emoji-magic/package.json
index 922c69c..d157cfc 100644
--- a/packages/tools/emoji-magic/package.json
+++ b/packages/tools/emoji-magic/package.json
@@ -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"
}
]
diff --git a/packages/tools/hello/package.json b/packages/tools/hello/package.json
index 075f08d..503be05 100644
--- a/packages/tools/hello/package.json
+++ b/packages/tools/hello/package.json
@@ -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": [
{
diff --git a/packages/tools/markdown-formatter/package.json b/packages/tools/markdown-formatter/package.json
index a392a15..e65e7d7 100644
--- a/packages/tools/markdown-formatter/package.json
+++ b/packages/tools/markdown-formatter/package.json
@@ -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"
}
]
diff --git a/packages/tools/official/audience-persona/package.json b/packages/tools/official/audience-persona/package.json
index 61acb5c..9ef8099 100644
--- a/packages/tools/official/audience-persona/package.json
+++ b/packages/tools/official/audience-persona/package.json
@@ -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": [
{
diff --git a/packages/tools/official/blocks.yml b/packages/tools/official/blocks.yml
index 36a3055..ae086b6 100644
--- a/packages/tools/official/blocks.yml
+++ b/packages/tools/official/blocks.yml
@@ -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
# =============================================================================
diff --git a/packages/tools/official/churn-risk-score/package.json b/packages/tools/official/churn-risk-score/package.json
index d74b61b..d7232b6 100644
--- a/packages/tools/official/churn-risk-score/package.json
+++ b/packages/tools/official/churn-risk-score/package.json
@@ -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": [
{
diff --git a/packages/tools/official/content-calendar-plan/package.json b/packages/tools/official/content-calendar-plan/package.json
index 9775e75..f060976 100644
--- a/packages/tools/official/content-calendar-plan/package.json
+++ b/packages/tools/official/content-calendar-plan/package.json
@@ -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": [
{
diff --git a/packages/tools/official/createBlogPost/package.json b/packages/tools/official/createBlogPost/package.json
index c066330..bd9b541 100644
--- a/packages/tools/official/createBlogPost/package.json
+++ b/packages/tools/official/createBlogPost/package.json
@@ -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": [
{
diff --git a/packages/tools/official/email-subject-score/package.json b/packages/tools/official/email-subject-score/package.json
index b2d2eba..b73c73d 100644
--- a/packages/tools/official/email-subject-score/package.json
+++ b/packages/tools/official/email-subject-score/package.json
@@ -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": [
{
diff --git a/packages/tools/official/feedback-themes/package.json b/packages/tools/official/feedback-themes/package.json
index 2f79e0d..f8e6ada 100644
--- a/packages/tools/official/feedback-themes/package.json
+++ b/packages/tools/official/feedback-themes/package.json
@@ -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": [
{
diff --git a/packages/tools/official/nps-analysis/package.json b/packages/tools/official/nps-analysis/package.json
index a3d2de1..f8c0d8a 100644
--- a/packages/tools/official/nps-analysis/package.json
+++ b/packages/tools/official/nps-analysis/package.json
@@ -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": [
{
diff --git a/packages/tools/official/pricing-page-copy/package.json b/packages/tools/official/pricing-page-copy/package.json
index dffb98f..6bae8ce 100644
--- a/packages/tools/official/pricing-page-copy/package.json
+++ b/packages/tools/official/pricing-page-copy/package.json
@@ -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": [
{
diff --git a/packages/tools/official/reconciliation-match/package.json b/packages/tools/official/reconciliation-match/package.json
index 3fca231..52579b3 100644
--- a/packages/tools/official/reconciliation-match/package.json
+++ b/packages/tools/official/reconciliation-match/package.json
@@ -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": [
{
diff --git a/packages/tools/official/social-post-draft/package.json b/packages/tools/official/social-post-draft/package.json
index 30a5fdc..9a36212 100644
--- a/packages/tools/official/social-post-draft/package.json
+++ b/packages/tools/official/social-post-draft/package.json
@@ -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": [
{
diff --git a/packages/tools/official/sprites-checkpoint-create/package.json b/packages/tools/official/sprites-checkpoint-create/package.json
new file mode 100644
index 0000000..a541f72
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-create/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-checkpoint-create/src/index.ts b/packages/tools/official/sprites-checkpoint-create/src/index.ts
new file mode 100644
index 0000000..14f7537
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-create/src/index.ts
@@ -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({
+ 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 {
+ 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 = {};
+ 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;
+ try {
+ data = (await response.json()) as Record;
+ } 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;
diff --git a/packages/tools/official/sprites-checkpoint-create/tsconfig.json b/packages/tools/official/sprites-checkpoint-create/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-create/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-checkpoint-create/tsup.config.ts b/packages/tools/official/sprites-checkpoint-create/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-create/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-checkpoint-list/package.json b/packages/tools/official/sprites-checkpoint-list/package.json
new file mode 100644
index 0000000..5a91247
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-list/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-checkpoint-list/src/index.ts b/packages/tools/official/sprites-checkpoint-list/src/index.ts
new file mode 100644
index 0000000..248dbae
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-list/src/index.ts
@@ -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({
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ description: 'Name of the sprite to list checkpoints for',
+ },
+ },
+ required: ['name'],
+ additionalProperties: false,
+ }),
+ async execute({ name }): Promise {
+ 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;
+ } catch {
+ throw new Error('Failed to parse response from Sprites API');
+ }
+
+ const checkpointsArray = Array.isArray(data)
+ ? data
+ : (data as Record).checkpoints;
+
+ const checkpoints: Checkpoint[] = (Array.isArray(checkpointsArray) ? checkpointsArray : []).map(
+ (c: Record) => ({
+ 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;
diff --git a/packages/tools/official/sprites-checkpoint-list/tsconfig.json b/packages/tools/official/sprites-checkpoint-list/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-list/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-checkpoint-list/tsup.config.ts b/packages/tools/official/sprites-checkpoint-list/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-list/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-checkpoint-restore/package.json b/packages/tools/official/sprites-checkpoint-restore/package.json
new file mode 100644
index 0000000..9633559
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-restore/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-checkpoint-restore/src/index.ts b/packages/tools/official/sprites-checkpoint-restore/src/index.ts
new file mode 100644
index 0000000..ef063b6
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-restore/src/index.ts
@@ -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;
+}
+
+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({
+ 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 {
+ 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;
+ try {
+ data = (await response.json()) as Record;
+ } catch {
+ // If no JSON response, assume success
+ data = {};
+ }
+
+ const spriteData = (data.sprite as Record) || 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 | undefined,
+ },
+ };
+ },
+});
+
+export default spritesCheckpointRestoreTool;
diff --git a/packages/tools/official/sprites-checkpoint-restore/tsconfig.json b/packages/tools/official/sprites-checkpoint-restore/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-restore/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-checkpoint-restore/tsup.config.ts b/packages/tools/official/sprites-checkpoint-restore/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-checkpoint-restore/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-create/package.json b/packages/tools/official/sprites-create/package.json
new file mode 100644
index 0000000..a8bffdc
--- /dev/null
+++ b/packages/tools/official/sprites-create/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-create/src/index.ts b/packages/tools/official/sprites-create/src/index.ts
new file mode 100644
index 0000000..1e9c447
--- /dev/null
+++ b/packages/tools/official/sprites-create/src/index.ts
@@ -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;
+}
+
+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({
+ 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 {
+ // 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;
+ try {
+ data = (await response.json()) as Record;
+ } 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 | undefined,
+ };
+
+ return sprite;
+ },
+});
+
+export default spritesCreateTool;
diff --git a/packages/tools/official/sprites-create/tsconfig.json b/packages/tools/official/sprites-create/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-create/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-create/tsup.config.ts b/packages/tools/official/sprites-create/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-create/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-delete/package.json b/packages/tools/official/sprites-delete/package.json
new file mode 100644
index 0000000..d6db288
--- /dev/null
+++ b/packages/tools/official/sprites-delete/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-delete/src/index.ts b/packages/tools/official/sprites-delete/src/index.ts
new file mode 100644
index 0000000..75b6abe
--- /dev/null
+++ b/packages/tools/official/sprites-delete/src/index.ts
@@ -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({
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ description: 'Name of the sprite to delete',
+ },
+ },
+ required: ['name'],
+ additionalProperties: false,
+ }),
+ async execute({ name }): Promise {
+ 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;
diff --git a/packages/tools/official/sprites-delete/tsconfig.json b/packages/tools/official/sprites-delete/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-delete/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-delete/tsup.config.ts b/packages/tools/official/sprites-delete/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-delete/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-exec/package.json b/packages/tools/official/sprites-exec/package.json
new file mode 100644
index 0000000..7bc9f7b
--- /dev/null
+++ b/packages/tools/official/sprites-exec/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-exec/src/index.ts b/packages/tools/official/sprites-exec/src/index.ts
new file mode 100644
index 0000000..b12bf85
--- /dev/null
+++ b/packages/tools/official/sprites-exec/src/index.ts
@@ -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({
+ 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 {
+ 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 = { 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;
+ try {
+ data = (await response.json()) as Record;
+ } 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;
diff --git a/packages/tools/official/sprites-exec/tsconfig.json b/packages/tools/official/sprites-exec/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-exec/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-exec/tsup.config.ts b/packages/tools/official/sprites-exec/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-exec/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-get/package.json b/packages/tools/official/sprites-get/package.json
new file mode 100644
index 0000000..cab3cbe
--- /dev/null
+++ b/packages/tools/official/sprites-get/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-get/src/index.ts b/packages/tools/official/sprites-get/src/index.ts
new file mode 100644
index 0000000..f4e7420
--- /dev/null
+++ b/packages/tools/official/sprites-get/src/index.ts
@@ -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;
+}
+
+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({
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ description: 'Name of the sprite to retrieve',
+ },
+ },
+ required: ['name'],
+ additionalProperties: false,
+ }),
+ async execute({ name }): Promise {
+ 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;
+ try {
+ data = (await response.json()) as Record;
+ } 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 | undefined,
+ };
+ },
+});
+
+export default spritesGetTool;
diff --git a/packages/tools/official/sprites-get/tsconfig.json b/packages/tools/official/sprites-get/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-get/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-get/tsup.config.ts b/packages/tools/official/sprites-get/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-get/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-list/package.json b/packages/tools/official/sprites-list/package.json
new file mode 100644
index 0000000..0f7a3e1
--- /dev/null
+++ b/packages/tools/official/sprites-list/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-list/src/index.ts b/packages/tools/official/sprites-list/src/index.ts
new file mode 100644
index 0000000..327f8d3
--- /dev/null
+++ b/packages/tools/official/sprites-list/src/index.ts
@@ -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;
+}
+
+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>({
+ type: 'object',
+ properties: {},
+ additionalProperties: false,
+ }),
+ async execute(): Promise {
+ 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;
+ } 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).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;
+ 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 | undefined,
+ };
+ });
+
+ return {
+ sprites,
+ count: sprites.length,
+ };
+ },
+});
+
+export default spritesListTool;
diff --git a/packages/tools/official/sprites-list/tsconfig.json b/packages/tools/official/sprites-list/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-list/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-list/tsup.config.ts b/packages/tools/official/sprites-list/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-list/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-policy-get/package.json b/packages/tools/official/sprites-policy-get/package.json
new file mode 100644
index 0000000..7335850
--- /dev/null
+++ b/packages/tools/official/sprites-policy-get/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-policy-get/src/index.ts b/packages/tools/official/sprites-policy-get/src/index.ts
new file mode 100644
index 0000000..010907b
--- /dev/null
+++ b/packages/tools/official/sprites-policy-get/src/index.ts
@@ -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[];
+}
+
+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({
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ description: 'Name of the sprite to get policy for',
+ },
+ },
+ required: ['name'],
+ additionalProperties: false,
+ }),
+ async execute({ name }): Promise {
+ 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;
+ try {
+ data = (await response.json()) as Record;
+ } 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[] | undefined,
+ };
+ },
+});
+
+export default spritesPolicyGetTool;
diff --git a/packages/tools/official/sprites-policy-get/tsconfig.json b/packages/tools/official/sprites-policy-get/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-policy-get/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-policy-get/tsup.config.ts b/packages/tools/official/sprites-policy-get/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-policy-get/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-policy-set/package.json b/packages/tools/official/sprites-policy-set/package.json
new file mode 100644
index 0000000..b527ed8
--- /dev/null
+++ b/packages/tools/official/sprites-policy-set/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-policy-set/src/index.ts b/packages/tools/official/sprites-policy-set/src/index.ts
new file mode 100644
index 0000000..6e46d2b
--- /dev/null
+++ b/packages/tools/official/sprites-policy-set/src/index.ts
@@ -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[];
+}
+
+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({
+ 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 {
+ 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;
+ try {
+ data = (await response.json()) as Record;
+ } 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[] | undefined,
+ },
+ applied: true,
+ };
+ },
+});
+
+export default spritesPolicySetTool;
diff --git a/packages/tools/official/sprites-policy-set/tsconfig.json b/packages/tools/official/sprites-policy-set/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-policy-set/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-policy-set/tsup.config.ts b/packages/tools/official/sprites-policy-set/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-policy-set/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/sprites-sessions/package.json b/packages/tools/official/sprites-sessions/package.json
new file mode 100644
index 0000000..37a80fd
--- /dev/null
+++ b/packages/tools/official/sprites-sessions/package.json
@@ -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"
+ }
+}
diff --git a/packages/tools/official/sprites-sessions/src/index.ts b/packages/tools/official/sprites-sessions/src/index.ts
new file mode 100644
index 0000000..1820c88
--- /dev/null
+++ b/packages/tools/official/sprites-sessions/src/index.ts
@@ -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({
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ description: 'Name of the sprite to list sessions for',
+ },
+ },
+ required: ['name'],
+ additionalProperties: false,
+ }),
+ async execute({ name }): Promise {
+ 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;
+ } catch {
+ throw new Error('Failed to parse response from Sprites API');
+ }
+
+ const sessionsArray = Array.isArray(data) ? data : (data as Record).sessions;
+
+ const sessions: ExecSession[] = (Array.isArray(sessionsArray) ? sessionsArray : []).map(
+ (s: Record) => ({
+ 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;
diff --git a/packages/tools/official/sprites-sessions/tsconfig.json b/packages/tools/official/sprites-sessions/tsconfig.json
new file mode 100644
index 0000000..6521d56
--- /dev/null
+++ b/packages/tools/official/sprites-sessions/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "@tpmjs/tsconfig/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "incremental": false,
+ "composite": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/tools/official/sprites-sessions/tsup.config.ts b/packages/tools/official/sprites-sessions/tsup.config.ts
new file mode 100644
index 0000000..a242871
--- /dev/null
+++ b/packages/tools/official/sprites-sessions/tsup.config.ts
@@ -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,
+});
diff --git a/packages/tools/official/ticket-categorize/package.json b/packages/tools/official/ticket-categorize/package.json
index beb4cfe..522ea30 100644
--- a/packages/tools/official/ticket-categorize/package.json
+++ b/packages/tools/official/ticket-categorize/package.json
@@ -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": [
{
diff --git a/packages/tools/registryExecute/README.md b/packages/tools/registryExecute/README.md
index c0a2503..2e42d99 100644
--- a/packages/tools/registryExecute/README.md
+++ b/packages/tools/registryExecute/README.md
@@ -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 |
diff --git a/packages/tools/registryExecute/package.json b/packages/tools/registryExecute/package.json
index 99c5546..fbba93c 100644
--- a/packages/tools/registryExecute/package.json
+++ b/packages/tools/registryExecute/package.json
@@ -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
},
{
diff --git a/packages/tools/registrySearch/package.json b/packages/tools/registrySearch/package.json
index 0401044..f964bd7 100644
--- a/packages/tools/registrySearch/package.json
+++ b/packages/tools/registrySearch/package.json
@@ -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.",
diff --git a/packages/tools/search-registry/package.json b/packages/tools/search-registry/package.json
index 82a1966..8a5ef94 100644
--- a/packages/tools/search-registry/package.json
+++ b/packages/tools/search-registry/package.json
@@ -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.",
diff --git a/packages/tools/unsandbox/package.json b/packages/tools/unsandbox/package.json
index fe0af43..51d4cfd 100644
--- a/packages/tools/unsandbox/package.json
+++ b/packages/tools/unsandbox/package.json
@@ -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": {
diff --git a/packages/types/package.json b/packages/types/package.json
index 8923700..e630515 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -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",
diff --git a/packages/types/src/tpmjs.ts b/packages/types/src/tpmjs.ts
index 1a536b7..dec3ce5 100644
--- a/packages/types/src/tpmjs.ts
+++ b/packages/types/src/tpmjs.ts
@@ -14,6 +14,7 @@ export const TPMJS_CATEGORIES = [
'statistics',
'ops',
'agent',
+ 'sandbox',
'utilities',
'html',
'compliance',
@@ -101,44 +102,23 @@ export type TpmjsAiAgent = z.infer;
* 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;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1e29bf6..06cf916 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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:
diff --git a/scripts/ralph-validation/validate-all-priorities.sh b/scripts/ralph-validation/validate-all-priorities.sh
new file mode 100755
index 0000000..811284e
--- /dev/null
+++ b/scripts/ralph-validation/validate-all-priorities.sh
@@ -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
diff --git a/scripts/ralph-validation/validate-documentation.sh b/scripts/ralph-validation/validate-documentation.sh
new file mode 100755
index 0000000..f4a225f
--- /dev/null
+++ b/scripts/ralph-validation/validate-documentation.sh
@@ -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
diff --git a/scripts/ralph-validation/validate-sdk-packages.sh b/scripts/ralph-validation/validate-sdk-packages.sh
new file mode 100755
index 0000000..1e8a18c
--- /dev/null
+++ b/scripts/ralph-validation/validate-sdk-packages.sh
@@ -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
diff --git a/scripts/ralph-validation/validate-social-features.sh b/scripts/ralph-validation/validate-social-features.sh
new file mode 100755
index 0000000..6950083
--- /dev/null
+++ b/scripts/ralph-validation/validate-social-features.sh
@@ -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