diff --git a/.claude/skills/agentmail b/.claude/skills/agentmail new file mode 120000 index 0000000..63f635b --- /dev/null +++ b/.claude/skills/agentmail @@ -0,0 +1 @@ +../../.agents/skills/agentmail \ No newline at end of file diff --git a/.claude/skills/tpmjs-tool-creator/SKILL.md b/.claude/skills/tpmjs-tool-creator/SKILL.md new file mode 100644 index 0000000..38d0c7b --- /dev/null +++ b/.claude/skills/tpmjs-tool-creator/SKILL.md @@ -0,0 +1,296 @@ +--- +name: tpmjs-tool-creator +description: Guide for creating official TPMJS tools using the blocks CLI. Use when a user wants to create a new tool for the TPMJS registry, add a tool to packages/tools/official/, implement an AI SDK v6 tool, define a block in blocks.yml, validate a tool with `pnpm blocks run`, or publish a tool to npm with the tpmjs keyword. +--- + +# TPMJS Tool Creator + +Create production-ready tools for the TPMJS registry using the blocks CLI. Tools are npm packages following the AI SDK v6 pattern, validated by blocks, and automatically synced to tpmjs.com. + +## Workflow + +1. Define the tool block in `packages/tools/official/blocks.yml` +2. Create the tool package directory +3. Implement the tool using AI SDK v6 `tool()` + `jsonSchema()` +4. Validate with `pnpm blocks run ` +5. Build and publish to npm + +## Step 1: Define in blocks.yml + +Add to the `blocks:` section of `packages/tools/official/blocks.yml`: + +```yaml +blocks: + category.toolName: + type: utility + description: "LLM-friendly description of what the tool does" + path: "tool-directory-name" + domain_rules: + - id: rule_name + description: "What this implementation must do" + inputs: + - name: inputName + type: string + description: "Description for LLMs" + - name: optionalInput + type: number + optional: true + description: "Optional parameter" + outputs: + - name: result + type: ResultType + description: "What the tool returns" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] +``` + +**Category prefix** (before the dot): `research`, `web`, `data`, `documentation`, `engineering`, `security`, `statistics`, `ops`, `agent`, `sandbox`, `utilities`, `html`, `compliance`, `finance`, `legal`, `hr`, `marketing`, `cx`, `edu`, `sales`. + +For domain entities and quality measures, see [references/domain.md](references/domain.md). + +## Step 2: Create Package Directory + +Create `packages/tools/official//`: + +``` +/ +├── package.json +├── tsconfig.json +├── tsup.config.ts +├── README.md +└── src/ + └── index.ts +``` + +**package.json:** +```json +{ + "name": "@tpmjs/official-", + "version": "0.1.0", + "description": "Short description", + "type": "module", + "keywords": ["tpmjs", "", "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" + }, + "dependencies": { + "ai": "6.0.49" + }, + "publishConfig": { "access": "public" }, + "repository": { + "type": "git", + "url": "https://github.com/tpmjs/tpmjs.git", + "directory": "packages/tools/official/" + }, + "homepage": "https://tpmjs.com", + "license": "MIT", + "tpmjs": { + "category": "", + "frameworks": ["vercel-ai"], + "tools": [ + { + "name": "toolName", + "description": "Clear description (20+ chars)." + } + ] + } +} +``` + +**tsconfig.json:** +```json +{ + "extends": "@tpmjs/tsconfig/react-library.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} +``` + +**tsup.config.ts:** +```typescript +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, + sourcemap: true, + target: 'es2022', +}); +``` + +## Step 3: Implement the Tool + +Every tool follows this AI SDK v6 pattern in `src/index.ts`: + +```typescript +import { jsonSchema, tool } from 'ai'; + +interface MyToolInput { + param1: string; + param2?: number; +} + +export interface MyToolResult { + data: string; + metadata: { processedAt: string }; +} + +export const myTool = tool({ + description: 'Clear LLM-friendly description of what this tool does.', + parameters: jsonSchema({ + type: 'object', + properties: { + param1: { + type: 'string', + description: 'What param1 is for', + }, + param2: { + type: 'number', + description: 'Optional: what param2 is for', + }, + }, + required: ['param1'], + additionalProperties: false, + }), + execute: async (input): Promise => { + if (!input.param1) { + throw new Error('param1 is required and must be non-empty'); + } + + try { + const result = await processData(input.param1); + return { + data: result, + metadata: { processedAt: new Date().toISOString() }, + }; + } catch (error) { + throw new Error( + `Failed to process: ${error instanceof Error ? error.message : String(error)}` + ); + } + }, +}); + +export default myTool; +``` + +**Hard rules:** +- No stubs, TODOs, or placeholders — every tool must be fully working +- Single-shot: one call in, one structured result out +- Validate inputs before processing +- Try-catch with descriptive errors including context +- `additionalProperties: false` on jsonSchema +- Description on every schema property +- Export as both named and default export +- Output interface must be exported + +### Multi-Tool Packages + +For packages with multiple tools, add root-level files: + +**block.ts:** +```typescript +import { toolA, toolB } from './src/index.js'; +export const block = { name: 'package-name', tools: { toolA, toolB } }; +export default block; +``` + +**index.ts (root):** +```typescript +export * from './src/index.js'; +export { default } from './src/index.js'; +``` + +Each tool gets its own entry in blocks.yml (same `path`) and in `tpmjs.tools` array. + +## Step 4: Validate + +The blocks CLI domain validator requires an OpenAI API key. Source it from `.env.local` before running: + +```bash +cd packages/tools/official + +# Load the OpenAI API key for domain validation +source ../../../.env.local +export OPENAI_API_KEY + +pnpm blocks run # Validate (schema → shape → domain) +pnpm blocks run --force # Force full validation (skip cache) +pnpm blocks run --json # JSON output for debugging +pnpm blocks run --all # Validate all tools +``` + +**Common errors:** +- `Tool "X" not found in exports` → Export name must match blocks.yml +- `Required file not found` → Check package root has all required files +- `invalid tpmjs field` → Category must be valid, tools array required + +## Step 5: Build and Publish + +```bash +pnpm --filter=@tpmjs/official- build +cd packages/tools/official/ && npm publish --access public +``` + +The tool syncs to tpmjs.com automatically via the changes feed (every 2 min) and keyword search (every 15 min). To trigger immediately: + +```bash +source apps/web/.env.local +curl -X POST https://tpmjs.com/api/sync/keyword \ + -H "Authorization: Bearer $CRON_SECRET" +``` + +## README Template + +Every tool needs a README: + +```markdown +# @tpmjs/official- + +Short description. + +## Installation + +npm install @tpmjs/official- + +## Usage + +\`\`\`typescript +import { myTool } from '@tpmjs/official-'; + +const result = await myTool.execute({ param1: 'example' }); +\`\`\` + +## Parameters + +| Name | Type | Required | Description | +|--------|--------|----------|--------------------| +| param1 | string | Yes | What param1 is for | + +## Output + +| Field | Type | Description | +|-------|--------|----------------------| +| data | string | The processed result | + +## License + +MIT +``` diff --git a/.claude/skills/tpmjs-tool-creator/references/domain.md b/.claude/skills/tpmjs-tool-creator/references/domain.md new file mode 100644 index 0000000..db62b0f --- /dev/null +++ b/.claude/skills/tpmjs-tool-creator/references/domain.md @@ -0,0 +1,58 @@ +# Domain Reference + +## Entities + +Reusable output types defined in blocks.yml. Reference these in your tool's output `type` field. + +| Entity | Fields | +|--------|--------| +| url | href, domain, protocol, path, query, fragment | +| webpage | url, title, html, text, metadata | +| text_content | raw, sentences, paragraphs, wordCount | +| claim | statement, confidence, needsCitation, category | +| timeline | events, dateRange, gaps, eventCount | +| evidence | source, type, strength, relevance | +| summary | text, keyPoints, length, compressionRatio | +| sentiment | score, label, confidence, aspects | +| entity | name, type, mentions, context | +| relationship | source, target, type, strength | +| pattern | name, frequency, examples, significance | +| anomaly | description, severity, context, recommendation | +| metric | name, value, unit, trend | +| comparison | items, criteria, rankings, analysis | +| recommendation | action, priority, rationale, impact | +| risk | description, likelihood, impact, mitigation | +| code_snippet | language, code, explanation, complexity | +| api_endpoint | method, path, parameters, response | +| data_schema | fields, types, constraints, relationships | +| workflow_step | action, input, output, conditions | + +## Quality Measures + +Reference these in your output's `measures` array. + +| Measure | Severity | What it checks | +|---------|----------|---------------| +| working_implementation | error | No TODOs, stubs, or placeholders. Returns actual computed values. | +| valid_output_structure | error | Returns object matching declared interface. All required fields present. Arrays never undefined. | +| proper_error_handling | error | Throws descriptive Error with context. Validates inputs. Catches external API errors. | +| ai_sdk_compliance | error | Uses `tool()` + `jsonSchema()` from 'ai'. Clear description. Every property has description. | +| npm_publishable | error | Valid package.json with tpmjs field. Named + default exports. Proper types. Semver version. | +| readme_documentation | error | README exists. Describes tool. Usage example. Documents inputs/outputs. | +| deterministic_output | warning | Same input produces same output (where applicable). | +| minimal_dependencies | warning | Uses stable, well-maintained packages. Avoids unnecessary deps. | + +## Domain Rules + +Common domain rule categories for the `domain_rules` field in blocks.yml: + +- **Core implementation**: working code, proper types, error handling +- **Web & fetch**: URL validation, content extraction, timeout handling +- **Document generation**: format compliance, template rendering +- **Data transformation**: schema validation, type coercion, encoding +- **Engineering/code analysis**: AST parsing, complexity metrics +- **Security & compliance**: input sanitization, safe execution +- **Statistical rigor**: numerical accuracy, proper rounding +- **Workflow/recipe**: step sequencing, state management + +Define custom rules specific to your tool's requirements. Each rule needs an `id` and `description`. diff --git a/.github/workflows/build-omega-mac.yml b/.github/workflows/build-omega-mac.yml new file mode 100644 index 0000000..f987110 --- /dev/null +++ b/.github/workflows/build-omega-mac.yml @@ -0,0 +1,72 @@ +name: Build Omega Mac + +on: + workflow_dispatch: + +jobs: + build: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + + - name: Resolve dependencies + working-directory: apps/omega-mac + run: swift package resolve + + - name: Build release + working-directory: apps/omega-mac + run: swift build -c release + + - name: Package .app bundle + working-directory: apps/omega-mac + run: | + mkdir -p OmegaMac.app/Contents/MacOS + mkdir -p OmegaMac.app/Contents/Resources + cp .build/release/OmegaMac OmegaMac.app/Contents/MacOS/OmegaMac + + cat > OmegaMac.app/Contents/Info.plist << 'PLIST' + + + + + CFBundleExecutable + OmegaMac + CFBundleIdentifier + com.tpmjs.omega-mac + CFBundleName + Omega + CFBundleDisplayName + Omega + CFBundleVersion + 1 + CFBundleShortVersionString + 1.0.0 + CFBundlePackageType + APPL + LSMinimumSystemVersion + 14.0 + NSHighResolutionCapable + + com.apple.security.app-sandbox + + com.apple.security.network.client + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + + PLIST + + codesign --force --sign - OmegaMac.app + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: OmegaMac + path: apps/omega-mac/OmegaMac.app diff --git a/.github/workflows/sync-enrich.yml b/.github/workflows/sync-enrich.yml new file mode 100644 index 0000000..bef72fc --- /dev/null +++ b/.github/workflows/sync-enrich.yml @@ -0,0 +1,114 @@ +name: Sync Tool Enrichment + +on: + schedule: + # Run every 2 minutes + - cron: '*/2 * * * *' + workflow_dispatch: + +jobs: + sync-enrich: + runs-on: ubuntu-latest + steps: + - name: Trigger enrichment sync + id: sync + run: | + # Call the sync API and capture response + response=$(curl -X POST "${{ secrets.VERCEL_PRODUCTION_URL }}/api/sync/enrich" \ + -H "Authorization: Bearer ${{ secrets.CRON_SECRET }}" \ + -H "Content-Type: application/json" \ + -f -s -S) + + echo "Response: $response" + + # Extract data using jq + enriched=$(echo "$response" | jq -r '.data.enriched') + discovered=$(echo "$response" | jq -r '.data.discovered') + skipped=$(echo "$response" | jq -r '.data.skipped') + errors=$(echo "$response" | jq -r '.data.errors') + durationMs=$(echo "$response" | jq -r '.data.durationMs') + + # Extract and display error messages + errorMessages=$(echo "$response" | jq -r '.data.errorMessages[]?' 2>/dev/null || echo "") + + if [ -n "$errorMessages" ]; then + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "⚠️ ENRICHMENT ERRORS ($errors total):" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "$response" | jq -r '.data.errorMessages[]?' | while IFS= read -r error; do + echo " • $error" + done + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + fi + + # Set outputs for Discord notification + echo "enriched=$enriched" >> $GITHUB_OUTPUT + echo "discovered=$discovered" >> $GITHUB_OUTPUT + echo "skipped=$skipped" >> $GITHUB_OUTPUT + echo "errors=$errors" >> $GITHUB_OUTPUT + echo "durationMs=$durationMs" >> $GITHUB_OUTPUT + + # Store error messages for Discord (first 3, truncated) + if [ "$errors" -gt 0 ]; then + errorSummary=$(echo "$response" | jq -r '.data.errorMessages[0:3]? | join("\n• ")' 2>/dev/null || echo "") + if [ -n "$errorSummary" ]; then + echo "• $errorSummary" > /tmp/error_summary.txt + fi + fi + + # Determine status emoji + if [ "$errors" -gt 0 ]; then + echo "status_emoji=⚠️" >> $GITHUB_OUTPUT + echo "status_color=16776960" >> $GITHUB_OUTPUT # Yellow + else + echo "status_emoji=✅" >> $GITHUB_OUTPUT + echo "status_color=5763719" >> $GITHUB_OUTPUT # Green + fi + + - name: Send Discord notification + if: always() + run: | + # Format duration + duration_sec=$(echo "scale=2; ${{ steps.sync.outputs.durationMs }} / 1000" | bc) + + # Build Discord payload using jq for proper JSON escaping + error_text="" + + if [ -f /tmp/error_summary.txt ] && [ ${{ steps.sync.outputs.errors }} -gt 0 ]; then + error_text=$(cat /tmp/error_summary.txt | head -c 800) + fi + + # Build fields array dynamically + base_fields='[ + { "name": "🔧 Enriched", "value": "${{ steps.sync.outputs.enriched }}", "inline": true }, + { "name": "🔍 Discovered", "value": "${{ steps.sync.outputs.discovered }}", "inline": true }, + { "name": "⏭️ Skipped", "value": "${{ steps.sync.outputs.skipped }}", "inline": true }, + { "name": "❌ Errors", "value": "${{ steps.sync.outputs.errors }}", "inline": true }, + { "name": "⏱️ Duration", "value": "'"${duration_sec}s"'", "inline": true }, + { "name": "🔗 Run", "value": "[View Logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})", "inline": true } + ]' + + # Create payload with dynamic fields + payload=$(jq -n \ + --arg title "${{ steps.sync.outputs.status_emoji }} Tool Enrichment Sync" \ + --argjson color ${{ steps.sync.outputs.status_color }} \ + --argjson baseFields "$base_fields" \ + --arg error_text "$error_text" \ + --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ + ' + { + embeds: [{ + title: $title, + color: $color, + fields: ( + $baseFields + + (if $error_text != "" then [{ name: "🔍 Error Details", value: ("```\n" + $error_text + "\n```"), inline: false }] else [] end) + ), + timestamp: $timestamp + }] + }') + + # Send to Discord + curl -X POST "${{ secrets.DISCORD_WEBHOOK }}" \ + -H "Content-Type: application/json" \ + -d "$payload" diff --git a/.gitignore b/.gitignore index c32553e..c826f12 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,22 @@ secrets.json # ide .idea +.agent/ +.agents/ +.continue/ +.cursor/ +.windsurf/ + +# temporary analysis docs +COMPREHENSIVE_ANALYSIS.md +PLAN.md +REGISTRY_TOOLS_ANALYSIS.md +USER_ACCOUNT_ANALYSIS_*.md +*.skill + +# symlinked skill dirs (source is .agents/ which is gitignored) +/skills/ +.claude/skills/skill-creator # storybook storybook-static diff --git a/CLAUDE.md b/CLAUDE.md index 6995eac..5af27cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,1170 +1,144 @@ +## Project Overview -## Monorepo Setup +Turborepo monorepo. pnpm workspaces. Next.js 16 App Router (`apps/web`). PostgreSQL via Prisma (`packages/db`). Deployed on Vercel. Database on Neon (via Railway for some services). -This project uses a Turborepo monorepo architecture with the following structure: +## Architecture Rules -### Packages +1. **Use `@tpmjs/ui` components** — never raw HTML `