refactor: replace exportName with name throughout codebase
- Update TpmjsToolDefinitionSchema to only use 'name' field - Add 'sandbox' as valid category for sprites tools - Update all package.json files to use 'name' instead of 'exportName' - Update documentation and source files accordingly - Add 11 new sprites tools for sandbox/code-execution
This commit is contained in:
parent
b1dd3371cd
commit
2cd2b10cd0
91 changed files with 4019 additions and 195 deletions
16
.claude/plugins/ralph-wiggum/commands/cancel-ralph.md
Normal file
16
.claude/plugins/ralph-wiggum/commands/cancel-ralph.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
description: Cancel the active Ralph loop
|
||||||
|
command: rm -f .claude/ralph-loop.local.md && echo "Ralph loop cancelled"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cancel Ralph Loop
|
||||||
|
|
||||||
|
Immediately cancel any active Ralph loop and allow normal session exit.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/cancel-ralph
|
||||||
|
```
|
||||||
|
|
||||||
|
This removes the state file that drives the loop, allowing the session to exit normally.
|
||||||
43
.claude/plugins/ralph-wiggum/commands/ralph-loop.md
Normal file
43
.claude/plugins/ralph-wiggum/commands/ralph-loop.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
description: Start Ralph Wiggum loop in current session
|
||||||
|
command: "${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ralph Loop
|
||||||
|
|
||||||
|
Start an iterative development loop that continues until the task is complete.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/ralph-loop "Your task description" [--max-iterations N] [--validation-script PATH] [--completion-promise TEXT]
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. You provide a task and optional validation criteria
|
||||||
|
2. Claude works on the task
|
||||||
|
3. When Claude tries to exit, the stop hook intercepts
|
||||||
|
4. If validation fails OR completion promise not met, the loop continues
|
||||||
|
5. Claude sees previous work and continues iterating
|
||||||
|
6. Loop ends when validation passes or max iterations reached
|
||||||
|
|
||||||
|
## Important Rules
|
||||||
|
|
||||||
|
- If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE
|
||||||
|
- Do NOT use false completion promises as an exit strategy
|
||||||
|
- The loop persists until genuine completion is achieved
|
||||||
|
- Use validation scripts for programmatic verification
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With validation script only
|
||||||
|
/ralph-loop "Build the SDK package" --validation-script ./scripts/validate-sdk.sh
|
||||||
|
|
||||||
|
# With completion promise
|
||||||
|
/ralph-loop "Fix all type errors" --completion-promise "ALL_TYPES_PASS"
|
||||||
|
|
||||||
|
# With both
|
||||||
|
/ralph-loop "Complete feature X" --max-iterations 15 --validation-script ./validate.sh --completion-promise "FEATURE_COMPLETE"
|
||||||
|
```
|
||||||
15
.claude/plugins/ralph-wiggum/hooks/hooks.json
Normal file
15
.claude/plugins/ralph-wiggum/hooks/hooks.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"description": "Ralph Wiggum plugin stop hook for self-referential loops",
|
||||||
|
"hooks": {
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
111
.claude/plugins/ralph-wiggum/hooks/stop-hook.sh
Executable file
111
.claude/plugins/ralph-wiggum/hooks/stop-hook.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Ralph Wiggum Stop Hook - Self-referential loop for iterative development
|
||||||
|
# This hook intercepts the Stop event and decides whether to continue the loop
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE_FILE=".claude/ralph-loop.local.md"
|
||||||
|
TRANSCRIPT_FILE="${CLAUDE_TRANSCRIPT:-}"
|
||||||
|
|
||||||
|
# Check if ralph loop is active
|
||||||
|
if [[ ! -f "$STATE_FILE" ]]; then
|
||||||
|
# No active loop, allow normal exit
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse the state file frontmatter
|
||||||
|
parse_frontmatter() {
|
||||||
|
local key="$1"
|
||||||
|
sed -n '/^---$/,/^---$/p' "$STATE_FILE" | grep "^${key}:" | sed "s/^${key}: *//" | tr -d '"'
|
||||||
|
}
|
||||||
|
|
||||||
|
iteration=$(parse_frontmatter "iteration")
|
||||||
|
max_iterations=$(parse_frontmatter "max_iterations")
|
||||||
|
completion_promise=$(parse_frontmatter "completion_promise")
|
||||||
|
prompt=$(parse_frontmatter "prompt")
|
||||||
|
validation_script=$(parse_frontmatter "validation_script")
|
||||||
|
|
||||||
|
# Validate numeric fields
|
||||||
|
if ! [[ "$iteration" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Error: Invalid iteration count in state file" >&2
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$max_iterations" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Error: Invalid max_iterations in state file" >&2
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if max iterations reached
|
||||||
|
if [[ "$max_iterations" -gt 0 ]] && [[ "$iteration" -ge "$max_iterations" ]]; then
|
||||||
|
echo "Ralph loop reached max iterations ($max_iterations). Exiting." >&2
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run validation script if provided
|
||||||
|
validation_passed=false
|
||||||
|
if [[ -n "$validation_script" ]] && [[ -f "$validation_script" ]]; then
|
||||||
|
echo "Running validation script: $validation_script" >&2
|
||||||
|
if bash "$validation_script" 2>&1; then
|
||||||
|
validation_passed=true
|
||||||
|
echo "Validation PASSED!" >&2
|
||||||
|
else
|
||||||
|
echo "Validation FAILED. Continuing loop..." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for completion promise in transcript
|
||||||
|
if [[ -n "$completion_promise" ]] && [[ -n "$TRANSCRIPT_FILE" ]] && [[ -f "$TRANSCRIPT_FILE" ]]; then
|
||||||
|
# Get the last assistant message
|
||||||
|
last_message=$(tail -100 "$TRANSCRIPT_FILE" | grep -o '<promise>[^<]*</promise>' | tail -1 | sed 's/<promise>\(.*\)<\/promise>/\1/' || true)
|
||||||
|
|
||||||
|
if [[ "$last_message" == "$completion_promise" ]]; then
|
||||||
|
# Also check if validation passed (if validation script exists)
|
||||||
|
if [[ -z "$validation_script" ]] || [[ "$validation_passed" == "true" ]]; then
|
||||||
|
echo "Completion promise matched and validation passed. Ralph loop complete!" >&2
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Completion promise matched but validation failed. Continuing..." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If validation passed without explicit promise, we can exit
|
||||||
|
if [[ "$validation_passed" == "true" ]] && [[ -z "$completion_promise" ]]; then
|
||||||
|
echo "Validation passed. Ralph loop complete!" >&2
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increment iteration
|
||||||
|
new_iteration=$((iteration + 1))
|
||||||
|
|
||||||
|
# Update state file
|
||||||
|
sed -i.bak "s/^iteration: .*/iteration: $new_iteration/" "$STATE_FILE"
|
||||||
|
rm -f "${STATE_FILE}.bak"
|
||||||
|
|
||||||
|
# Build the continuation message
|
||||||
|
cat << EOF
|
||||||
|
{
|
||||||
|
"decision": "block",
|
||||||
|
"reason": "Ralph loop iteration $new_iteration of $max_iterations",
|
||||||
|
"message": "
|
||||||
|
---
|
||||||
|
RALPH LOOP - Iteration $new_iteration / $max_iterations
|
||||||
|
---
|
||||||
|
|
||||||
|
Continue working on the task. Your previous iteration's work is preserved in the codebase.
|
||||||
|
|
||||||
|
TASK: $prompt
|
||||||
|
|
||||||
|
$(if [[ -n "$validation_script" ]]; then echo "VALIDATION: Run the validation to check progress. Script: $validation_script"; fi)
|
||||||
|
$(if [[ -n "$completion_promise" ]]; then echo "COMPLETION: Output <promise>$completion_promise</promise> ONLY when the task is completely done AND validation passes."; fi)
|
||||||
|
|
||||||
|
Review what you've done so far and continue from where you left off.
|
||||||
|
"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
120
.claude/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh
Executable file
120
.claude/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh
Executable file
|
|
@ -0,0 +1,120 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Setup Ralph Loop - Initialize the iterative development loop
|
||||||
|
# Usage: setup-ralph-loop.sh "PROMPT" [--max-iterations N] [--completion-promise TEXT] [--validation-script PATH]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE_FILE=".claude/ralph-loop.local.md"
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
MAX_ITERATIONS=20
|
||||||
|
COMPLETION_PROMISE=""
|
||||||
|
VALIDATION_SCRIPT=""
|
||||||
|
PROMPT=""
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--max-iterations)
|
||||||
|
MAX_ITERATIONS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--completion-promise)
|
||||||
|
COMPLETION_PROMISE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--validation-script)
|
||||||
|
VALIDATION_SCRIPT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat << EOF
|
||||||
|
Ralph Loop Setup
|
||||||
|
|
||||||
|
Usage: setup-ralph-loop.sh "PROMPT" [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--max-iterations N Maximum iterations before stopping (default: 20, 0 = unlimited)
|
||||||
|
--completion-promise TXT Phrase to output when complete (use <promise>TXT</promise>)
|
||||||
|
--validation-script PATH Script to run for validation (exit 0 = pass)
|
||||||
|
--help, -h Show this help
|
||||||
|
|
||||||
|
Example:
|
||||||
|
setup-ralph-loop.sh "Build the SDK package" --max-iterations 10 --validation-script ./validate.sh
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$PROMPT" ]]; then
|
||||||
|
PROMPT="$1"
|
||||||
|
else
|
||||||
|
PROMPT="$PROMPT $1"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Validate prompt
|
||||||
|
if [[ -z "$PROMPT" ]]; then
|
||||||
|
echo "Error: PROMPT is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate max iterations
|
||||||
|
if ! [[ "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Error: --max-iterations must be a number" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate validation script exists if provided
|
||||||
|
if [[ -n "$VALIDATION_SCRIPT" ]] && [[ ! -f "$VALIDATION_SCRIPT" ]]; then
|
||||||
|
echo "Error: Validation script not found: $VALIDATION_SCRIPT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create state directory
|
||||||
|
mkdir -p "$(dirname "$STATE_FILE")"
|
||||||
|
|
||||||
|
# Create state file
|
||||||
|
cat << EOF > "$STATE_FILE"
|
||||||
|
---
|
||||||
|
iteration: 1
|
||||||
|
max_iterations: $MAX_ITERATIONS
|
||||||
|
completion_promise: "$COMPLETION_PROMISE"
|
||||||
|
validation_script: "$VALIDATION_SCRIPT"
|
||||||
|
prompt: "$PROMPT"
|
||||||
|
started_at: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ralph Loop State
|
||||||
|
|
||||||
|
This file tracks the state of an active Ralph loop. DO NOT DELETE while loop is running.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
- **Task**: $PROMPT
|
||||||
|
- **Max Iterations**: $MAX_ITERATIONS
|
||||||
|
- **Validation Script**: ${VALIDATION_SCRIPT:-"None"}
|
||||||
|
- **Completion Promise**: ${COMPLETION_PROMISE:-"None (validation only)"}
|
||||||
|
|
||||||
|
## Progress Log
|
||||||
|
|
||||||
|
Iteration logs will be appended below as the loop progresses.
|
||||||
|
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Ralph loop initialized!"
|
||||||
|
echo " Task: $PROMPT"
|
||||||
|
echo " Max iterations: $MAX_ITERATIONS"
|
||||||
|
echo " Validation: ${VALIDATION_SCRIPT:-"None"}"
|
||||||
|
echo " Completion promise: ${COMPLETION_PROMISE:-"None"}"
|
||||||
|
echo ""
|
||||||
|
echo "The loop will continue until:"
|
||||||
|
if [[ -n "$VALIDATION_SCRIPT" ]]; then
|
||||||
|
echo " - Validation script passes ($VALIDATION_SCRIPT returns exit code 0)"
|
||||||
|
fi
|
||||||
|
if [[ -n "$COMPLETION_PROMISE" ]]; then
|
||||||
|
echo " - You output: <promise>$COMPLETION_PROMISE</promise>"
|
||||||
|
fi
|
||||||
|
echo " - OR max iterations ($MAX_ITERATIONS) is reached"
|
||||||
|
|
@ -154,7 +154,7 @@ automation, ai-ml, security, monitoring
|
||||||
- NPM_MIRROR: Different formula entirely
|
- NPM_MIRROR: Different formula entirely
|
||||||
|
|
||||||
### Field Names Inconsistent
|
### 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
|
- Deprecated fields (`parameters`, `returns`) mentioned but unclear when deprecated
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ Edit `manual-tools.ts` and add a new entry:
|
||||||
npmPackageName: 'example-package',
|
npmPackageName: 'example-package',
|
||||||
category: 'search',
|
category: 'search',
|
||||||
frameworks: ['vercel-ai'],
|
frameworks: ['vercel-ai'],
|
||||||
exportName: 'exampleTool',
|
name: 'exampleTool',
|
||||||
description: 'A clear, concise description of what this tool does',
|
description: 'A clear, concise description of what this tool does',
|
||||||
|
|
||||||
// Optional but recommended for 'rich' tier
|
// Optional but recommended for 'rich' tier
|
||||||
|
|
@ -116,24 +116,24 @@ pnpm dev --filter=@tpmjs/web
|
||||||
|
|
||||||
## Multi-Tool Packages
|
## 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
|
```typescript
|
||||||
{
|
{
|
||||||
npmPackageName: 'firecrawl-aisdk',
|
npmPackageName: 'firecrawl-aisdk',
|
||||||
exportName: 'scrapeTool',
|
name: 'scrapeTool',
|
||||||
description: 'Scrape websites...',
|
description: 'Scrape websites...',
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
npmPackageName: 'firecrawl-aisdk',
|
npmPackageName: 'firecrawl-aisdk',
|
||||||
exportName: 'searchTool',
|
name: 'searchTool',
|
||||||
description: 'Search the web...',
|
description: 'Search the web...',
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
npmPackageName: 'firecrawl-aisdk',
|
npmPackageName: 'firecrawl-aisdk',
|
||||||
exportName: 'crawlTool',
|
name: 'crawlTool',
|
||||||
description: 'Crawl entire websites...',
|
description: 'Crawl entire websites...',
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
|
|
|
||||||
149
TOP_5_PRIORITIES.md
Normal file
149
TOP_5_PRIORITIES.md
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
# Top 5 Priorities for TPMJS to Become Production-Ready
|
||||||
|
|
||||||
|
> Based on comprehensive codebase analysis - January 2026
|
||||||
|
|
||||||
|
TPMJS is approximately 70-75% towards being production-ready for widespread use. The platform has solid fundamentals: a well-architected monorepo, robust npm sync system, working MCP integration, and professional code quality standards. However, five critical gaps need addressing before TPMJS can become the "npm for AI tools" it aspires to be.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Complete the Developer SDK Packages
|
||||||
|
|
||||||
|
**The Problem:**
|
||||||
|
Developers can't easily integrate TPMJS tools into their applications. The SDK packages `@tpmjs/registry-search` and `@tpmjs/registry-execute` are either incomplete or missing. This defeats the core value proposition - if AI agents can't programmatically discover and execute tools from TPMJS, the registry is just a website, not an ecosystem.
|
||||||
|
|
||||||
|
**What's Needed:**
|
||||||
|
- `@tpmjs/registry-search` - TypeScript client for searching tools with full type safety
|
||||||
|
- `@tpmjs/registry-execute` - Execute any TPMJS tool from any Node.js application
|
||||||
|
- `@tpmjs/agent-toolkit` - Pre-built integration for popular agent frameworks (LangChain, AutoGPT, CrewAI)
|
||||||
|
- Clear examples showing integration with Claude, GPT-4, and other LLMs
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
Without these SDKs, TPMJS is just a directory. With them, every AI developer can instantly access 100+ tools with a single `npm install`. This is the difference between a catalog and a platform.
|
||||||
|
|
||||||
|
**Effort:** 2-4 weeks of focused development
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Add Social Proof and Discovery Features
|
||||||
|
|
||||||
|
**The Problem:**
|
||||||
|
Users have no way to evaluate tool quality beyond download counts. There's no star rating, no reviews, no "trending this week," and no recommendations. When browsing tools, users can't distinguish battle-tested tools from abandoned experiments.
|
||||||
|
|
||||||
|
**What's Needed:**
|
||||||
|
- **5-star rating system** with verified user ratings
|
||||||
|
- **User reviews** with upvoting and author responses
|
||||||
|
- **Trending tools** algorithm (based on recent usage, not just total downloads)
|
||||||
|
- **"Staff Picks"** or curated collections for common use cases
|
||||||
|
- **Similar tools** recommendations on each tool page
|
||||||
|
- **Usage statistics** - "Used in 50 agents" or "10,000 executions this month"
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
Social proof is essential for adoption. GitHub has stars, npm has weekly downloads prominently displayed, Product Hunt has upvotes. TPMJS needs its own trust signals. Without them, users default to building their own tools or using alternatives they can evaluate.
|
||||||
|
|
||||||
|
**Effort:** 3-4 weeks including UI/UX design
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Build Comprehensive Documentation and Onboarding
|
||||||
|
|
||||||
|
**The Problem:**
|
||||||
|
The publishing guide exists but there's no interactive tutorial for new users. API documentation is schema-only with no examples. Developers looking to build tools, create agents, or integrate TPMJS into their workflow face a steep learning curve with limited guidance.
|
||||||
|
|
||||||
|
**What's Needed:**
|
||||||
|
- **Interactive onboarding flow** - Guided first-time experience creating an agent with tools
|
||||||
|
- **API documentation** with copy-paste examples for every endpoint
|
||||||
|
- **Video tutorials** - 5-minute quickstarts for common tasks
|
||||||
|
- **Example agents** - Pre-built agents demonstrating best practices (research agent, coding assistant, data analyst)
|
||||||
|
- **Tool development guide** - Step-by-step from `npm init` to published tool
|
||||||
|
- **Troubleshooting guide** - Common errors and solutions
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
Documentation is a product feature. Every hour spent on docs saves thousands of hours of user frustration. LangChain succeeded partly because of excellent docs. TPMJS needs the same investment.
|
||||||
|
|
||||||
|
**Effort:** 4-6 weeks for comprehensive documentation overhaul
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Build Observability and Platform Trust
|
||||||
|
|
||||||
|
**The Problem:**
|
||||||
|
There's no public status page, no platform-wide health dashboard, and limited visibility into what's working. Users can't answer basic questions: "Is TPMJS up?", "How reliable is this tool?", "What's the average response time?"
|
||||||
|
|
||||||
|
**What's Needed:**
|
||||||
|
- **Public status page** (status.tpmjs.com) showing real-time platform health
|
||||||
|
- **Tool health dashboard** - Aggregate view of which tools are healthy/broken
|
||||||
|
- **Response time metrics** - P50/P95/P99 latency for tool executions
|
||||||
|
- **Uptime guarantees** - Published SLA (even informal "99.9% target")
|
||||||
|
- **Incident history** - Transparent communication about outages
|
||||||
|
- **Usage analytics dashboard** - For tool authors to see how their tools are used
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
Trust is earned through transparency. AWS publishes their health dashboard. GitHub has status.github.com. Enterprises won't adopt platforms they can't monitor. Even individual developers want to know if their agent's failures are their code or the platform.
|
||||||
|
|
||||||
|
**Effort:** 2-3 weeks for MVP status page and health dashboard
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Add Team and Enterprise Features
|
||||||
|
|
||||||
|
**The Problem:**
|
||||||
|
TPMJS is individual-only. There's no way to share collections within a team, manage API keys across an organization, or implement approval workflows. This blocks enterprise adoption where multiple developers need to collaborate on agent tooling.
|
||||||
|
|
||||||
|
**What's Needed:**
|
||||||
|
- **Organizations** - Create teams with shared collections and agents
|
||||||
|
- **Role-based access control (RBAC)** - Admin, Developer, Viewer roles
|
||||||
|
- **Shared API keys** - Organization-scoped keys with usage attribution
|
||||||
|
- **Audit logging** - Who did what, when (required for compliance)
|
||||||
|
- **Private tools** - Organization-only tool publishing
|
||||||
|
- **SSO/SAML** - Enterprise identity provider integration
|
||||||
|
- **Usage quotas** - Set limits per team member or project
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
Enterprise customers pay for tools. They also require these features for security and compliance. One enterprise contract can fund months of development. More importantly, enterprise adoption validates the platform and attracts more developers.
|
||||||
|
|
||||||
|
**Effort:** 6-8 weeks for core team features, 3-6 months for full enterprise suite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Priority | Impact | Effort | Recommended Order |
|
||||||
|
|----------|--------|--------|-------------------|
|
||||||
|
| 1. Complete SDK Packages | Critical | 2-4 weeks | First |
|
||||||
|
| 2. Social Proof/Discovery | High | 3-4 weeks | Second |
|
||||||
|
| 3. Documentation | High | 4-6 weeks | Parallel with #2 |
|
||||||
|
| 4. Observability/Trust | Medium-High | 2-3 weeks | Third |
|
||||||
|
| 5. Enterprise Features | Medium | 6-8 weeks | Fourth |
|
||||||
|
|
||||||
|
**Recommended approach:**
|
||||||
|
1. **Weeks 1-4:** Complete SDK packages (unlocks programmatic adoption)
|
||||||
|
2. **Weeks 2-6:** Build ratings/reviews and documentation in parallel
|
||||||
|
3. **Weeks 7-9:** Add status page and health dashboard
|
||||||
|
4. **Weeks 10+:** Begin enterprise features based on customer demand
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Strengths to Leverage
|
||||||
|
|
||||||
|
TPMJS already has strong foundations:
|
||||||
|
- Robust npm sync system (tools auto-discovered)
|
||||||
|
- Working MCP protocol integration
|
||||||
|
- Clean monorepo architecture
|
||||||
|
- Good authentication system
|
||||||
|
- Solid database design
|
||||||
|
- Quality coding standards
|
||||||
|
|
||||||
|
These investments mean the platform can scale. The gaps identified above are about adoption and trust, not technical architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Bottom Line
|
||||||
|
|
||||||
|
TPMJS has built a good tool registry. To become **the** AI tools platform, it needs to:
|
||||||
|
1. Make tools easy to use programmatically (SDKs)
|
||||||
|
2. Help users find good tools (social proof)
|
||||||
|
3. Help developers build tools (documentation)
|
||||||
|
4. Build platform confidence (observability)
|
||||||
|
5. Enable team adoption (enterprise features)
|
||||||
|
|
||||||
|
With focused effort on these five areas over the next 3-6 months, TPMJS can establish itself as the definitive platform for AI agent tooling.
|
||||||
|
|
@ -21,7 +21,7 @@ Load a tool from esm.sh and return its schema
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"packageName": "firecrawl-aisdk",
|
"packageName": "firecrawl-aisdk",
|
||||||
"exportName": "webSearchTool",
|
"name": "webSearchTool",
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"importUrl": "https://esm.sh/firecrawl-aisdk@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,
|
"success": true,
|
||||||
"tool": {
|
"tool": {
|
||||||
"exportName": "webSearchTool",
|
"name": "webSearchTool",
|
||||||
"description": "Search the web using Firecrawl",
|
"description": "Search the web using Firecrawl",
|
||||||
"inputSchema": { ... }
|
"inputSchema": { ... }
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ Execute a tool with parameters
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"packageName": "firecrawl-aisdk",
|
"packageName": "firecrawl-aisdk",
|
||||||
"exportName": "webSearchTool",
|
"name": "webSearchTool",
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"params": {
|
"params": {
|
||||||
"query": "latest AI news"
|
"query": "latest AI news"
|
||||||
|
|
@ -128,7 +128,7 @@ curl -X POST http://localhost:3001/load-and-describe \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"packageName": "@tpmjs/hello",
|
"packageName": "@tpmjs/hello",
|
||||||
"exportName": "helloWorldTool",
|
"name": "helloWorldTool",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
@ -139,7 +139,7 @@ curl -X POST http://localhost:3001/execute-tool \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"packageName": "@tpmjs/hello",
|
"packageName": "@tpmjs/hello",
|
||||||
"exportName": "helloWorldTool",
|
"name": "helloWorldTool",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"params": {}
|
"params": {}
|
||||||
}'
|
}'
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,16 @@ app.get('/health', (req, res) => {
|
||||||
* Returns tool metadata (description, schema) without executing
|
* Returns tool metadata (description, schema) without executing
|
||||||
*/
|
*/
|
||||||
app.post('/load-and-describe', async (req, res) => {
|
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({
|
return res.status(400).json({
|
||||||
success: false,
|
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 {
|
try {
|
||||||
let toolModule;
|
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
|
// esm.sh returns ES modules, try to get default or named export
|
||||||
const module = moduleExports.default || moduleExports;
|
const module = moduleExports.default || moduleExports;
|
||||||
toolModule = module[exportName] || module;
|
toolModule = module[name] || module;
|
||||||
|
|
||||||
if (!toolModule) {
|
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({
|
return res.status(404).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: `Export "${exportName}" not found in module`,
|
error: `Export "${name}" not found in module`,
|
||||||
availableExports: Object.keys(module),
|
availableExports: Object.keys(module),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +108,7 @@ app.post('/load-and-describe', async (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
tool: {
|
tool: {
|
||||||
exportName,
|
name,
|
||||||
description: toolModule.description,
|
description: toolModule.description,
|
||||||
inputSchema: toolModule.inputSchema || toolModule.parameters?.shape || {},
|
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
|
* Execute a dynamically loaded tool with parameters
|
||||||
*/
|
*/
|
||||||
app.post('/execute-tool', async (req, res) => {
|
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({
|
return res.status(400).json({
|
||||||
success: false,
|
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();
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -151,7 +151,7 @@ app.post('/execute-tool', async (req, res) => {
|
||||||
console.log(`📦 Importing for execution: ${url}`);
|
console.log(`📦 Importing for execution: ${url}`);
|
||||||
|
|
||||||
const module = await import(url);
|
const module = await import(url);
|
||||||
toolModule = module[exportName];
|
toolModule = module[name];
|
||||||
|
|
||||||
if (!toolModule || !toolModule.execute) {
|
if (!toolModule || !toolModule.execute) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
|
|
|
||||||
|
|
@ -550,9 +550,9 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
Deno.env.set(key, stringValue);
|
Deno.env.set(key, stringValue);
|
||||||
|
|
||||||
// ALSO set in Node.js process.env (for npm: imports)
|
// 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) {
|
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;
|
globalThis.process.env[key] = stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -782,10 +782,10 @@ async function listExports(req: Request): Promise<Response> {
|
||||||
error?: string;
|
error?: string;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
for (const exportName of allExports) {
|
for (const exportKey of allExports) {
|
||||||
if (exportName === 'default') continue;
|
if (exportKey === 'default') continue;
|
||||||
|
|
||||||
let rawExport = module[exportName];
|
let rawExport = module[exportKey];
|
||||||
|
|
||||||
// Check if it's a factory function
|
// Check if it's a factory function
|
||||||
if (typeof rawExport === 'function' && !rawExport.description && !rawExport.execute) {
|
if (typeof rawExport === 'function' && !rawExport.description && !rawExport.execute) {
|
||||||
|
|
@ -809,14 +809,14 @@ async function listExports(req: Request): Promise<Response> {
|
||||||
// Check if it's a valid AI SDK tool
|
// Check if it's a valid AI SDK tool
|
||||||
if (rawExport?.description && rawExport?.execute) {
|
if (rawExport?.description && rawExport?.execute) {
|
||||||
tools.push({
|
tools.push({
|
||||||
name: exportName,
|
name: exportKey,
|
||||||
isValidTool: true,
|
isValidTool: true,
|
||||||
description: rawExport.description,
|
description: rawExport.description,
|
||||||
});
|
});
|
||||||
} else if (typeof rawExport === 'object' && rawExport !== null) {
|
} else if (typeof rawExport === 'object' && rawExport !== null) {
|
||||||
// It's an object but not a valid tool - might be a factory that needs specific config
|
// It's an object but not a valid tool - might be a factory that needs specific config
|
||||||
tools.push({
|
tools.push({
|
||||||
name: exportName,
|
name: exportKey,
|
||||||
isValidTool: false,
|
isValidTool: false,
|
||||||
error: 'Not a valid AI SDK tool (missing description or execute)',
|
error: 'Not a valid AI SDK tool (missing description or execute)',
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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)
|
// URL-decode slug components (@ comes as %40)
|
||||||
const decodedSlug = slug.map((s) => decodeURIComponent(s));
|
const decodedSlug = slug.map((s) => decodeURIComponent(s));
|
||||||
|
|
||||||
if (decodedSlug[0]?.startsWith('@')) {
|
if (decodedSlug[0]?.startsWith('@')) {
|
||||||
// Scoped package: ['@scope', 'package', 'exportName?']
|
// Scoped package: ['@scope', 'package', 'toolName?']
|
||||||
const packageName = decodedSlug.slice(0, 2).join('/');
|
const packageName = decodedSlug.slice(0, 2).join('/');
|
||||||
const exportName = decodedSlug[2];
|
const toolName = decodedSlug[2];
|
||||||
return { packageName, exportName };
|
return { packageName, toolName };
|
||||||
}
|
}
|
||||||
// Unscoped: ['package', 'exportName?']
|
// Unscoped: ['package', 'toolName?']
|
||||||
return { packageName: decodedSlug[0] || '', exportName: decodedSlug[1] };
|
return { packageName: decodedSlug[0] || '', toolName: decodedSlug[1] };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch tool data from database
|
* Fetch tool data from database
|
||||||
*/
|
*/
|
||||||
async function getTool(slug: string[]): Promise<Tool | null> {
|
async function getTool(slug: string[]): Promise<Tool | null> {
|
||||||
const { packageName, exportName } = parseSlug(slug);
|
const { packageName, toolName } = parseSlug(slug);
|
||||||
|
|
||||||
if (!packageName) {
|
if (!packageName) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -49,7 +49,7 @@ async function getTool(slug: string[]): Promise<Tool | null> {
|
||||||
const tool = await prisma.tool.findFirst({
|
const tool = await prisma.tool.findFirst({
|
||||||
where: {
|
where: {
|
||||||
packageId: pkg.id,
|
packageId: pkg.id,
|
||||||
...(exportName && { name: exportName }),
|
...(toolName && { name: toolName }),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
package: true,
|
package: true,
|
||||||
|
|
@ -122,9 +122,9 @@ export async function generateMetadata({ params }: ToolDetailPageProps): Promise
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { packageName, exportName } = parseSlug(slug);
|
const { packageName, toolName } = parseSlug(slug);
|
||||||
const ogPath = exportName
|
const ogPath = toolName
|
||||||
? `/api/og/tool/${encodeURIComponent(packageName)}/${encodeURIComponent(exportName)}`
|
? `/api/og/tool/${encodeURIComponent(packageName)}/${encodeURIComponent(toolName)}`
|
||||||
: `/api/og/tool/${encodeURIComponent(packageName)}`;
|
: `/api/og/tool/${encodeURIComponent(packageName)}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Remove /tool/ prefix
|
||||||
const segments = path.replace(/^\/tool\//, '').split('/');
|
const segments = path.replace(/^\/tool\//, '').split('/');
|
||||||
|
|
||||||
let packageName: string;
|
let packageName: string;
|
||||||
let exportName: string | undefined;
|
let toolName: string | undefined;
|
||||||
|
|
||||||
if (segments[0]?.startsWith('@')) {
|
if (segments[0]?.startsWith('@')) {
|
||||||
// Scoped package: @scope/package/export
|
// Scoped package: @scope/package/toolName
|
||||||
packageName = segments.slice(0, 2).join('/');
|
packageName = segments.slice(0, 2).join('/');
|
||||||
exportName = segments[2];
|
toolName = segments[2];
|
||||||
} else {
|
} else {
|
||||||
// Unscoped: package/export
|
// Unscoped: package/toolName
|
||||||
packageName = segments[0] || '';
|
packageName = segments[0] || '';
|
||||||
exportName = segments[1];
|
toolName = segments[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return { packageName, exportName };
|
return { packageName, toolName };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch tool data from internal API
|
* Fetch tool data from internal API
|
||||||
*/
|
*/
|
||||||
async function fetchToolContent(path: string): Promise<PageContent> {
|
async function fetchToolContent(path: string): Promise<PageContent> {
|
||||||
const { packageName, exportName } = parseToolPath(path);
|
const { packageName, toolName } = parseToolPath(path);
|
||||||
|
|
||||||
// Build API URL
|
// Build API URL
|
||||||
const baseUrl = process.env.VERCEL_URL
|
const baseUrl = process.env.VERCEL_URL
|
||||||
? `https://${process.env.VERCEL_URL}`
|
? `https://${process.env.VERCEL_URL}`
|
||||||
: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
const apiPath = exportName
|
const apiPath = toolName
|
||||||
? `/api/tools/${encodeURIComponent(packageName)}/${encodeURIComponent(exportName)}`
|
? `/api/tools/${encodeURIComponent(packageName)}/${encodeURIComponent(toolName)}`
|
||||||
: `/api/tools/${encodeURIComponent(packageName)}`;
|
: `/api/tools/${encodeURIComponent(packageName)}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -157,11 +157,11 @@ async function fetchToolContent(path: string): Promise<PageContent> {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageType: 'tool',
|
pageType: 'tool',
|
||||||
title: tool.name || exportName || packageName,
|
title: tool.name || toolName || packageName,
|
||||||
description: tool.description || `AI tool from ${packageName}`,
|
description: tool.description || `AI tool from ${packageName}`,
|
||||||
keywords: [tool.package?.category || 'tool', 'AI', 'npm', packageName],
|
keywords: [tool.package?.category || 'tool', 'AI', 'npm', packageName],
|
||||||
tool: {
|
tool: {
|
||||||
name: tool.name || exportName || 'Tool',
|
name: tool.name || toolName || 'Tool',
|
||||||
packageName: tool.package?.npmPackageName || packageName,
|
packageName: tool.package?.npmPackageName || packageName,
|
||||||
category: tool.package?.category || 'other',
|
category: tool.package?.category || 'other',
|
||||||
description: tool.description || '',
|
description: tool.description || '',
|
||||||
|
|
@ -175,11 +175,11 @@ async function fetchToolContent(path: string): Promise<PageContent> {
|
||||||
// Return basic content on failure
|
// Return basic content on failure
|
||||||
return {
|
return {
|
||||||
pageType: 'tool',
|
pageType: 'tool',
|
||||||
title: exportName || packageName,
|
title: toolName || packageName,
|
||||||
description: `AI tool from ${packageName}`,
|
description: `AI tool from ${packageName}`,
|
||||||
keywords: ['tool', 'AI', 'npm'],
|
keywords: ['tool', 'AI', 'npm'],
|
||||||
tool: {
|
tool: {
|
||||||
name: exportName || 'Tool',
|
name: toolName || 'Tool',
|
||||||
packageName,
|
packageName,
|
||||||
category: 'other',
|
category: 'other',
|
||||||
description: '',
|
description: '',
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ TPMJS provides two main tools:
|
||||||
```typescript
|
```typescript
|
||||||
import { registrySearchTool } from '@tpmjs/registry-search';
|
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({
|
const result = await registrySearchTool.execute({
|
||||||
query: 'web scraping',
|
query: 'web scraping',
|
||||||
category: 'web-scraping',
|
category: 'web-scraping',
|
||||||
|
|
@ -203,9 +203,9 @@ const myRegistryExecuteTool = tool({
|
||||||
// YOUR CUSTOM EXECUTE FUNCTION
|
// YOUR CUSTOM EXECUTE FUNCTION
|
||||||
async execute({ toolId, params, env }) {
|
async execute({ toolId, params, env }) {
|
||||||
// Option 1: Execute locally instead of sandbox
|
// Option 1: Execute locally instead of sandbox
|
||||||
const [packageName, exportName] = toolId.split('::');
|
const [packageName, name] = toolId.split('::');
|
||||||
const pkg = await import(packageName);
|
const pkg = await import(packageName);
|
||||||
const toolFn = pkg[exportName];
|
const toolFn = pkg[name];
|
||||||
return toolFn.execute(params);
|
return toolFn.execute(params);
|
||||||
|
|
||||||
// Option 2: Route to your own executor
|
// Option 2: Route to your own executor
|
||||||
|
|
@ -700,7 +700,7 @@ async function executeSandbox(
|
||||||
timeout: number,
|
timeout: number,
|
||||||
abortSignal?: AbortSignal
|
abortSignal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
const [packageName, exportName] = toolId.split('::');
|
const [packageName, name] = toolId.split('::');
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
@ -714,7 +714,7 @@ async function executeSandbox(
|
||||||
const response = await fetch(`${executorUrl}/execute-tool`, {
|
const response = await fetch(`${executorUrl}/execute-tool`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ packageName, exportName, params, env }),
|
body: JSON.stringify({ packageName, name, params, env }),
|
||||||
signal: combinedSignal,
|
signal: combinedSignal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -735,14 +735,14 @@ async function executeLocal(
|
||||||
params: any,
|
params: any,
|
||||||
basePath?: string
|
basePath?: string
|
||||||
) {
|
) {
|
||||||
const [packageName, exportName] = toolId.split('::');
|
const [packageName, name] = toolId.split('::');
|
||||||
const modulePath = basePath ? `${basePath}/${packageName}` : packageName;
|
const modulePath = basePath ? `${basePath}/${packageName}` : packageName;
|
||||||
|
|
||||||
const module = await import(modulePath);
|
const module = await import(modulePath);
|
||||||
const toolFn = module[exportName] || module.default;
|
const toolFn = module[name] || module.default;
|
||||||
|
|
||||||
if (!toolFn?.execute) {
|
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);
|
return toolFn.execute(params);
|
||||||
|
|
@ -1151,7 +1151,7 @@ type SearchOutput = {
|
||||||
query: string;
|
query: string;
|
||||||
matchCount: number;
|
matchCount: number;
|
||||||
tools: Array<{
|
tools: Array<{
|
||||||
toolId: string; // "package::exportName"
|
toolId: string; // "package::name"
|
||||||
name: string;
|
name: string;
|
||||||
package: string;
|
package: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
@ -1170,7 +1170,7 @@ import { registryExecuteTool } from '@tpmjs/registry-execute';
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
type ExecuteInput = {
|
type ExecuteInput = {
|
||||||
toolId: string; // "package::exportName"
|
toolId: string; // "package::name"
|
||||||
params: Record<string, unknown>; // Tool parameters
|
params: Record<string, unknown>; // Tool parameters
|
||||||
env?: Record<string, string>; // Environment variables
|
env?: Record<string, string>; // Environment variables
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ A **tool** in TPMJS is an atomic unit of computation that:
|
||||||
```typescript
|
```typescript
|
||||||
// Tool metadata structure
|
// Tool metadata structure
|
||||||
interface Tool {
|
interface Tool {
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
parameters: Parameter[];
|
parameters: Parameter[];
|
||||||
returns: ReturnType;
|
returns: ReturnType;
|
||||||
|
|
@ -336,7 +336,7 @@ interface ExecutionStep {
|
||||||
// Tool reference
|
// Tool reference
|
||||||
tool: {
|
tool: {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -415,7 +415,7 @@ interface ExecutionStep {
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"tool": {
|
"tool": {
|
||||||
"packageName": "tpmjs-web-scraper",
|
"packageName": "tpmjs-web-scraper",
|
||||||
"exportName": "scrapeUrl"
|
"name": "scrapeUrl"
|
||||||
},
|
},
|
||||||
"purpose": "Fetch the product listings page HTML",
|
"purpose": "Fetch the product listings page HTML",
|
||||||
"input": {
|
"input": {
|
||||||
|
|
@ -448,7 +448,7 @@ interface ExecutionStep {
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"tool": {
|
"tool": {
|
||||||
"packageName": "tpmjs-html-parser",
|
"packageName": "tpmjs-html-parser",
|
||||||
"exportName": "extractElements"
|
"name": "extractElements"
|
||||||
},
|
},
|
||||||
"purpose": "Extract product cards from the HTML",
|
"purpose": "Extract product cards from the HTML",
|
||||||
"input": {
|
"input": {
|
||||||
|
|
@ -477,7 +477,7 @@ interface ExecutionStep {
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"tool": {
|
"tool": {
|
||||||
"packageName": "tpmjs-price-extractor",
|
"packageName": "tpmjs-price-extractor",
|
||||||
"exportName": "extractPrices"
|
"name": "extractPrices"
|
||||||
},
|
},
|
||||||
"purpose": "Parse and normalize price values",
|
"purpose": "Parse and normalize price values",
|
||||||
"input": {
|
"input": {
|
||||||
|
|
@ -507,7 +507,7 @@ interface ExecutionStep {
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"tool": {
|
"tool": {
|
||||||
"packageName": "tpmjs-text-cleaner",
|
"packageName": "tpmjs-text-cleaner",
|
||||||
"exportName": "cleanProductNames"
|
"name": "cleanProductNames"
|
||||||
},
|
},
|
||||||
"purpose": "Clean and normalize product names",
|
"purpose": "Clean and normalize product names",
|
||||||
"input": {
|
"input": {
|
||||||
|
|
@ -533,7 +533,7 @@ interface ExecutionStep {
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"tool": {
|
"tool": {
|
||||||
"packageName": "tpmjs-data-merger",
|
"packageName": "tpmjs-data-merger",
|
||||||
"exportName": "mergeArrays"
|
"name": "mergeArrays"
|
||||||
},
|
},
|
||||||
"purpose": "Combine prices and names into product objects",
|
"purpose": "Combine prices and names into product objects",
|
||||||
"input": {
|
"input": {
|
||||||
|
|
@ -561,7 +561,7 @@ interface ExecutionStep {
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"tool": {
|
"tool": {
|
||||||
"packageName": "tpmjs-spreadsheet-generator",
|
"packageName": "tpmjs-spreadsheet-generator",
|
||||||
"exportName": "createXlsx"
|
"name": "createXlsx"
|
||||||
},
|
},
|
||||||
"purpose": "Generate Excel spreadsheet with price comparison",
|
"purpose": "Generate Excel spreadsheet with price comparison",
|
||||||
"input": {
|
"input": {
|
||||||
|
|
@ -1587,7 +1587,7 @@ async function executeWithFallback(
|
||||||
const modifiedStep = { ...step, tool };
|
const modifiedStep = { ...step, tool };
|
||||||
return await executeStep(modifiedStep, context);
|
return await executeStep(modifiedStep, context);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Tool ${tool.exportName} failed, trying fallback...`);
|
console.log(`Tool ${tool.name} failed, trying fallback...`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
* │ TOOL │
|
* │ TOOL │
|
||||||
* │ ───── │
|
* │ ───── │
|
||||||
* │ packageName: "tpmjs-web-scraper" │
|
* │ packageName: "tpmjs-web-scraper" │
|
||||||
* │ exportName: "scrapeUrl" │
|
* │ name: "scrapeUrl" │
|
||||||
* │ description: "Fetches webpage..." │
|
* │ description: "Fetches webpage..." │
|
||||||
* │ parameters: [...] │
|
* │ parameters: [...] │
|
||||||
* │ returns: { type: "string" } │
|
* │ returns: { type: "string" } │
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
export interface Tool {
|
export interface Tool {
|
||||||
id: string;
|
id: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
parameters: ToolParameter[];
|
parameters: ToolParameter[];
|
||||||
returns: ToolReturn;
|
returns: ToolReturn;
|
||||||
|
|
@ -254,7 +254,7 @@ export interface PlanStep {
|
||||||
|
|
||||||
export interface ToolReference {
|
export interface ToolReference {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -743,7 +743,7 @@ export interface ToolContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolPreview {
|
export interface ToolPreview {
|
||||||
exportName: string;
|
name: string;
|
||||||
purpose: string;
|
purpose: string;
|
||||||
tokenEstimate: number;
|
tokenEstimate: number;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,10 @@ Returns tool metadata including name, description, required env vars, and how to
|
||||||
matchCount: data.data.length,
|
matchCount: data.data.length,
|
||||||
tools: data.data.map((tool: any) => ({
|
tools: data.data.map((tool: any) => ({
|
||||||
// Unique identifier for registryExecuteTool
|
// Unique identifier for registryExecuteTool
|
||||||
toolId: `${tool.package.npmPackageName}::${tool.exportName}`,
|
toolId: `${tool.package.npmPackageName}::${tool.name}`,
|
||||||
|
|
||||||
// Human-readable info
|
// Human-readable info
|
||||||
name: tool.exportName,
|
name: tool.name,
|
||||||
package: tool.package.npmPackageName,
|
package: tool.package.npmPackageName,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
category: tool.category,
|
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.`,
|
The tool runs in a secure sandbox - you don't need to install anything.`,
|
||||||
|
|
||||||
parameters: z.object({
|
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'),
|
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'),
|
env: z.record(z.string()).optional().describe('Environment variables (API keys) if required'),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
execute: async ({ toolId, params, env }) => {
|
execute: async ({ toolId, params, env }) => {
|
||||||
const [packageName, exportName] = toolId.split('::');
|
const [packageName, name] = toolId.split('::');
|
||||||
|
|
||||||
if (!packageName || !exportName) {
|
if (!packageName || !name) {
|
||||||
throw new Error(`Invalid toolId format. Expected "package::exportName", got "${toolId}"`);
|
throw new Error(`Invalid toolId format. Expected "package::name", got "${toolId}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tool metadata to find version and importUrl
|
// Get tool metadata to find version and importUrl
|
||||||
const metaResponse = await fetch(
|
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 metaData = await metaResponse.json();
|
||||||
const toolMeta = metaData.data?.[0];
|
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' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version: toolMeta.package.npmVersion,
|
version: toolMeta.package.npmVersion,
|
||||||
importUrl: toolMeta.importUrl || `https://esm.sh/${packageName}@${toolMeta.package.npmVersion}`,
|
importUrl: toolMeta.importUrl || `https://esm.sh/${packageName}@${toolMeta.package.npmVersion}`,
|
||||||
params,
|
params,
|
||||||
|
|
@ -291,7 +291,7 @@ Response:
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"id": "...",
|
"id": "...",
|
||||||
"exportName": "webSearch",
|
"name": "webSearch",
|
||||||
"description": "Search the web...",
|
"description": "Search the web...",
|
||||||
"category": "search",
|
"category": "search",
|
||||||
"executionHealth": "HEALTHY",
|
"executionHealth": "HEALTHY",
|
||||||
|
|
@ -319,7 +319,7 @@ Request:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"packageName": "@exalabs/ai-sdk",
|
"packageName": "@exalabs/ai-sdk",
|
||||||
"exportName": "webSearch",
|
"name": "webSearch",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5",
|
"importUrl": "https://esm.sh/@exalabs/ai-sdk@1.0.5",
|
||||||
"params": { "query": "latest AI news" },
|
"params": { "query": "latest AI news" },
|
||||||
|
|
|
||||||
|
|
@ -132,10 +132,10 @@ After every tool execution, the executor reports the result:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// On successful execution
|
// On successful execution
|
||||||
reportToolHealth(packageName, exportName, true).catch(() => {});
|
reportToolHealth(packageName, name, true).catch(() => {});
|
||||||
|
|
||||||
// On failed execution
|
// 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.
|
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' | \
|
-H 'Authorization: Bearer tpmjs_sk_your_api_key_here' | \
|
||||||
jq '.data[] | select(.package.npmPackageName == "PACKAGE_NAME") | {
|
jq '.data[] | select(.package.npmPackageName == "PACKAGE_NAME") | {
|
||||||
packageName: .package.npmPackageName,
|
packageName: .package.npmPackageName,
|
||||||
exportName: .exportName,
|
name: .name,
|
||||||
importHealth: .importHealth,
|
importHealth: .importHealth,
|
||||||
executionHealth: .executionHealth,
|
executionHealth: .executionHealth,
|
||||||
healthCheckError: .healthCheckError,
|
healthCheckError: .healthCheckError,
|
||||||
|
|
@ -188,7 +188,7 @@ curl -X POST 'https://tpmjs.com/api/tools/report-health' \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{
|
-d '{
|
||||||
"packageName": "@scope/package",
|
"packageName": "@scope/package",
|
||||||
"exportName": "toolName",
|
"name": "toolName",
|
||||||
"success": true
|
"success": true
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
@ -197,7 +197,7 @@ curl -X POST 'https://tpmjs.com/api/tools/report-health' \
|
||||||
|
|
||||||
### 1. Executor Bugs Masking Tool Errors
|
### 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:
|
**Lesson:** Always ensure executor error handling is bulletproof. Any variable used in a catch block MUST be declared before the try block with sensible defaults:
|
||||||
|
|
||||||
|
|
@ -206,16 +206,16 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
// Declare with defaults BEFORE try
|
// Declare with defaults BEFORE try
|
||||||
let packageName = 'unknown';
|
let packageName = 'unknown';
|
||||||
let exportName = 'unknown';
|
let name = 'unknown';
|
||||||
try {
|
try {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { packageName: pkg, exportName: exp, ... } = body;
|
const { packageName: pkg, name: exp, ... } = body;
|
||||||
packageName = pkg || 'unknown';
|
packageName = pkg || 'unknown';
|
||||||
exportName = exp || 'unknown';
|
name = exp || 'unknown';
|
||||||
// ... rest of execution
|
// ... rest of execution
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Now these are always in scope
|
// Now these are always in scope
|
||||||
reportToolHealth(packageName, exportName, false, error.message);
|
reportToolHealth(packageName, name, false, error.message);
|
||||||
return Response.json({
|
return Response.json({
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ A tutorial on implementing a hierarchical planning agent that dynamically loads
|
||||||
interface Tool {
|
interface Tool {
|
||||||
id: string;
|
id: string;
|
||||||
packageName: string;
|
packageName: string;
|
||||||
exportName: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
parameters: Parameter[];
|
parameters: Parameter[];
|
||||||
returns: ReturnType;
|
returns: ReturnType;
|
||||||
|
|
@ -447,7 +447,7 @@ function buildStepContext(
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
|
|
||||||
// 1. Current tool description
|
// 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(current.tool.description);
|
||||||
parts.push(formatParameters(current.tool.parameters));
|
parts.push(formatParameters(current.tool.parameters));
|
||||||
|
|
||||||
|
|
@ -463,7 +463,7 @@ function buildStepContext(
|
||||||
// 3. Upcoming tools (just names, for continuity)
|
// 3. Upcoming tools (just names, for continuity)
|
||||||
if (upcoming.length > 0) {
|
if (upcoming.length > 0) {
|
||||||
parts.push(`## Coming Next`);
|
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)
|
// 4. Relevant prior results (summarized)
|
||||||
|
|
@ -500,7 +500,7 @@ async function executeWithFallbacks(
|
||||||
try {
|
try {
|
||||||
return await executeTool(tool, input, context);
|
return await executeTool(tool, input, context);
|
||||||
} catch (error) {
|
} 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
|
context: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/tools/execute/${tool.packageName}/${tool.exportName}`,
|
`/api/tools/execute/${tool.packageName}/${tool.name}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ input, context }),
|
body: JSON.stringify({ input, context }),
|
||||||
|
|
@ -973,7 +973,7 @@ export async function POST(req: Request) {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
steps: p.steps.map(s => ({
|
steps: p.steps.map(s => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
tool: s.tool.exportName,
|
tool: s.tool.name,
|
||||||
purpose: s.purpose
|
purpose: s.purpose
|
||||||
})),
|
})),
|
||||||
estimatedCost: p.estimatedCost,
|
estimatedCost: p.estimatedCost,
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"exportName": "scrapeTool",
|
"name": "scrapeTool",
|
||||||
"description": "Scrape content from any webpage and return structured data",
|
"description": "Scrape content from any webpage and return structured data",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -149,15 +149,15 @@ Tools declare their capabilities via a `tpmjs` field in package.json:
|
||||||
|
|
||||||
**1. Multi-tool packages**
|
**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
|
```json
|
||||||
{
|
{
|
||||||
"tpmjs": {
|
"tpmjs": {
|
||||||
"tools": [
|
"tools": [
|
||||||
{ "exportName": "scrapeTool", "description": "..." },
|
{ "name": "scrapeTool", "description": "..." },
|
||||||
{ "exportName": "screenshotTool", "description": "..." },
|
{ "name": "screenshotTool", "description": "..." },
|
||||||
{ "exportName": "pdfExtractTool", "description": "..." }
|
{ "name": "pdfExtractTool", "description": "..." }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -327,12 +327,12 @@ const conversationEnv = new Map<string, Record<string, string>>();
|
||||||
|
|
||||||
export async function loadToolDynamically(
|
export async function loadToolDynamically(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
exportName: string,
|
name: string,
|
||||||
version: string,
|
version: string,
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
): Promise<Tool | null> {
|
): Promise<Tool | null> {
|
||||||
const cacheKey = `${packageName}::${exportName}`;
|
const cacheKey = `${packageName}::${name}`;
|
||||||
|
|
||||||
// Return cached tool if available
|
// Return cached tool if available
|
||||||
if (moduleCache.has(cacheKey)) {
|
if (moduleCache.has(cacheKey)) {
|
||||||
|
|
@ -354,7 +354,7 @@ export async function loadToolDynamically(
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version,
|
version,
|
||||||
importUrl: `https://esm.sh/${packageName}@${version}`,
|
importUrl: `https://esm.sh/${packageName}@${version}`,
|
||||||
env: env || {},
|
env: env || {},
|
||||||
|
|
@ -378,7 +378,7 @@ export async function loadToolDynamically(
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
packageName,
|
packageName,
|
||||||
exportName,
|
name,
|
||||||
version,
|
version,
|
||||||
params,
|
params,
|
||||||
env: currentEnv, // Fresh on every execution
|
env: currentEnv, // Fresh on every execution
|
||||||
|
|
@ -395,7 +395,7 @@ export async function loadToolDynamically(
|
||||||
return toolWrapper;
|
return toolWrapper;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to load ${packageName}/${exportName}:`, error);
|
console.error(`Failed to load ${packageName}/${name}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -469,7 +469,7 @@ export async function POST(request: Request) {
|
||||||
const loadPromises = searchResults.tools.map(meta =>
|
const loadPromises = searchResults.tools.map(meta =>
|
||||||
loadToolDynamically(
|
loadToolDynamically(
|
||||||
meta.packageName,
|
meta.packageName,
|
||||||
meta.exportName,
|
meta.name,
|
||||||
meta.version,
|
meta.version,
|
||||||
conversationId,
|
conversationId,
|
||||||
env
|
env
|
||||||
|
|
@ -481,7 +481,7 @@ export async function POST(request: Request) {
|
||||||
// 7. Add to toolset with sanitized names
|
// 7. Add to toolset with sanitized names
|
||||||
searchResults.tools.forEach((meta, i) => {
|
searchResults.tools.forEach((meta, i) => {
|
||||||
if (loadedTools[i]) {
|
if (loadedTools[i]) {
|
||||||
const key = sanitizeToolName(`${meta.packageName}-${meta.exportName}`);
|
const key = sanitizeToolName(`${meta.packageName}-${meta.name}`);
|
||||||
discoveredTools[key] = loadedTools[i];
|
discoveredTools[key] = loadedTools[i];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -583,7 +583,7 @@ Run a separate service (Railway, Fly.io, AWS Lambda) that:
|
||||||
```typescript
|
```typescript
|
||||||
// Sandbox service (runs on Railway/Fly.io)
|
// Sandbox service (runs on Railway/Fly.io)
|
||||||
app.post('/execute-tool', async (req, res) => {
|
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
|
// Set env vars for this execution only
|
||||||
const originalEnv = { ...process.env };
|
const originalEnv = { ...process.env };
|
||||||
|
|
@ -594,9 +594,9 @@ app.post('/execute-tool', async (req, res) => {
|
||||||
const importUrl = `https://esm.sh/${packageName}@${version}`;
|
const importUrl = `https://esm.sh/${packageName}@${version}`;
|
||||||
const module = await import(importUrl);
|
const module = await import(importUrl);
|
||||||
|
|
||||||
const tool = module[exportName] || module.default;
|
const tool = module[name] || module.default;
|
||||||
if (!tool?.execute) {
|
if (!tool?.execute) {
|
||||||
throw new Error(`No executable tool found at ${exportName}`);
|
throw new Error(`No executable tool found at ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute with timeout
|
// Execute with timeout
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"exportName": "textToEmoji",
|
"name": "textToEmoji",
|
||||||
"description": "Convert text into emoji representations - perfect for making messages more expressive!"
|
"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"
|
"description": "Detect the mood/sentiment and suggest appropriate emojis for the text"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"exportName": "helloWorldTool",
|
"name": "helloWorldTool",
|
||||||
"description": "Returns a simple 'Hello, World!' greeting with optional timestamp and customizable message",
|
"description": "Returns a simple 'Hello, World!' greeting with optional timestamp and customizable message",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "helloNameTool",
|
"name": "helloNameTool",
|
||||||
"description": "Returns a personalized greeting with the provided name",
|
"description": "Returns a personalized greeting with the provided name",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"exportName": "markdownToPlainText",
|
"name": "markdownToPlainText",
|
||||||
"description": "Convert markdown to plain text by removing all formatting"
|
"description": "Convert markdown to plain text by removing all formatting"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "formatMarkdownTable",
|
"name": "formatMarkdownTable",
|
||||||
"description": "Format and align markdown table columns for better readability"
|
"description": "Format and align markdown table columns for better readability"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"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": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,37 @@ domain:
|
||||||
fields: [effect, standardError, assumptions, parallel]
|
fields: [effect, standardError, assumptions, parallel]
|
||||||
description: "Difference-in-differences result"
|
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
|
# Agent & workflow entities
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
@ -521,10 +552,9 @@ domain:
|
||||||
severity: warning
|
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)
|
# Core implementation rules (apply to ALL tools)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
@ -794,10 +824,10 @@ blocks:
|
||||||
- Handle missing/extra items
|
- Handle missing/extra items
|
||||||
- Report confidence in scores
|
- 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)
|
# A) Web Research & Evidence (15 tools)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -4494,6 +4524,260 @@ blocks:
|
||||||
description: "Standards to activities mapping"
|
description: "Standards to activities mapping"
|
||||||
measures: [working_implementation, valid_output_structure, readme_documentation]
|
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
|
# VALIDATORS - Which validators to run against each block
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Scores customer churn risk based on usage, engagement, and support signals. Provides risk score (0-100) with detailed contributing factors and recommendations.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"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": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Creates structured blog posts with customizable frontmatter, content sections, and SEO metadata. Supports multiple output formats including Markdown and MDX.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Scores email subject lines for open rate potential based on length, clarity, urgency, curiosity, and personalization. Provides detailed feedback and improvement suggestions.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Extracts themes and sentiment from customer feedback text. Identifies recurring themes, scores sentiment per theme, and provides frequency counts.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Analyzes NPS survey responses to categorize by promoter/passive/detractor and extract themes from comments. Provides NPS score, distribution, and actionable insights.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"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": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Matches bank transactions to ledger entries for reconciliation. Uses amount, date proximity, and description similarity to identify matches with confidence scoring.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"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": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
124
packages/tools/official/sprites-checkpoint-create/src/index.ts
Normal file
124
packages/tools/official/sprites-checkpoint-create/src/index.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
/**
|
||||||
|
* Sprites Checkpoint Create Tool for TPMJS
|
||||||
|
* Creates a point-in-time snapshot of a sprite's filesystem state for later restoration.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface Checkpoint {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
createdAt: string;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesCheckpointCreateInput = {
|
||||||
|
name: string;
|
||||||
|
checkpointName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesCheckpointCreateTool = tool({
|
||||||
|
description:
|
||||||
|
"Create a point-in-time snapshot (checkpoint) of a sprite's filesystem state for later restoration. Useful for saving state before risky operations.",
|
||||||
|
inputSchema: jsonSchema<SpritesCheckpointCreateInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to checkpoint',
|
||||||
|
},
|
||||||
|
checkpointName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional human-readable name for the checkpoint',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name, checkpointName }): Promise<Checkpoint> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 120000); // 2 min for checkpoints
|
||||||
|
|
||||||
|
const body: Record<string, unknown> = {};
|
||||||
|
if (checkpointName) {
|
||||||
|
body.name = checkpointName;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await fetch(
|
||||||
|
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
signal: controller.signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to create checkpoint for sprite "${name}" timed out`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to create checkpoint for sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to create checkpoint for sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to create checkpoint for sprite "${name}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: (data.id as string) || '',
|
||||||
|
name: (data.name as string) || checkpointName,
|
||||||
|
createdAt:
|
||||||
|
(data.createdAt as string) || (data.created_at as string) || new Date().toISOString(),
|
||||||
|
size: data.size as number | undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesCheckpointCreateTool;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
});
|
||||||
71
packages/tools/official/sprites-checkpoint-list/package.json
Normal file
71
packages/tools/official/sprites-checkpoint-list/package.json
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-checkpoint-list",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "List all checkpoints for a sprite ordered by creation time",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"checkpoint",
|
||||||
|
"snapshot",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-checkpoint-list"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesCheckpointListTool",
|
||||||
|
"description": "List all checkpoints for a sprite ordered by creation time",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to list checkpoints for",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "{ checkpoints: Checkpoint[], count: number }",
|
||||||
|
"description": "Array of checkpoints with count"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
127
packages/tools/official/sprites-checkpoint-list/src/index.ts
Normal file
127
packages/tools/official/sprites-checkpoint-list/src/index.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* Sprites Checkpoint List Tool for TPMJS
|
||||||
|
* Lists all checkpoints for a sprite ordered by creation time.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface Checkpoint {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
createdAt: string;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpritesCheckpointListResult {
|
||||||
|
checkpoints: Checkpoint[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesCheckpointListInput = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesCheckpointListTool = tool({
|
||||||
|
description:
|
||||||
|
'List all checkpoints for a sprite ordered by creation time. Use this to find checkpoint IDs for restoration.',
|
||||||
|
inputSchema: jsonSchema<SpritesCheckpointListInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to list checkpoints for',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name }): Promise<SpritesCheckpointListResult> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(
|
||||||
|
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to list checkpoints for sprite "${name}" timed out`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to list checkpoints for sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to list checkpoints for sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to list checkpoints for sprite "${name}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: unknown;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkpointsArray = Array.isArray(data)
|
||||||
|
? data
|
||||||
|
: (data as Record<string, unknown>).checkpoints;
|
||||||
|
|
||||||
|
const checkpoints: Checkpoint[] = (Array.isArray(checkpointsArray) ? checkpointsArray : []).map(
|
||||||
|
(c: Record<string, unknown>) => ({
|
||||||
|
id: c.id as string,
|
||||||
|
name: c.name as string | undefined,
|
||||||
|
createdAt: (c.createdAt as string) || (c.created_at as string) || '',
|
||||||
|
size: c.size as number | undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
checkpoints,
|
||||||
|
count: checkpoints.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesCheckpointListTool;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
});
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
135
packages/tools/official/sprites-checkpoint-restore/src/index.ts
Normal file
135
packages/tools/official/sprites-checkpoint-restore/src/index.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
/**
|
||||||
|
* Sprites Checkpoint Restore Tool for TPMJS
|
||||||
|
* Restores a sprite to a previous checkpoint state, reverting all filesystem changes.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface Sprite {
|
||||||
|
name: string;
|
||||||
|
status: 'creating' | 'running' | 'stopped' | 'error';
|
||||||
|
createdAt: string;
|
||||||
|
runtime?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpritesCheckpointRestoreResult {
|
||||||
|
restored: boolean;
|
||||||
|
checkpointId: string;
|
||||||
|
sprite: Sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesCheckpointRestoreInput = {
|
||||||
|
name: string;
|
||||||
|
checkpointId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesCheckpointRestoreTool = tool({
|
||||||
|
description:
|
||||||
|
'Restore a sprite to a previous checkpoint state, reverting all filesystem changes since that checkpoint. Use this to undo changes or recover from errors.',
|
||||||
|
inputSchema: jsonSchema<SpritesCheckpointRestoreInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to restore',
|
||||||
|
},
|
||||||
|
checkpointId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'ID of the checkpoint to restore to',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name', 'checkpointId'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name, checkpointId }): Promise<SpritesCheckpointRestoreResult> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
if (!checkpointId || typeof checkpointId !== 'string') {
|
||||||
|
throw new Error('Checkpoint ID is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 120000); // 2 min for restore
|
||||||
|
|
||||||
|
response = await fetch(
|
||||||
|
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/checkpoints/${encodeURIComponent(checkpointId)}/restore`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to restore checkpoint "${checkpointId}" timed out`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to restore checkpoint "${checkpointId}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to restore checkpoint "${checkpointId}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" or checkpoint "${checkpointId}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to restore checkpoint "${checkpointId}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
// If no JSON response, assume success
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const spriteData = (data.sprite as Record<string, unknown>) || data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
restored: true,
|
||||||
|
checkpointId,
|
||||||
|
sprite: {
|
||||||
|
name: (spriteData.name as string) || name,
|
||||||
|
status: (spriteData.status as Sprite['status']) || 'running',
|
||||||
|
createdAt: (spriteData.createdAt as string) || '',
|
||||||
|
runtime: spriteData.runtime as string | undefined,
|
||||||
|
metadata: spriteData.metadata as Record<string, unknown> | undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesCheckpointRestoreTool;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
});
|
||||||
70
packages/tools/official/sprites-create/package.json
Normal file
70
packages/tools/official/sprites-create/package.json
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-create",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Create a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"code-execution",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-create"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesCreateTool",
|
||||||
|
"description": "Create a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Unique name for the sprite (must be lowercase alphanumeric with hyphens)",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "Sprite",
|
||||||
|
"description": "Created sprite with name, status, and metadata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
141
packages/tools/official/sprites-create/src/index.ts
Normal file
141
packages/tools/official/sprites-create/src/index.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
/**
|
||||||
|
* Sprites Create Tool for TPMJS
|
||||||
|
* Creates a new isolated Linux sandbox environment (sprite) with persistent filesystem
|
||||||
|
* using the Sprites API.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output interface for a sprite
|
||||||
|
*/
|
||||||
|
export interface Sprite {
|
||||||
|
name: string;
|
||||||
|
status: 'creating' | 'running' | 'stopped' | 'error';
|
||||||
|
createdAt: string;
|
||||||
|
runtime?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesCreateInput = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a sprite name is valid
|
||||||
|
* Must be lowercase alphanumeric with hyphens, 3-63 characters
|
||||||
|
*/
|
||||||
|
function isValidSpriteName(name: string): boolean {
|
||||||
|
return /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Sprites API token from environment
|
||||||
|
*/
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sprites Create Tool
|
||||||
|
* Creates a new sprite sandbox environment
|
||||||
|
*/
|
||||||
|
export const spritesCreateTool = tool({
|
||||||
|
description:
|
||||||
|
'Create a new isolated Linux sandbox environment (sprite) with persistent filesystem using the Sprites API. Sprites are lightweight VMs for running code securely.',
|
||||||
|
inputSchema: jsonSchema<SpritesCreateInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'Unique name for the sprite (must be lowercase alphanumeric with hyphens, 3-63 characters)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name }): Promise<Sprite> {
|
||||||
|
// Validate input
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidSpriteName(name)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid sprite name: ${name}. Must be lowercase alphanumeric with hyphens, 3-63 characters.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
// Create the sprite via API
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60s timeout for creation
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name }),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to create sprite "${name}" timed out after 60 seconds`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to create sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to create sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
if (response.status === 409) {
|
||||||
|
throw new Error(`Sprite "${name}" already exists. Choose a different name.`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to create sprite "${name}": HTTP ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error(`Failed to parse response from Sprites API`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sprite: Sprite = {
|
||||||
|
name: (data.name as string) || name,
|
||||||
|
status: (data.status as Sprite['status']) || 'creating',
|
||||||
|
createdAt: (data.createdAt as string) || new Date().toISOString(),
|
||||||
|
runtime: data.runtime as string | undefined,
|
||||||
|
metadata: data.metadata as Record<string, unknown> | undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesCreateTool;
|
||||||
11
packages/tools/official/sprites-create/tsconfig.json
Normal file
11
packages/tools/official/sprites-create/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-create/tsup.config.ts
Normal file
10
packages/tools/official/sprites-create/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
70
packages/tools/official/sprites-delete/package.json
Normal file
70
packages/tools/official/sprites-delete/package.json
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-delete",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Delete a sprite and all its associated data including checkpoints",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"code-execution",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-delete"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesDeleteTool",
|
||||||
|
"description": "Delete a sprite and all its associated data including checkpoints",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to delete",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "{ deleted: boolean, name: string }",
|
||||||
|
"description": "Deletion result with success status"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
95
packages/tools/official/sprites-delete/src/index.ts
Normal file
95
packages/tools/official/sprites-delete/src/index.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* Sprites Delete Tool for TPMJS
|
||||||
|
* Deletes a sprite and all its associated data including checkpoints.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface SpritesDeleteResult {
|
||||||
|
deleted: boolean;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesDeleteInput = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesDeleteTool = tool({
|
||||||
|
description:
|
||||||
|
'Delete a sprite and all its associated data including checkpoints. This action is irreversible.',
|
||||||
|
inputSchema: jsonSchema<SpritesDeleteInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to delete',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name }): Promise<SpritesDeleteResult> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to delete sprite "${name}" timed out after 30 seconds`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to delete sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to delete sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(`Failed to delete sprite "${name}": HTTP ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: true,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesDeleteTool;
|
||||||
11
packages/tools/official/sprites-delete/tsconfig.json
Normal file
11
packages/tools/official/sprites-delete/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-delete/tsup.config.ts
Normal file
10
packages/tools/official/sprites-delete/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
88
packages/tools/official/sprites-exec/package.json
Normal file
88
packages/tools/official/sprites-exec/package.json
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-exec",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Execute a command inside a sprite and return the output with exit code",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"code-execution",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-exec"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesExecTool",
|
||||||
|
"description": "Execute a command inside a sprite and return the output with exit code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to execute command in",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cmd",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Command to execute",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stdin",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional stdin input",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "timeoutMs",
|
||||||
|
"type": "number",
|
||||||
|
"description": "Execution timeout in milliseconds",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "ExecResult",
|
||||||
|
"description": "Command output with exitCode, stdout, stderr, and duration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
138
packages/tools/official/sprites-exec/src/index.ts
Normal file
138
packages/tools/official/sprites-exec/src/index.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* Sprites Exec Tool for TPMJS
|
||||||
|
* Executes a command inside a sprite and returns the output.
|
||||||
|
* Supports stdin input for interactive commands.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface ExecResult {
|
||||||
|
exitCode: number;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesExecInput = {
|
||||||
|
name: string;
|
||||||
|
cmd: string;
|
||||||
|
stdin?: string;
|
||||||
|
timeoutMs?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesExecTool = tool({
|
||||||
|
description:
|
||||||
|
'Execute a command inside a sprite and return the output. Supports stdin input for interactive commands. Returns exit code, stdout, stderr, and execution duration.',
|
||||||
|
inputSchema: jsonSchema<SpritesExecInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to execute command in',
|
||||||
|
},
|
||||||
|
cmd: {
|
||||||
|
type: 'string',
|
||||||
|
description: "Command to execute (e.g., 'ls -la', 'python script.py')",
|
||||||
|
},
|
||||||
|
stdin: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Optional stdin input to pass to the command',
|
||||||
|
},
|
||||||
|
timeoutMs: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Execution timeout in milliseconds (default: 60000)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name', 'cmd'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name, cmd, stdin, timeoutMs }): Promise<ExecResult> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
if (!cmd || typeof cmd !== 'string') {
|
||||||
|
throw new Error('Command is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
const timeout = timeoutMs || 60000;
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
const body: Record<string, unknown> = { cmd };
|
||||||
|
if (stdin) {
|
||||||
|
body.stdin = stdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Command execution in sprite "${name}" timed out after ${timeout}ms`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to execute command in sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to execute command in sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to execute command in sprite "${name}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
exitCode: (data.exitCode as number) ?? (data.exit_code as number) ?? 0,
|
||||||
|
stdout: (data.stdout as string) || '',
|
||||||
|
stderr: (data.stderr as string) || '',
|
||||||
|
duration: (data.duration as number) || duration,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesExecTool;
|
||||||
11
packages/tools/official/sprites-exec/tsconfig.json
Normal file
11
packages/tools/official/sprites-exec/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-exec/tsup.config.ts
Normal file
10
packages/tools/official/sprites-exec/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
70
packages/tools/official/sprites-get/package.json
Normal file
70
packages/tools/official/sprites-get/package.json
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-get",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Retrieve details of a specific sprite by name including status and configuration",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"code-execution",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-get"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesGetTool",
|
||||||
|
"description": "Retrieve details of a specific sprite by name including status and configuration",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to retrieve",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "Sprite",
|
||||||
|
"description": "Sprite details including status and metadata"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
108
packages/tools/official/sprites-get/src/index.ts
Normal file
108
packages/tools/official/sprites-get/src/index.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
/**
|
||||||
|
* Sprites Get Tool for TPMJS
|
||||||
|
* Retrieves details of a specific sprite by name including status and configuration.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface Sprite {
|
||||||
|
name: string;
|
||||||
|
status: 'creating' | 'running' | 'stopped' | 'error';
|
||||||
|
createdAt: string;
|
||||||
|
runtime?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesGetInput = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesGetTool = tool({
|
||||||
|
description:
|
||||||
|
'Retrieve details of a specific sprite by name including status and configuration. Use this to check if a sprite exists and get its current state.',
|
||||||
|
inputSchema: jsonSchema<SpritesGetInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to retrieve',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name }): Promise<Sprite> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to get sprite "${name}" timed out after 30 seconds`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to get sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to get sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(`Failed to get sprite "${name}": HTTP ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: (data.name as string) || name,
|
||||||
|
status: (data.status as Sprite['status']) || 'running',
|
||||||
|
createdAt: (data.createdAt as string) || '',
|
||||||
|
runtime: data.runtime as string | undefined,
|
||||||
|
metadata: data.metadata as Record<string, unknown> | undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesGetTool;
|
||||||
11
packages/tools/official/sprites-get/tsconfig.json
Normal file
11
packages/tools/official/sprites-get/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-get/tsup.config.ts
Normal file
10
packages/tools/official/sprites-get/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
63
packages/tools/official/sprites-list/package.json
Normal file
63
packages/tools/official/sprites-list/package.json
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-list",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "List all sprites in the account with their current status and metadata",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"code-execution",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-list"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesListTool",
|
||||||
|
"description": "List all sprites in the account with their current status and metadata",
|
||||||
|
"parameters": [],
|
||||||
|
"returns": {
|
||||||
|
"type": "{ sprites: Sprite[], count: number }",
|
||||||
|
"description": "Array of sprites with count"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
118
packages/tools/official/sprites-list/src/index.ts
Normal file
118
packages/tools/official/sprites-list/src/index.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* Sprites List Tool for TPMJS
|
||||||
|
* Lists all sprites in the account with their current status and metadata.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface Sprite {
|
||||||
|
name: string;
|
||||||
|
status: 'creating' | 'running' | 'stopped' | 'error';
|
||||||
|
createdAt: string;
|
||||||
|
runtime?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpritesListResult {
|
||||||
|
sprites: Sprite[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesListTool = tool({
|
||||||
|
description:
|
||||||
|
'List all sprites in the account with their current status and metadata. Returns an array of sprites with their names, statuses, and creation times.',
|
||||||
|
inputSchema: jsonSchema<Record<string, never>>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {},
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute(): Promise<SpritesListResult> {
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error('Request to list sprites timed out after 30 seconds');
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to list sprites: ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error('Failed to list sprites: Unknown network error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to list sprites: HTTP ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: unknown;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle both array response and object with sprites property
|
||||||
|
const spritesArray = Array.isArray(data) ? data : (data as Record<string, unknown>).sprites;
|
||||||
|
|
||||||
|
if (!Array.isArray(spritesArray)) {
|
||||||
|
throw new Error('Invalid response from Sprites API: expected an array of sprites');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sprites: Sprite[] = spritesArray.map((s: unknown) => {
|
||||||
|
const sprite = s as Record<string, unknown>;
|
||||||
|
const status = sprite.status as string | undefined;
|
||||||
|
|
||||||
|
// Validate status is a known value
|
||||||
|
if (status && !['creating', 'running', 'stopped', 'error'].includes(status)) {
|
||||||
|
throw new Error(`Invalid sprite status: ${status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: sprite.name as string,
|
||||||
|
status: (status as Sprite['status']) || 'running',
|
||||||
|
createdAt: (sprite.createdAt as string) || '',
|
||||||
|
runtime: sprite.runtime as string | undefined,
|
||||||
|
metadata: sprite.metadata as Record<string, unknown> | undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sprites,
|
||||||
|
count: sprites.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesListTool;
|
||||||
11
packages/tools/official/sprites-list/tsconfig.json
Normal file
11
packages/tools/official/sprites-list/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-list/tsup.config.ts
Normal file
10
packages/tools/official/sprites-list/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
71
packages/tools/official/sprites-policy-get/package.json
Normal file
71
packages/tools/official/sprites-policy-get/package.json
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-policy-get",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Retrieve the current network policy for a sprite including allowed domains",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"network",
|
||||||
|
"policy",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-policy-get"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesPolicyGetTool",
|
||||||
|
"description": "Retrieve the current network policy for a sprite including allowed domains",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to get policy for",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "NetworkPolicy",
|
||||||
|
"description": "Current network policy with allowed domains and rules"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
106
packages/tools/official/sprites-policy-get/src/index.ts
Normal file
106
packages/tools/official/sprites-policy-get/src/index.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* Sprites Policy Get Tool for TPMJS
|
||||||
|
* Retrieves the current network policy for a sprite including allowed domains.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface NetworkPolicy {
|
||||||
|
mode: 'allow' | 'deny';
|
||||||
|
domains: string[];
|
||||||
|
rules?: Record<string, unknown>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesPolicyGetInput = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesPolicyGetTool = tool({
|
||||||
|
description:
|
||||||
|
'Retrieve the current network policy for a sprite including allowed domains and filtering rules. Use this to understand what network access a sprite has.',
|
||||||
|
inputSchema: jsonSchema<SpritesPolicyGetInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to get policy for',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name }): Promise<NetworkPolicy> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policies`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to get policy for sprite "${name}" timed out`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to get policy for sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to get policy for sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get policy for sprite "${name}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: (data.mode as NetworkPolicy['mode']) || 'deny',
|
||||||
|
domains: (data.domains as string[]) || (data.allowedDomains as string[]) || [],
|
||||||
|
rules: data.rules as Record<string, unknown>[] | undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesPolicyGetTool;
|
||||||
11
packages/tools/official/sprites-policy-get/tsconfig.json
Normal file
11
packages/tools/official/sprites-policy-get/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-policy-get/tsup.config.ts
Normal file
10
packages/tools/official/sprites-policy-get/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
83
packages/tools/official/sprites-policy-set/package.json
Normal file
83
packages/tools/official/sprites-policy-set/package.json
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-policy-set",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "Update the network policy for a sprite to control outbound network access",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"network",
|
||||||
|
"policy",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-policy-set"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesPolicySetTool",
|
||||||
|
"description": "Update the network policy for a sprite to control outbound network access",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to update policy for",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"type": "'allow' | 'deny'",
|
||||||
|
"description": "Policy mode",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "domains",
|
||||||
|
"type": "string[]",
|
||||||
|
"description": "List of domains to allow or deny",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "{ policy: NetworkPolicy, applied: boolean }",
|
||||||
|
"description": "Updated policy and application status"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
136
packages/tools/official/sprites-policy-set/src/index.ts
Normal file
136
packages/tools/official/sprites-policy-set/src/index.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
/**
|
||||||
|
* Sprites Policy Set Tool for TPMJS
|
||||||
|
* Updates the network policy for a sprite to control outbound network access.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface NetworkPolicy {
|
||||||
|
mode: 'allow' | 'deny';
|
||||||
|
domains: string[];
|
||||||
|
rules?: Record<string, unknown>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpritesPolicySetResult {
|
||||||
|
policy: NetworkPolicy;
|
||||||
|
applied: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesPolicySetInput = {
|
||||||
|
name: string;
|
||||||
|
mode: 'allow' | 'deny';
|
||||||
|
domains: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesPolicySetTool = tool({
|
||||||
|
description:
|
||||||
|
"Update the network policy for a sprite to control outbound network access with DNS-based filtering. Use 'allow' mode to block all traffic except listed domains, or 'deny' mode to allow all traffic except listed domains.",
|
||||||
|
inputSchema: jsonSchema<SpritesPolicySetInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to update policy for',
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['allow', 'deny'],
|
||||||
|
description:
|
||||||
|
"Policy mode - 'allow' blocks all except listed domains, 'deny' allows all except listed domains",
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'List of domains to allow or deny based on mode',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name', 'mode', 'domains'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name, mode, domains }): Promise<SpritesPolicySetResult> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
if (mode !== 'allow' && mode !== 'deny') {
|
||||||
|
throw new Error("Mode must be 'allow' or 'deny'");
|
||||||
|
}
|
||||||
|
if (!Array.isArray(domains)) {
|
||||||
|
throw new Error('Domains must be an array of strings');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/policies`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ mode, domains }),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to set policy for sprite "${name}" timed out`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to set policy for sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to set policy for sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to set policy for sprite "${name}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
// If no JSON response, assume policy was applied with our input
|
||||||
|
data = { mode, domains };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
policy: {
|
||||||
|
mode: (data.mode as NetworkPolicy['mode']) || mode,
|
||||||
|
domains: (data.domains as string[]) || domains,
|
||||||
|
rules: data.rules as Record<string, unknown>[] | undefined,
|
||||||
|
},
|
||||||
|
applied: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesPolicySetTool;
|
||||||
11
packages/tools/official/sprites-policy-set/tsconfig.json
Normal file
11
packages/tools/official/sprites-policy-set/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-policy-set/tsup.config.ts
Normal file
10
packages/tools/official/sprites-policy-set/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
70
packages/tools/official/sprites-sessions/package.json
Normal file
70
packages/tools/official/sprites-sessions/package.json
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "@tpmjs/tools-sprites-sessions",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"description": "List active execution sessions for a sprite",
|
||||||
|
"type": "module",
|
||||||
|
"keywords": [
|
||||||
|
"tpmjs",
|
||||||
|
"sprites",
|
||||||
|
"sandbox",
|
||||||
|
"code-execution",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist .turbo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tpmjs/tsconfig": "workspace:*",
|
||||||
|
"tsup": "^8.5.1",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/anthropics/tpmjs.git",
|
||||||
|
"directory": "packages/tools/official/sprites-sessions"
|
||||||
|
},
|
||||||
|
"homepage": "https://tpmjs.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"tpmjs": {
|
||||||
|
"category": "sandbox",
|
||||||
|
"frameworks": [
|
||||||
|
"vercel-ai"
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "spritesSessionsTool",
|
||||||
|
"description": "List active execution sessions for a sprite",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the sprite to list sessions for",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"type": "{ sessions: ExecSession[], count: number }",
|
||||||
|
"description": "Array of active execution sessions with count"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai": "6.0.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
125
packages/tools/official/sprites-sessions/src/index.ts
Normal file
125
packages/tools/official/sprites-sessions/src/index.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
/**
|
||||||
|
* Sprites Sessions Tool for TPMJS
|
||||||
|
* Lists active execution sessions for a sprite.
|
||||||
|
*
|
||||||
|
* @requires SPRITES_TOKEN environment variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { jsonSchema, tool } from 'ai';
|
||||||
|
|
||||||
|
const SPRITES_API_BASE = 'https://api.sprites.dev/v1';
|
||||||
|
|
||||||
|
export interface ExecSession {
|
||||||
|
id: string;
|
||||||
|
status: 'active' | 'completed' | 'terminated';
|
||||||
|
startedAt: string;
|
||||||
|
command?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpritesSessionsResult {
|
||||||
|
sessions: ExecSession[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpritesSessionsInput = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSpritesToken(): string {
|
||||||
|
const token = process.env.SPRITES_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error(
|
||||||
|
'SPRITES_TOKEN environment variable is required. Get your token from https://sprites.dev'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spritesSessionsTool = tool({
|
||||||
|
description:
|
||||||
|
'List active execution sessions for a sprite. Useful for monitoring running commands or attaching to existing sessions.',
|
||||||
|
inputSchema: jsonSchema<SpritesSessionsInput>({
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name of the sprite to list sessions for',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['name'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
async execute({ name }): Promise<SpritesSessionsResult> {
|
||||||
|
if (!name || typeof name !== 'string') {
|
||||||
|
throw new Error('Sprite name is required and must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getSpritesToken();
|
||||||
|
|
||||||
|
let response: Response;
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
||||||
|
|
||||||
|
response = await fetch(
|
||||||
|
`${SPRITES_API_BASE}/sprites/${encodeURIComponent(name)}/exec/sessions`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'User-Agent': 'TPMJS/1.0',
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`Request to list sessions for sprite "${name}" timed out`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to list sessions for sprite "${name}": ${error.message}`);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to list sessions for sprite "${name}": Unknown network error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error(`Sprite "${name}" not found`);
|
||||||
|
}
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new Error('Invalid SPRITES_TOKEN. Check your API token at https://sprites.dev');
|
||||||
|
}
|
||||||
|
const errorText = await response.text().catch(() => 'Unknown error');
|
||||||
|
throw new Error(
|
||||||
|
`Failed to list sessions for sprite "${name}": HTTP ${response.status} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: unknown;
|
||||||
|
try {
|
||||||
|
data = (await response.json()) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
throw new Error('Failed to parse response from Sprites API');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionsArray = Array.isArray(data) ? data : (data as Record<string, unknown>).sessions;
|
||||||
|
|
||||||
|
const sessions: ExecSession[] = (Array.isArray(sessionsArray) ? sessionsArray : []).map(
|
||||||
|
(s: Record<string, unknown>) => ({
|
||||||
|
id: s.id as string,
|
||||||
|
status: (s.status as ExecSession['status']) || 'active',
|
||||||
|
startedAt: (s.startedAt as string) || (s.started_at as string) || '',
|
||||||
|
command: s.command as string | undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessions,
|
||||||
|
count: sessions.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default spritesSessionsTool;
|
||||||
11
packages/tools/official/sprites-sessions/tsconfig.json
Normal file
11
packages/tools/official/sprites-sessions/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "@tpmjs/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"incremental": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
10
packages/tools/official/sprites-sessions/tsup.config.ts
Normal file
10
packages/tools/official/sprites-sessions/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'tsup';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
format: ['esm'],
|
||||||
|
dts: true,
|
||||||
|
clean: true,
|
||||||
|
treeshake: true,
|
||||||
|
splitting: false,
|
||||||
|
});
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Categorizes support tickets by type, priority, and product area. Suggests routing based on category and identifies urgent issues requiring immediate attention.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ Execute a tool from the TPMJS registry by its toolId.
|
||||||
|
|
||||||
| Name | Type | Required | Description |
|
| 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 |
|
| `params` | object | Yes | Parameters to pass to the tool |
|
||||||
| `env` | object | No | Environment variables (API keys) if required |
|
| `env` | object | No | Environment variables (API keys) if required |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,13 +44,13 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"exportName": "registryExecuteTool",
|
"name": "registryExecuteTool",
|
||||||
"description": "Execute a tool from the TPMJS registry by toolId. Use registrySearchTool first to find toolIds.",
|
"description": "Execute a tool from the TPMJS registry by toolId. Use registrySearchTool first to find toolIds.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "toolId",
|
"name": "toolId",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Tool identifier from registrySearchTool (format: 'package::exportName')",
|
"description": "Tool identifier from registrySearchTool (format: 'package::name')",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Search the TPMJS tool registry to find AI SDK tools. Returns tool metadata including toolId for use with registryExecuteTool.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
],
|
],
|
||||||
"returns": {
|
"returns": {
|
||||||
"type": "object",
|
"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": {
|
"aiAgent": {
|
||||||
"useCase": "Use when you need to find a tool for a specific task. Search returns toolIds that can be executed with registryExecuteTool.",
|
"useCase": "Use when you need to find a tool for a specific task. Search returns toolIds that can be executed with registryExecuteTool.",
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"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.",
|
"description": "Search the TPMJS tool registry to find AI SDK tools by keyword, category, or description. Returns tool metadata for dynamic loading.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
],
|
],
|
||||||
"returns": {
|
"returns": {
|
||||||
"type": "object",
|
"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": {
|
"aiAgent": {
|
||||||
"useCase": "Use when you need a tool that isn't currently available. Search for tools by keyword or domain.",
|
"useCase": "Use when you need a tool that isn't currently available. Search for tools by keyword or domain.",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
],
|
],
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"exportName": "executeCode",
|
"name": "executeCode",
|
||||||
"description": "Execute code synchronously in a secure sandbox. Supports 42+ languages including Python, JavaScript, TypeScript, Go, Rust, and more.",
|
"description": "Execute code synchronously in a secure sandbox. Supports 42+ languages including Python, JavaScript, TypeScript, Go, Rust, and more.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -99,7 +99,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "executeCodeAsync",
|
"name": "executeCodeAsync",
|
||||||
"description": "Execute code asynchronously in a secure sandbox. Returns a job_id immediately for tracking.",
|
"description": "Execute code asynchronously in a secure sandbox. Returns a job_id immediately for tracking.",
|
||||||
"parameters": [
|
"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).",
|
"description": "Execute code with automatic language detection from shebang line (e.g., #!/usr/bin/env python3).",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -151,7 +151,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "runCodeAsync",
|
"name": "runCodeAsync",
|
||||||
"description": "Execute code asynchronously with automatic language detection from shebang.",
|
"description": "Execute code asynchronously with automatic language detection from shebang.",
|
||||||
"parameters": [
|
"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.",
|
"description": "List all active jobs for your API key. Jobs are retained for 3 minutes after completion.",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"returns": {
|
"returns": {
|
||||||
|
|
@ -188,7 +188,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "getJob",
|
"name": "getJob",
|
||||||
"description": "Get status and results of a specific async job.",
|
"description": "Get status and results of a specific async job.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -210,7 +210,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "cancelJob",
|
"name": "cancelJob",
|
||||||
"description": "Cancel a running or pending async job.",
|
"description": "Cancel a running or pending async job.",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
|
|
@ -232,7 +232,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exportName": "listLanguages",
|
"name": "listLanguages",
|
||||||
"description": "List all 42+ supported programming languages and their aliases.",
|
"description": "List all 42+ supported programming languages and their aliases.",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"returns": {
|
"returns": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@tpmjs/types",
|
"name": "@tpmjs/types",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Shared TypeScript types and Zod schemas for TPMJS",
|
"description": "Shared TypeScript types and Zod schemas for TPMJS",
|
||||||
"author": "TPMJS",
|
"author": "TPMJS",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export const TPMJS_CATEGORIES = [
|
||||||
'statistics',
|
'statistics',
|
||||||
'ops',
|
'ops',
|
||||||
'agent',
|
'agent',
|
||||||
|
'sandbox',
|
||||||
'utilities',
|
'utilities',
|
||||||
'html',
|
'html',
|
||||||
'compliance',
|
'compliance',
|
||||||
|
|
@ -101,44 +102,23 @@ export type TpmjsAiAgent = z.infer<typeof TpmjsAiAgentSchema>;
|
||||||
* Optional fields (auto-extracted if not provided):
|
* Optional fields (auto-extracted if not provided):
|
||||||
* - description: A description of what the tool does (20-500 chars) - auto-extracted from tool
|
* - 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
|
* - parameters: Tool input parameters - auto-extracted from inputSchema
|
||||||
* - returns: Tool return type - auto-extracted from tool
|
* - returns: Tool return type - auto-extracted from tool
|
||||||
* - aiAgent: AI agent guidance - 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
|
export const TpmjsToolDefinitionSchema = z.object({
|
||||||
.object({
|
// Required: The export name of the tool from the package
|
||||||
// Required: The export name of the tool from the package
|
name: z.string().min(1, 'Tool name is required'),
|
||||||
// Accepts both 'name' and legacy 'exportName' field
|
// Optional - auto-extracted from tool if not provided
|
||||||
name: z.string().min(1).optional(),
|
description: z.string().min(20, 'Description must be at least 20 characters').max(500).optional(),
|
||||||
// @deprecated - renamed to 'name', kept for backward compatibility
|
// @deprecated - now auto-extracted from tool's inputSchema
|
||||||
exportName: z.string().min(1).optional(),
|
parameters: z.array(TpmjsParameterSchema).optional(),
|
||||||
// Optional - auto-extracted from tool if not provided
|
// @deprecated - now auto-extracted from tool
|
||||||
description: z
|
returns: TpmjsReturnsSchema.optional(),
|
||||||
.string()
|
// @deprecated - now auto-extracted from tool
|
||||||
.min(20, 'Description must be at least 20 characters')
|
aiAgent: TpmjsAiAgentSchema.optional(),
|
||||||
.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 type TpmjsToolDefinition = z.infer<typeof TpmjsToolDefinitionSchema>;
|
export type TpmjsToolDefinition = z.infer<typeof TpmjsToolDefinitionSchema>;
|
||||||
|
|
||||||
|
|
|
||||||
176
pnpm-lock.yaml
generated
176
pnpm-lock.yaml
generated
|
|
@ -3011,6 +3011,182 @@ importers:
|
||||||
specifier: ^5.9.3
|
specifier: ^5.9.3
|
||||||
version: 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:
|
packages/tools/official/stacktrace-parse:
|
||||||
dependencies:
|
dependencies:
|
||||||
ai:
|
ai:
|
||||||
|
|
|
||||||
64
scripts/ralph-validation/validate-all-priorities.sh
Executable file
64
scripts/ralph-validation/validate-all-priorities.sh
Executable file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Master validation script for top 3 priorities
|
||||||
|
# Exit 0 = all validations pass, Exit 1 = any validation fails
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " TPMJS Top 3 Priorities Validation"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
# Priority 1: SDK Packages
|
||||||
|
echo ">>> Priority 1: SDK Packages"
|
||||||
|
echo ""
|
||||||
|
if "$SCRIPT_DIR/validate-sdk-packages.sh"; then
|
||||||
|
echo ""
|
||||||
|
echo ">>> Priority 1: PASSED"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo ">>> Priority 1: FAILED"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Priority 2: Social Features
|
||||||
|
echo ">>> Priority 2: Social Proof & Discovery"
|
||||||
|
echo ""
|
||||||
|
if "$SCRIPT_DIR/validate-social-features.sh"; then
|
||||||
|
echo ""
|
||||||
|
echo ">>> Priority 2: PASSED"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo ">>> Priority 2: FAILED"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Priority 3: Documentation
|
||||||
|
echo ">>> Priority 3: Documentation & Onboarding"
|
||||||
|
echo ""
|
||||||
|
if "$SCRIPT_DIR/validate-documentation.sh"; then
|
||||||
|
echo ""
|
||||||
|
echo ">>> Priority 3: PASSED"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo ">>> Priority 3: FAILED"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " Final Summary"
|
||||||
|
echo "========================================"
|
||||||
|
if [[ $FAILED -eq 0 ]]; then
|
||||||
|
echo "✓ All 3 priorities complete!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "✗ $FAILED priority/priorities still need work"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
160
scripts/ralph-validation/validate-documentation.sh
Executable file
160
scripts/ralph-validation/validate-documentation.sh
Executable file
|
|
@ -0,0 +1,160 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Validation script for Priority 3: Documentation & Onboarding
|
||||||
|
# Exit 0 = validation passes, Exit 1 = validation fails
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Validating Documentation & Onboarding ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Check 1: API documentation pages
|
||||||
|
echo "1. Checking API documentation..."
|
||||||
|
API_DOCS_DIR="apps/web/src/app/docs/api"
|
||||||
|
if [[ -d "$API_DOCS_DIR" ]]; then
|
||||||
|
# Check for key API doc pages
|
||||||
|
REQUIRED_DOCS=("tools" "agents" "collections" "authentication")
|
||||||
|
for doc in "${REQUIRED_DOCS[@]}"; do
|
||||||
|
if [[ -f "$API_DOCS_DIR/$doc/page.tsx" ]] || [[ -f "$API_DOCS_DIR/$doc/page.mdx" ]]; then
|
||||||
|
echo " ✓ API docs: $doc exists"
|
||||||
|
else
|
||||||
|
echo " ✗ API docs: $doc missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo " ✗ API documentation directory missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 2: Interactive tutorial/quickstart
|
||||||
|
echo "2. Checking interactive tutorial..."
|
||||||
|
if [[ -f "apps/web/src/app/docs/quickstart/page.tsx" ]] || \
|
||||||
|
[[ -f "apps/web/src/app/tutorial/page.tsx" ]] || \
|
||||||
|
[[ -f "apps/web/src/app/getting-started/page.tsx" ]]; then
|
||||||
|
echo " ✓ Tutorial/quickstart page exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Interactive tutorial missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 3: Example code snippets
|
||||||
|
echo "3. Checking example code..."
|
||||||
|
EXAMPLES_COUNT=$(grep -r '\`\`\`typescript\|\`\`\`javascript\|\`\`\`bash' apps/web/src/app/docs/ 2>/dev/null | wc -l || echo "0")
|
||||||
|
if [[ "$EXAMPLES_COUNT" -gt 20 ]]; then
|
||||||
|
echo " ✓ Found $EXAMPLES_COUNT code examples in docs"
|
||||||
|
else
|
||||||
|
echo " ✗ Insufficient code examples (found: $EXAMPLES_COUNT, need: 20+)"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 4: SDK documentation
|
||||||
|
echo "4. Checking SDK documentation..."
|
||||||
|
if [[ -f "apps/web/src/app/docs/sdk/page.tsx" ]] || \
|
||||||
|
[[ -d "apps/web/src/app/docs/sdk" ]]; then
|
||||||
|
echo " ✓ SDK documentation exists"
|
||||||
|
else
|
||||||
|
echo " ✗ SDK documentation missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 5: Tool development guide
|
||||||
|
echo "5. Checking tool development guide..."
|
||||||
|
if [[ -f "apps/web/src/app/docs/publishing/page.tsx" ]] || \
|
||||||
|
[[ -f "apps/web/src/app/publish/page.tsx" ]]; then
|
||||||
|
|
||||||
|
# Check for comprehensive content
|
||||||
|
FILE=$(find apps/web/src/app -name "page.tsx" -path "*publish*" -o -name "page.tsx" -path "*docs/publishing*" 2>/dev/null | head -1)
|
||||||
|
if [[ -n "$FILE" ]] && [[ $(wc -l < "$FILE") -gt 100 ]]; then
|
||||||
|
echo " ✓ Tool development guide exists and is comprehensive"
|
||||||
|
else
|
||||||
|
echo " ⚠ Tool development guide exists but may be short"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ Tool development guide missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 6: API endpoint documentation completeness
|
||||||
|
echo "6. Checking API endpoint coverage..."
|
||||||
|
API_ROUTES=$(find apps/web/src/app/api -name "route.ts" 2>/dev/null | wc -l)
|
||||||
|
DOCUMENTED_ROUTES=$(grep -r "@api\|@route\|endpoint" apps/web/src/app/docs/api/ 2>/dev/null | wc -l || echo "0")
|
||||||
|
|
||||||
|
echo " API routes: $API_ROUTES"
|
||||||
|
echo " Documented references: $DOCUMENTED_ROUTES"
|
||||||
|
|
||||||
|
if [[ "$DOCUMENTED_ROUTES" -gt "$((API_ROUTES / 2))" ]]; then
|
||||||
|
echo " ✓ Good documentation coverage"
|
||||||
|
else
|
||||||
|
echo " ✗ Insufficient API documentation coverage"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 7: Onboarding flow components
|
||||||
|
echo "7. Checking onboarding components..."
|
||||||
|
if grep -rq "onboarding\|firstTime\|welcome\|tutorial" apps/web/src/components/ 2>/dev/null || \
|
||||||
|
[[ -d "apps/web/src/components/onboarding" ]]; then
|
||||||
|
echo " ✓ Onboarding components exist"
|
||||||
|
else
|
||||||
|
echo " ✗ Onboarding components missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 8: Example agents/collections
|
||||||
|
echo "8. Checking example templates..."
|
||||||
|
if grep -rq "example\|template\|starter" apps/web/src/app/docs/ 2>/dev/null || \
|
||||||
|
[[ -d "apps/web/src/app/templates" ]]; then
|
||||||
|
echo " ✓ Example templates exist"
|
||||||
|
else
|
||||||
|
echo " ✗ Example templates missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 9: Search/navigation in docs
|
||||||
|
echo "9. Checking docs navigation..."
|
||||||
|
if [[ -f "apps/web/src/components/docs/DocsSidebar.tsx" ]] || \
|
||||||
|
[[ -f "apps/web/src/app/docs/layout.tsx" ]]; then
|
||||||
|
echo " ✓ Docs navigation exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Docs navigation missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 10: Troubleshooting guide
|
||||||
|
echo "10. Checking troubleshooting guide..."
|
||||||
|
if [[ -f "apps/web/src/app/docs/troubleshooting/page.tsx" ]] || \
|
||||||
|
grep -rq "troubleshoot\|common errors\|FAQ" apps/web/src/app/docs/ 2>/dev/null; then
|
||||||
|
echo " ✓ Troubleshooting content exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Troubleshooting guide missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Validation Summary ==="
|
||||||
|
if [[ $ERRORS -eq 0 ]]; then
|
||||||
|
echo "✓ All checks passed!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "✗ $ERRORS check(s) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
169
scripts/ralph-validation/validate-sdk-packages.sh
Executable file
169
scripts/ralph-validation/validate-sdk-packages.sh
Executable file
|
|
@ -0,0 +1,169 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Validation script for Priority 1: Complete SDK Packages
|
||||||
|
# Exit 0 = validation passes, Exit 1 = validation fails
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Validating SDK Packages ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Check 1: @tpmjs/registry-search package exists
|
||||||
|
echo "1. Checking @tpmjs/registry-search package..."
|
||||||
|
if [[ -d "packages/registry-search" ]] && [[ -f "packages/registry-search/package.json" ]]; then
|
||||||
|
echo " ✓ Package directory exists"
|
||||||
|
|
||||||
|
# Check for src/index.ts
|
||||||
|
if [[ -f "packages/registry-search/src/index.ts" ]]; then
|
||||||
|
echo " ✓ Source file exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Missing src/index.ts"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check package.json has correct name
|
||||||
|
if grep -q '"name": "@tpmjs/registry-search"' packages/registry-search/package.json; then
|
||||||
|
echo " ✓ Package name correct"
|
||||||
|
else
|
||||||
|
echo " ✗ Package name incorrect"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ Package directory missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 2: @tpmjs/registry-execute package exists
|
||||||
|
echo "2. Checking @tpmjs/registry-execute package..."
|
||||||
|
if [[ -d "packages/registry-execute" ]] && [[ -f "packages/registry-execute/package.json" ]]; then
|
||||||
|
echo " ✓ Package directory exists"
|
||||||
|
|
||||||
|
if [[ -f "packages/registry-execute/src/index.ts" ]]; then
|
||||||
|
echo " ✓ Source file exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Missing src/index.ts"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q '"name": "@tpmjs/registry-execute"' packages/registry-execute/package.json; then
|
||||||
|
echo " ✓ Package name correct"
|
||||||
|
else
|
||||||
|
echo " ✗ Package name incorrect"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ Package directory missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 3: TypeScript compiles
|
||||||
|
echo "3. Checking TypeScript compilation..."
|
||||||
|
if command -v pnpm &> /dev/null; then
|
||||||
|
if pnpm --filter=@tpmjs/registry-search type-check 2>/dev/null; then
|
||||||
|
echo " ✓ registry-search compiles"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-search has type errors"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if pnpm --filter=@tpmjs/registry-execute type-check 2>/dev/null; then
|
||||||
|
echo " ✓ registry-execute compiles"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-execute has type errors"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ⚠ pnpm not available, skipping type check"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 4: Packages have required exports
|
||||||
|
echo "4. Checking required exports..."
|
||||||
|
REQUIRED_SEARCH_EXPORTS=("searchTools" "getToolById" "getTrendingTools")
|
||||||
|
REQUIRED_EXECUTE_EXPORTS=("executeToolCall" "createToolClient")
|
||||||
|
|
||||||
|
if [[ -f "packages/registry-search/src/index.ts" ]]; then
|
||||||
|
for export in "${REQUIRED_SEARCH_EXPORTS[@]}"; do
|
||||||
|
if grep -q "export.*$export" packages/registry-search/src/index.ts 2>/dev/null || \
|
||||||
|
grep -q "export.*$export" packages/registry-search/src/*.ts 2>/dev/null; then
|
||||||
|
echo " ✓ registry-search exports $export"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-search missing export: $export"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "packages/registry-execute/src/index.ts" ]]; then
|
||||||
|
for export in "${REQUIRED_EXECUTE_EXPORTS[@]}"; do
|
||||||
|
if grep -q "export.*$export" packages/registry-execute/src/index.ts 2>/dev/null || \
|
||||||
|
grep -q "export.*$export" packages/registry-execute/src/*.ts 2>/dev/null; then
|
||||||
|
echo " ✓ registry-execute exports $export"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-execute missing export: $export"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 5: Tests exist and pass
|
||||||
|
echo "5. Checking tests..."
|
||||||
|
if [[ -d "packages/registry-search/src/__tests__" ]] || [[ -d "packages/registry-search/test" ]]; then
|
||||||
|
echo " ✓ registry-search has tests"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-search missing tests"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "packages/registry-execute/src/__tests__" ]] || [[ -d "packages/registry-execute/test" ]]; then
|
||||||
|
echo " ✓ registry-execute has tests"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-execute missing tests"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 6: README documentation
|
||||||
|
echo "6. Checking documentation..."
|
||||||
|
if [[ -f "packages/registry-search/README.md" ]]; then
|
||||||
|
if [[ $(wc -l < packages/registry-search/README.md) -gt 20 ]]; then
|
||||||
|
echo " ✓ registry-search has README"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-search README too short"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ registry-search missing README"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "packages/registry-execute/README.md" ]]; then
|
||||||
|
if [[ $(wc -l < packages/registry-execute/README.md) -gt 20 ]]; then
|
||||||
|
echo " ✓ registry-execute has README"
|
||||||
|
else
|
||||||
|
echo " ✗ registry-execute README too short"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ registry-execute missing README"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Validation Summary ==="
|
||||||
|
if [[ $ERRORS -eq 0 ]]; then
|
||||||
|
echo "✓ All checks passed!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "✗ $ERRORS check(s) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
143
scripts/ralph-validation/validate-social-features.sh
Executable file
143
scripts/ralph-validation/validate-social-features.sh
Executable file
|
|
@ -0,0 +1,143 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Validation script for Priority 2: Social Proof & Discovery Features
|
||||||
|
# Exit 0 = validation passes, Exit 1 = validation fails
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Validating Social Proof & Discovery Features ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Check 1: Rating system API routes
|
||||||
|
echo "1. Checking rating API routes..."
|
||||||
|
if [[ -f "apps/web/src/app/api/tools/[id]/rate/route.ts" ]]; then
|
||||||
|
echo " ✓ Tool rating route exists"
|
||||||
|
|
||||||
|
# Check for POST handler
|
||||||
|
if grep -q "export async function POST" "apps/web/src/app/api/tools/[id]/rate/route.ts"; then
|
||||||
|
echo " ✓ POST handler for rating exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Missing POST handler for rating"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ Tool rating route missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 2: Review system API routes
|
||||||
|
echo "2. Checking review API routes..."
|
||||||
|
if [[ -f "apps/web/src/app/api/tools/[id]/reviews/route.ts" ]]; then
|
||||||
|
echo " ✓ Tool reviews route exists"
|
||||||
|
|
||||||
|
if grep -q "export async function GET" "apps/web/src/app/api/tools/[id]/reviews/route.ts"; then
|
||||||
|
echo " ✓ GET handler for reviews exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Missing GET handler for reviews"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "export async function POST" "apps/web/src/app/api/tools/[id]/reviews/route.ts"; then
|
||||||
|
echo " ✓ POST handler for reviews exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Missing POST handler for reviews"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ Tool reviews route missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 3: Database schema for ratings/reviews
|
||||||
|
echo "3. Checking database schema..."
|
||||||
|
if grep -q "model ToolRating" "packages/db/prisma/schema.prisma"; then
|
||||||
|
echo " ✓ ToolRating model exists"
|
||||||
|
else
|
||||||
|
echo " ✗ ToolRating model missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "model ToolReview" "packages/db/prisma/schema.prisma"; then
|
||||||
|
echo " ✓ ToolReview model exists"
|
||||||
|
else
|
||||||
|
echo " ✗ ToolReview model missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 4: Trending tools API
|
||||||
|
echo "4. Checking trending tools..."
|
||||||
|
if [[ -f "apps/web/src/app/api/tools/trending/route.ts" ]]; then
|
||||||
|
echo " ✓ Trending tools route exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Trending tools route missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 5: UI Components for ratings/reviews
|
||||||
|
echo "5. Checking UI components..."
|
||||||
|
if [[ -f "packages/ui/src/Rating/Rating.tsx" ]] || [[ -f "apps/web/src/components/Rating.tsx" ]]; then
|
||||||
|
echo " ✓ Rating component exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Rating component missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "packages/ui/src/ReviewCard/ReviewCard.tsx" ]] || [[ -f "apps/web/src/components/ReviewCard.tsx" ]]; then
|
||||||
|
echo " ✓ ReviewCard component exists"
|
||||||
|
else
|
||||||
|
echo " ✗ ReviewCard component missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 6: Tool page shows ratings
|
||||||
|
echo "6. Checking tool page integration..."
|
||||||
|
if [[ -f "apps/web/src/app/tool/[slug]/page.tsx" ]]; then
|
||||||
|
if grep -q "Rating\|rating\|averageRating" "apps/web/src/app/tool/[slug]/page.tsx"; then
|
||||||
|
echo " ✓ Tool page shows ratings"
|
||||||
|
else
|
||||||
|
echo " ✗ Tool page missing rating display"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "Review\|review" "apps/web/src/app/tool/[slug]/page.tsx"; then
|
||||||
|
echo " ✓ Tool page shows reviews"
|
||||||
|
else
|
||||||
|
echo " ✗ Tool page missing reviews display"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ⚠ Tool page not found at expected location"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check 7: Average rating calculation
|
||||||
|
echo "7. Checking rating aggregation..."
|
||||||
|
if grep -q "averageRating" "packages/db/prisma/schema.prisma" || \
|
||||||
|
grep -rq "AVG.*rating\|averageRating" "apps/web/src/app/api/"; then
|
||||||
|
echo " ✓ Rating aggregation exists"
|
||||||
|
else
|
||||||
|
echo " ✗ Rating aggregation missing"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Validation Summary ==="
|
||||||
|
if [[ $ERRORS -eq 0 ]]; then
|
||||||
|
echo "✓ All checks passed!"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "✗ $ERRORS check(s) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Loading…
Add table
Add a link
Reference in a new issue