From 234548c6b24c57d3dd7cff6d1063efb0131e0d6e Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Sun, 25 Jan 2026 03:34:27 +1000 Subject: [PATCH] chore: sync remaining RealSkills updates and config changes - Update skills route, activity feed, and stats components - Update skills embedding and response generator modules - Update Omega conversation messages route - Update PRD documentation - Refresh CLI oclif manifest - Update gitignore and package configs --- .gitignore | 3 + apps/web/.gitignore | 1 + apps/web/next-env.d.ts | 2 +- apps/web/package.json | 3 +- .../collections/[slug]/skills/route.ts | 65 +- .../conversations/[id]/messages/route.ts | 20 +- apps/web/src/app/api/skills/activity/route.ts | 22 +- apps/web/src/app/api/skills/stats/route.ts | 22 +- apps/web/src/app/docs/skills/page.tsx | 23 +- .../components/skills/SkillsActivityFeed.tsx | 21 +- .../src/components/skills/SkillsSection.tsx | 16 +- .../web/src/components/skills/SkillsStats.tsx | 15 +- apps/web/src/lib/ai/skills-embedding.ts | 23 +- apps/web/src/lib/ai/skills-graph-updater.ts | 27 +- .../src/lib/ai/skills-response-generator.ts | 7 +- apps/web/src/lib/ai/skills-seeder.ts | 14 +- docs/PRD-omega.md | 920 ++++++++++++++---- packages/cli/oclif.manifest.json | 872 +++++++++++------ 18 files changed, 1397 insertions(+), 679 deletions(-) diff --git a/.gitignore b/.gitignore index 64877b4..2d7c8f1 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,9 @@ secrets.json # turbo .turbo +# ai sdk devtools +.devtools + # typescript *.tsbuildinfo diff --git a/apps/web/.gitignore b/apps/web/.gitignore index c8a7336..1ef07c5 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -1,2 +1,3 @@ .vercel .env*.local +.devtools diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index d6127d4..d855a01 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@ai-sdk/anthropic": "^3.0.9", + "@ai-sdk/devtools": "^0.0.8", "@ai-sdk/google": "^3.0.6", "@ai-sdk/groq": "^3.0.4", "@ai-sdk/mistral": "^3.0.5", @@ -61,8 +62,8 @@ "remark-gfm": "^4.0.1", "resend": "^6.7.0", "sonner": "^2.0.7", - "swr": "^2.2.5", "streamdown": "^1.6.11", + "swr": "^2.2.5", "three": "^0.182.0", "zod": "^4.3.5" }, diff --git a/apps/web/src/app/(profile)/[username]/collections/[slug]/skills/route.ts b/apps/web/src/app/(profile)/[username]/collections/[slug]/skills/route.ts index bdffd96..31ff032 100644 --- a/apps/web/src/app/(profile)/[username]/collections/[slug]/skills/route.ts +++ b/apps/web/src/app/(profile)/[username]/collections/[slug]/skills/route.ts @@ -8,23 +8,23 @@ * POST - Ask a question (RAG + LLM response) */ -import { createHash } from 'crypto'; import { prisma } from '@tpmjs/db'; -import { NextResponse, type NextRequest } from 'next/server'; +import { createHash } from 'crypto'; +import { type NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; import { checkQuestionSimilarity } from '~/lib/ai/skills-embedding'; -import { updateSkillGraph, getCollectionSkillsSummary } from '~/lib/ai/skills-graph-updater'; +import { getCollectionSkillsSummary, updateSkillGraph } from '~/lib/ai/skills-graph-updater'; import { - generateSkillResponse, - generateFollowupSuggestions, - calculateConfidence, type CollectionContext, + calculateConfidence, + generateFollowupSuggestions, + generateSkillResponse, } from '~/lib/ai/skills-response-generator'; import { - seedCollectionSkills, - getSeedingStatus, type CollectionWithTools, + getSeedingStatus, + seedCollectionSkills, } from '~/lib/ai/skills-seeder'; export const runtime = 'nodejs'; @@ -51,10 +51,7 @@ const SESSION_EXPIRY_MS = 24 * 60 * 60 * 1000; * Hash agent identity for anonymization */ function hashAgentIdentity(ip: string, userAgent: string): string { - return createHash('sha256') - .update(`${ip}:${userAgent}`) - .digest('hex') - .slice(0, 16); + return createHash('sha256').update(`${ip}:${userAgent}`).digest('hex').slice(0, 16); } /** @@ -70,10 +67,7 @@ async function loadCollection( }); if (!user || !user.username) { - return NextResponse.json( - { success: false, error: 'User not found' }, - { status: 404 } - ); + return NextResponse.json({ success: false, error: 'User not found' }, { status: 404 }); } const collection = await prisma.collection.findFirst({ @@ -96,10 +90,7 @@ async function loadCollection( }); if (!collection) { - return NextResponse.json( - { success: false, error: 'Collection not found' }, - { status: 404 } - ); + return NextResponse.json({ success: false, error: 'Collection not found' }, { status: 404 }); } if (!collection.isPublic) { @@ -160,9 +151,7 @@ export async function GET(_request: NextRequest, context: RouteContext) { try { const { username: rawUsername, slug } = await context.params; - const username = rawUsername.startsWith('@') - ? rawUsername.slice(1) - : rawUsername; + const username = rawUsername.startsWith('@') ? rawUsername.slice(1) : rawUsername; // Load collection const result = await loadCollection(username, slug); @@ -225,19 +214,14 @@ export async function POST(request: NextRequest, context: RouteContext) { try { const { username: rawUsername, slug } = await context.params; - const username = rawUsername.startsWith('@') - ? rawUsername.slice(1) - : rawUsername; + const username = rawUsername.startsWith('@') ? rawUsername.slice(1) : rawUsername; // Parse and validate request body let body: unknown; try { body = await request.json(); } catch { - return NextResponse.json( - { success: false, error: 'Invalid JSON body' }, - { status: 400 } - ); + return NextResponse.json({ success: false, error: 'Invalid JSON body' }, { status: 400 }); } const parseResult = PostRequestSchema.safeParse(body); @@ -252,8 +236,7 @@ export async function POST(request: NextRequest, context: RouteContext) { ); } - const { question, sessionId, agentName, context: questionContext, tags } = - parseResult.data; + const { question, sessionId, agentName, context: questionContext, tags } = parseResult.data; // Load collection const result = await loadCollection(username, slug); @@ -269,10 +252,7 @@ export async function POST(request: NextRequest, context: RouteContext) { const agentHash = hashAgentIdentity(ip, userAgent); // Check for similarity / cache hit - const similarityResult = await checkQuestionSimilarity( - question, - collection.id - ); + const similarityResult = await checkQuestionSimilarity(question, collection.id); // If very similar question exists (>95%), return cached answer if (similarityResult.isCacheHit && similarityResult.cachedAnswer) { @@ -302,8 +282,7 @@ export async function POST(request: NextRequest, context: RouteContext) { }; // Get session history if session exists - let sessionHistory: Array<{ role: 'user' | 'assistant'; content: string }> = - []; + let sessionHistory: Array<{ role: 'user' | 'assistant'; content: string }> = []; let activeSessionId = sessionId; if (sessionId) { @@ -319,9 +298,7 @@ export async function POST(request: NextRequest, context: RouteContext) { } // Generate response - const fullQuestion = questionContext - ? `${question}\n\nContext: ${questionContext}` - : question; + const fullQuestion = questionContext ? `${question}\n\nContext: ${questionContext}` : question; const { answer, tokensUsed } = await generateSkillResponse({ question: fullQuestion, @@ -401,11 +378,7 @@ export async function POST(request: NextRequest, context: RouteContext) { // Generate follow-up suggestions (optional, don't block) let suggestedFollowups: string[] = []; try { - suggestedFollowups = await generateFollowupSuggestions( - question, - answer, - collection.name - ); + suggestedFollowups = await generateFollowupSuggestions(question, answer, collection.name); } catch { // Ignore errors for followups } diff --git a/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts b/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts index 81c7c46..28866c0 100644 --- a/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts +++ b/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts @@ -12,7 +12,8 @@ import { Prisma, prisma } from '@tpmjs/db'; import { registryExecuteTool } from '@tpmjs/registry-execute'; import { registrySearchTool } from '@tpmjs/registry-search'; -import { jsonSchema, type ModelMessage } from 'ai'; +import { jsonSchema, wrapLanguageModel, type ModelMessage } from 'ai'; +import { devToolsMiddleware } from '@ai-sdk/devtools'; import { type NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; import { authenticateRequest } from '~/lib/api-keys/middleware'; @@ -20,6 +21,12 @@ import { decryptApiKey } from '~/lib/crypto/api-keys'; import { buildSystemPrompt } from '~/lib/omega/system-prompt'; import { checkRateLimit, type RateLimitConfig } from '~/lib/rate-limit'; +// Initialize devtools middleware once at module level (only used in development) +const devtools = process.env.NODE_ENV === 'development' ? devToolsMiddleware() : null; +if (devtools) { + console.log('[Omega] AI SDK DevTools middleware initialized'); +} + /** * Warning about missing environment variables */ @@ -556,7 +563,16 @@ Remember: Your value is in EXECUTING tools to get real results, not just describ } const openai = createOpenAI({ apiKey }); - const model = openai('gpt-4.1-mini'); + const baseModel = openai('gpt-4.1-mini'); + + // Wrap with devtools middleware in development + const model = devtools + ? wrapLanguageModel({ model: baseModel, middleware: devtools }) + : baseModel; + + if (devtools) { + console.log('[Omega] Model wrapped with DevTools middleware'); + } // Create SSE stream const stream = new ReadableStream({ diff --git a/apps/web/src/app/api/skills/activity/route.ts b/apps/web/src/app/api/skills/activity/route.ts index 97ffcf8..de971f7 100644 --- a/apps/web/src/app/api/skills/activity/route.ts +++ b/apps/web/src/app/api/skills/activity/route.ts @@ -5,7 +5,7 @@ */ import { prisma } from '@tpmjs/db'; -import { NextResponse, type NextRequest } from 'next/server'; +import { type NextRequest, NextResponse } from 'next/server'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -17,10 +17,7 @@ export async function GET(request: NextRequest) { const limit = Math.min(50, Math.max(1, parseInt(limitParam || '10', 10))); if (!collectionId) { - return NextResponse.json( - { error: 'collectionId is required' }, - { status: 400 } - ); + return NextResponse.json({ error: 'collectionId is required' }, { status: 400 }); } try { @@ -31,17 +28,11 @@ export async function GET(request: NextRequest) { }); if (!collection) { - return NextResponse.json( - { error: 'Collection not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'Collection not found' }, { status: 404 }); } if (!collection.isPublic) { - return NextResponse.json( - { error: 'Collection is not public' }, - { status: 403 } - ); + return NextResponse.json({ error: 'Collection is not public' }, { status: 403 }); } // Fetch recent questions with skill links (anonymized - no agent info) @@ -73,9 +64,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ questions }); } catch (error) { console.error('[Skills Activity Error]:', error); - return NextResponse.json( - { error: 'Failed to fetch activity' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to fetch activity' }, { status: 500 }); } } diff --git a/apps/web/src/app/api/skills/stats/route.ts b/apps/web/src/app/api/skills/stats/route.ts index af09da1..60ff202 100644 --- a/apps/web/src/app/api/skills/stats/route.ts +++ b/apps/web/src/app/api/skills/stats/route.ts @@ -5,7 +5,7 @@ */ import { prisma } from '@tpmjs/db'; -import { NextResponse, type NextRequest } from 'next/server'; +import { type NextRequest, NextResponse } from 'next/server'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -15,10 +15,7 @@ export async function GET(request: NextRequest) { const collectionId = searchParams.get('collectionId'); if (!collectionId) { - return NextResponse.json( - { error: 'collectionId is required' }, - { status: 400 } - ); + return NextResponse.json({ error: 'collectionId is required' }, { status: 400 }); } try { @@ -29,17 +26,11 @@ export async function GET(request: NextRequest) { }); if (!collection) { - return NextResponse.json( - { error: 'Collection not found' }, - { status: 404 } - ); + return NextResponse.json({ error: 'Collection not found' }, { status: 404 }); } if (!collection.isPublic) { - return NextResponse.json( - { error: 'Collection is not public' }, - { status: 403 } - ); + return NextResponse.json({ error: 'Collection is not public' }, { status: 403 }); } // Fetch stats in parallel @@ -65,9 +56,6 @@ export async function GET(request: NextRequest) { }); } catch (error) { console.error('[Skills Stats Error]:', error); - return NextResponse.json( - { error: 'Failed to fetch stats' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to fetch stats' }, { status: 500 }); } } diff --git a/apps/web/src/app/docs/skills/page.tsx b/apps/web/src/app/docs/skills/page.tsx index 01fe823..1f579a2 100644 --- a/apps/web/src/app/docs/skills/page.tsx +++ b/apps/web/src/app/docs/skills/page.tsx @@ -251,11 +251,7 @@ export default function SkillsPage(): React.ReactElement { />

Example Response

- + @@ -353,7 +349,9 @@ const followUp = await askSkills(

Tips for Better Responses

  • • Be specific about what you're trying to accomplish
  • -
  • • Include relevant context in the context field
  • +
  • + • Include relevant context in the context field +
  • • Use tags to hint at the problem domain
  • • Use sessions for related follow-up questions
@@ -413,8 +411,8 @@ const followUp = await askSkills(

- When a collection's skills endpoint is accessed for the first time, it automatically - seeds with synthetic questions generated from: + When a collection's skills endpoint is accessed for the first time, it + automatically seeds with synthetic questions generated from:

    @@ -424,8 +422,8 @@ const followUp = await askSkills(

- This ensures the endpoint is useful immediately, even before any real agent interactions. - Seeding typically adds 10-15 synthetic Q&A pairs. + This ensures the endpoint is useful immediately, even before any real agent + interactions. Seeding typically adds 10-15 synthetic Q&A pairs.

@@ -490,7 +488,10 @@ const followUp = await askSkills(

  • - + Collections API →

    diff --git a/apps/web/src/components/skills/SkillsActivityFeed.tsx b/apps/web/src/components/skills/SkillsActivityFeed.tsx index 9eb6824..120d4ae 100644 --- a/apps/web/src/components/skills/SkillsActivityFeed.tsx +++ b/apps/web/src/components/skills/SkillsActivityFeed.tsx @@ -1,15 +1,10 @@ 'use client'; import { Badge } from '@tpmjs/ui/Badge/Badge'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@tpmjs/ui/Card/Card'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card'; import { Icon } from '@tpmjs/ui/Icon/Icon'; import { Skeleton } from '@tpmjs/ui/Skeleton/Skeleton'; + // Simple relative time formatter function formatRelativeTime(date: Date): string { const now = Date.now(); @@ -24,6 +19,7 @@ function formatRelativeTime(date: Date): string { if (minutes > 0) return `${minutes}m ago`; return 'just now'; } + import { useEffect, useState } from 'react'; interface SkillQuestion { @@ -104,11 +100,7 @@ export function SkillsActivityFeed({ return ( - +

    No questions yet. Be the first to ask!

    @@ -127,10 +119,7 @@ export function SkillsActivityFeed({ {q.question}
    - = 0.7 ? 'success' : 'secondary'} - size="sm" - > + = 0.7 ? 'success' : 'secondary'} size="sm"> {Math.round(q.confidence * 100)}%
    diff --git a/apps/web/src/components/skills/SkillsSection.tsx b/apps/web/src/components/skills/SkillsSection.tsx index fdc6a13..51007bb 100644 --- a/apps/web/src/components/skills/SkillsSection.tsx +++ b/apps/web/src/components/skills/SkillsSection.tsx @@ -67,11 +67,7 @@ curl -X POST "${skillsUrl}" \\
  • - @@ -122,9 +118,7 @@ curl -X POST "${skillsUrl}" \\ {/* Activity Feed Column */}
    -

    - Recent Questions -

    +

    Recent Questions

    @@ -144,11 +138,7 @@ curl -X POST "${skillsUrl}" \\ Every question helps improve future responses for all agents.

    - diff --git a/apps/web/src/components/skills/SkillsStats.tsx b/apps/web/src/components/skills/SkillsStats.tsx index 0d3e93e..7a10710 100644 --- a/apps/web/src/components/skills/SkillsStats.tsx +++ b/apps/web/src/components/skills/SkillsStats.tsx @@ -1,12 +1,7 @@ 'use client'; import { Badge } from '@tpmjs/ui/Badge/Badge'; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from '@tpmjs/ui/Card/Card'; +import { Card, CardContent, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card'; import { Icon } from '@tpmjs/ui/Icon/Icon'; import { Skeleton } from '@tpmjs/ui/Skeleton/Skeleton'; import { useEffect, useState } from 'react'; @@ -25,9 +20,7 @@ interface SkillsStatsProps { collectionId: string; } -export function SkillsStats({ - collectionId, -}: SkillsStatsProps): React.ReactElement | null { +export function SkillsStats({ collectionId }: SkillsStatsProps): React.ReactElement | null { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -35,9 +28,7 @@ export function SkillsStats({ useEffect(() => { async function fetchStats() { try { - const response = await fetch( - `/api/skills/stats?collectionId=${collectionId}` - ); + const response = await fetch(`/api/skills/stats?collectionId=${collectionId}`); if (!response.ok) { throw new Error('Failed to fetch stats'); } diff --git a/apps/web/src/lib/ai/skills-embedding.ts b/apps/web/src/lib/ai/skills-embedding.ts index 35abf52..e0e569b 100644 --- a/apps/web/src/lib/ai/skills-embedding.ts +++ b/apps/web/src/lib/ai/skills-embedding.ts @@ -30,9 +30,7 @@ export async function embedQuestion(text: string): Promise { */ export function cosineSimilarity(a: number[], b: number[]): number { if (a.length !== b.length) { - throw new Error( - `Vector dimension mismatch: ${a.length} vs ${b.length}` - ); + throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`); } let dotProduct = 0; @@ -73,11 +71,7 @@ export async function findSimilarQuestions( excludeId?: string; } = {} ): Promise { - const { - threshold = DEFAULT_SIMILARITY_THRESHOLD, - limit = 5, - excludeId, - } = options; + const { threshold = DEFAULT_SIMILARITY_THRESHOLD, limit = 5, excludeId } = options; // Fetch all questions for this collection const questions = await prisma.skillQuestion.findMany({ @@ -115,9 +109,7 @@ export async function findSimilarQuestions( } // Sort by similarity descending and limit results - return similar - .sort((a, b) => b.similarity - a.similarity) - .slice(0, limit); + return similar.sort((a, b) => b.similarity - a.similarity).slice(0, limit); } /** @@ -191,11 +183,10 @@ export async function checkQuestionSimilarity( } // Find similar questions for RAG context - const similarQuestions = await findSimilarQuestions( - embedding, - collectionId, - { threshold: DEFAULT_SIMILARITY_THRESHOLD, limit: 5 } - ); + const similarQuestions = await findSimilarQuestions(embedding, collectionId, { + threshold: DEFAULT_SIMILARITY_THRESHOLD, + limit: 5, + }); return { isCacheHit: false, diff --git a/apps/web/src/lib/ai/skills-graph-updater.ts b/apps/web/src/lib/ai/skills-graph-updater.ts index 4d5ff80..fb484ad 100644 --- a/apps/web/src/lib/ai/skills-graph-updater.ts +++ b/apps/web/src/lib/ai/skills-graph-updater.ts @@ -13,10 +13,7 @@ import type { Skill, Tool } from '@prisma/client'; import { prisma } from '@tpmjs/db'; import { generateObject } from 'ai'; import { z } from 'zod'; -import { - cosineSimilarity, - embedQuestion, -} from './skills-embedding'; +import { cosineSimilarity, embedQuestion } from './skills-embedding'; const SKILL_MATCH_THRESHOLD = 0.75; @@ -50,12 +47,8 @@ export async function extractSkillsFromQuestion( schema: z.object({ skills: z.array( z.object({ - name: z - .string() - .describe('Short skill name (2-5 words), e.g., "API error handling"'), - description: z - .string() - .describe('One sentence describing what this skill enables'), + name: z.string().describe('Short skill name (2-5 words), e.g., "API error handling"'), + description: z.string().describe('One sentence describing what this skill enables'), }) ), }), @@ -228,11 +221,7 @@ export async function updateSkillGraph(params: { // 2. Get or create skill nodes and link to question for (const extracted of extractedSkills) { try { - const skill = await getOrCreateSkill( - collectionId, - extracted.name, - extracted.description - ); + const skill = await getOrCreateSkill(collectionId, extracted.name, extracted.description); // Link question to skill await prisma.skillQuestionSkill.upsert({ @@ -314,9 +303,7 @@ export async function updateSkillGraph(params: { /** * Get skill summary for a collection */ -export async function getCollectionSkillsSummary( - collectionId: string -): Promise<{ +export async function getCollectionSkillsSummary(collectionId: string): Promise<{ totalQuestions: number; totalSkills: number; topSkills: Array<{ @@ -351,9 +338,7 @@ export async function getCollectionSkillsSummary( * Recalculate confidence scores for all skills in a collection * (Useful for batch updates or maintenance) */ -export async function recalculateSkillConfidence( - collectionId: string -): Promise { +export async function recalculateSkillConfidence(collectionId: string): Promise { const skills = await prisma.skill.findMany({ where: { collectionId }, include: { diff --git a/apps/web/src/lib/ai/skills-response-generator.ts b/apps/web/src/lib/ai/skills-response-generator.ts index c562521..8b57222 100644 --- a/apps/web/src/lib/ai/skills-response-generator.ts +++ b/apps/web/src/lib/ai/skills-response-generator.ts @@ -65,9 +65,7 @@ function buildSystemPrompt(params: GenerateResponseParams): string { // Optional tag hints const tagHints = - tags && tags.length > 0 - ? `\nThe user has tagged this question with: ${tags.join(', ')}` - : ''; + tags && tags.length > 0 ? `\nThe user has tagged this question with: ${tags.join(', ')}` : ''; return `You are a helpful assistant that answers questions about using the tools in the "${collection.name}" collection. @@ -202,8 +200,7 @@ export function calculateConfidence( // Boost for similar questions (RAG context) if (similarQuestions.length > 0) { const avgSimilarity = - similarQuestions.reduce((sum, q) => sum + q.similarity, 0) / - similarQuestions.length; + similarQuestions.reduce((sum, q) => sum + q.similarity, 0) / similarQuestions.length; confidence += avgSimilarity * 0.4; // Up to 0.4 boost } diff --git a/apps/web/src/lib/ai/skills-seeder.ts b/apps/web/src/lib/ai/skills-seeder.ts index 041cb44..e414961 100644 --- a/apps/web/src/lib/ai/skills-seeder.ts +++ b/apps/web/src/lib/ai/skills-seeder.ts @@ -14,11 +14,8 @@ import { prisma } from '@tpmjs/db'; import { generateObject } from 'ai'; import { z } from 'zod'; import { embedQuestion } from './skills-embedding'; -import { - generateSkillResponse, - type CollectionContext, -} from './skills-response-generator'; import { updateSkillGraph } from './skills-graph-updater'; +import { type CollectionContext, generateSkillResponse } from './skills-response-generator'; const SEED_BATCH_SIZE = 5; const SEEDING_LOCK_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes @@ -151,9 +148,7 @@ async function isSeeding(collectionId: string): Promise { * Seed a collection with synthetic questions * Returns true if seeding was performed, false if skipped */ -export async function seedCollectionSkills( - collection: CollectionWithTools -): Promise<{ +export async function seedCollectionSkills(collection: CollectionWithTools): Promise<{ seeded: boolean; questionsCreated: number; reason?: string; @@ -189,10 +184,7 @@ export async function seedCollectionSkills( // 2. Generate from tool descriptions if (collection.tools.length > 0) { - const toolQuestions = await generateQuestionsFromTools( - collection.tools, - 5 - ); + const toolQuestions = await generateQuestionsFromTools(collection.tools, 5); allQuestions.push(...toolQuestions); } diff --git a/docs/PRD-omega.md b/docs/PRD-omega.md index 309c75b..687686f 100644 --- a/docs/PRD-omega.md +++ b/docs/PRD-omega.md @@ -1,234 +1,606 @@ -# Omega PRD (Draft v0.1) +# Omega PRD (Draft v0.2) -Project name: Omega -Path: `/omega` -Owner: TPMJS +Project name: Omega +Path: `/omega` +Owner: TPMJS Status: Draft +Last Updated: 2026-01-22 + +--- ## 1) Executive Summary -Omega is a TPMJS side project that demonstrates what happens when a single agent can discover and execute tools from the entire TPM registry. The vision is playful and bold: "the last robot/AI you ever need" (Skynet energy, but friendly). Omega will ship as a dedicated chat experience at `/omega` and a public REST API that lets anyone start and continue conversations. The product must highlight tool discovery, selection, and execution at scale, while handling missing credentials gracefully. + +Omega is a TPMJS flagship feature that demonstrates what happens when a single agent can discover and execute tools from the entire TPM registry (1M+ tools). The vision is playful and bold: "the last robot/AI you ever need" (Skynet energy, but friendly). Omega will ship as a dedicated chat experience at `/omega` and a public REST API that lets anyone start and continue conversations. The product must highlight tool discovery, selection, and execution at scale, while handling missing credentials gracefully. + +**Key Differentiators:** +- Tool Search Tool pattern for 1M+ tool scale (85% context savings) +- Multi-user collaborative conversations with participant tracking +- Pause/resume execution flow for missing credentials +- Plan-then-execute architecture with user selection +- Full audit trail and observability + +--- ## 2) Problem Statement + TPMJS has powerful registry and tool execution capabilities, but the end-user experience does not yet show how these components combine into a single, continuous, tool-driven agent. Users need a simple way to: - Ask for outcomes, not tools -- Watch the agent discover the right tool(s) +- Watch the agent discover the right tool(s) from 1M+ options - Provide credentials only when needed - Continue execution without restarting +- Collaborate with others on complex tasks + +--- ## 3) Vision + Omega is a single agent with access to the TPM registry, able to choose and run the right tools to complete tasks. Users can chat or call an API and see a transparent, step-by-step tool workflow. It is a public showcase that can become a standalone product over time. +**Design Principles:** +1. **Least Agency** - Only grant the agent minimum autonomy required for the task +2. **Transparency** - Show every tool call, input, output, and reasoning +3. **Graceful Degradation** - Handle failures with alternatives and clear UX +4. **Context Efficiency** - Use Tool Search pattern to preserve context window + +--- + ## 4) Goals and Success Metrics + ### Goals -- Showcase tool discovery and execution at scale (1M+ tools). -- Deliver a great developer experience (DX) with simple API endpoints. -- Make credential handling safe, clear, and resumable. -- Provide a polished chat UI that is fast and transparent. +- Showcase tool discovery and execution at scale (1M+ tools) +- Deliver a great developer experience (DX) with simple API endpoints +- Make credential handling safe, clear, and resumable +- Provide a polished chat UI that is fast and transparent +- Enable collaborative conversations between multiple users ### Success Metrics (Early) -- Conversations created per week -- % conversations that use at least one tool -- Median time to first tool call -- Completion rate (user gets a result without abandoning) -- Avg. tool-call count per conversation -- User feedback: "felt like a real agent" +| Metric | Target | +|--------|--------| +| Conversations created per week | 100+ | +| % conversations that use at least one tool | >60% | +| Median time to first tool call | <10 seconds | +| Completion rate (user gets a result without abandoning) | >70% | +| Avg. tool-call count per conversation | 3-5 | +| User feedback: "felt like a real agent" | >80% positive | +| Tool execution success rate | >90% | +| P95 response latency (streaming start) | <2 seconds | + +--- ## 5) Non-Goals (v0) -- Multi-agent orchestration + +- Multi-agent orchestration (single agent with tool access) - Enterprise SSO or complex permission tiers - Guaranteed deterministic tool selection - Complex billing or metering +- File uploads (deferred to later phase) +- Cross-conversation memory (per-conversation only) +- Team/workspace credentials (per-user only) + +--- ## 6) Target Users and Use Cases + ### Personas -- Developers evaluating TPMJS or building on it (primary for v0) -- Power users exploring tool chains -- Curious users experimenting with AI + tools + +| Persona | Description | Primary Use Cases | +|---------|-------------|-------------------| +| **Developers** (primary for v0) | Evaluating TPMJS or building on it | API testing, tool discovery, integration prototyping | +| **Power Users** | Exploring tool chains and automation | Complex multi-step tasks, workflow automation | +| **Curious Users** | Experimenting with AI + tools | General exploration, learning capabilities | ### Example Use Cases - "Summarize a website and turn it into a checklist" - "Analyze a CSV and generate insights" - "Generate a proposal and send it via email" - "Search for a tool to do X and run it" +- "Execute this Python code and show me the results" +- "Find all APIs that can translate text and compare their pricing" + +--- ## 7) Product Requirements ### 7.1 Chat UI (`/omega`) -- Landing intro before first chat -- New conversation + +#### Core Features +- Landing intro before first chat with creative sample prompts +- New conversation creation - Message stream with agent responses -- Tool activity panel (tool name, inputs, outputs, status) -- Debug mode (raw JSON) and "view raw" toggles for tool I/O +- Tool activity panel (tool name, inputs, outputs, status, duration) +- Debug mode (raw JSON) with "view raw" toggles for tool I/O - Shareable conversation URL - Requires login (v0) -- Credential prompt when missing env vars + +#### Tool Execution Display +- Real-time tool status: pending → running → success/error +- Collapsible input/output JSON sections +- Duration display (ms/s) for each tool call +- Error messages with clear explanations +- Alternative tool suggestions on failure + +#### Credential Handling +- Credential prompt when missing env vars (inline modal) +- Show required keys with descriptions - Resume execution after credentials are provided -- Planning panel for proposed plans (only when requested or task is complex) -- Cancel running tool execution or full conversation run -- Multi-user conversations: shared links allow anyone with URL to send messages +- Stored credentials encrypted per-user in Omega settings + +#### Planning Panel +- Displayed only when requested or task is complex +- Shows tool order, purpose, expected outputs, confidence +- User can select a plan or ask for another +- Cost estimation before execution + +#### Multi-User Support +- Shared links allow anyone with URL to send messages - Show participant display names and Gravatar avatars (email-based) +- Clear attribution of who sent each message +- Only owner-stored credentials may run tools + +#### Additional Features +- Cancel running tool execution or full conversation run - Export conversation to JSON and Markdown - Lazy-load long outputs and message history (Virtuoso-based) - Auto-generate conversation title after first response - System status banner (registry + executor health) -- Empty state includes creative sample prompts - Light/dark theme supported (use TPMJS style guide) +#### Control Transfer Indicators +- "Agent is thinking..." +- "Agent is executing tools..." +- "Waiting for your input..." +- "Agent wants approval to proceed..." + ### 7.2 REST API -Endpoints (initial): -- `POST /api/omega/conversations` - Create a conversation. Returns conversation ID and optional title. -- `POST /api/omega/conversations/:id/messages` - Send a message and stream response (SSE). -- `GET /api/omega/conversations/:id` - Fetch conversation history. -- `POST /api/omega/conversations/:id/credentials` - Provide missing env vars and resume. -- `POST /api/omega/conversations/:id/plan` - Optional: generate plan(s) without execution. -- API access requires TPMJS API key (standard user keys). -- Shared conversations accept either logged-in session or TPMJS API key. + +#### Endpoints (Initial) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/omega/conversations` | Create a conversation. Returns conversation ID and optional title. | +| `POST` | `/api/omega/conversations/:id/messages` | Send a message and stream response (SSE). | +| `GET` | `/api/omega/conversations/:id` | Fetch conversation history with pagination. | +| `POST` | `/api/omega/conversations/:id/credentials` | Provide missing env vars and resume. | +| `POST` | `/api/omega/conversations/:id/plan` | Generate plan(s) without execution. | +| `POST` | `/api/omega/conversations/:id/cancel` | Cancel running execution. | +| `DELETE` | `/api/omega/conversations/:id` | Delete a conversation (owner only). | + +#### Authentication +- API access requires TPMJS API key (standard user keys) +- Shared conversations accept either logged-in session or TPMJS API key +- Separate scopes for read vs. execute operations + +#### SSE Event Types (Granular Streaming) +``` +message.delta # Partial message content +run.step.started # Step beginning +run.step.tool.started # Tool execution starting +run.step.tool.delta # Streaming tool output (if supported) +run.step.tool.completed # Tool finished +run.step.tool.failed # Tool error +run.step.completed # Step finished +run.requires_action # Needs user input (credentials, approval) +run.completed # Full run complete +run.failed # Run failed +``` ### 7.3 Tool Discovery and Execution -- Agent can search registry and execute tools. -- Default path: registry search + execute tools (entire registry). -- Registry execution uses TPMJS sandbox executor. -- Allow tool usage transparency in UI and API responses. -- Allow users to pin preferred tools for the agent to prioritize. -- Pinned tools are always included in the plan/tool set. -- Max tool calls per message: 200 (AI SDK `maxSteps`). -### 7.6 System Prompt and Safety Controls -- Omega ships with a comprehensive default system prompt. -- Users can extend/override with a custom prompt in Omega settings. -- Safe Mode toggle: require explicit approval before tool execution (human-in-the-loop). +#### Tool Search Tool Pattern +Since Omega targets 1M+ tools, implement semantic tool discovery: +- Tools aren't loaded into context by default +- Agent queries a "tool search" meta-tool first +- Only relevant tools (top 10-20) are loaded for execution +- Preserves ~85% more context for the actual task + +#### Execution Flow +- Default path: registry search + execute tools (entire registry) +- Registry execution uses TPMJS sandbox executor +- Allow tool usage transparency in UI and API responses +- Max tool calls per message: 200 (AI SDK `maxSteps`) + +#### Tool Pinning & Preferences +- Allow users to pin preferred tools for the agent to prioritize +- Pinned tools are always included in search results +- Support negative pins (blocked tools never suggested) +- Tool preference ordering for ties + +#### Parallel Execution +- Enable parallel tool execution when tools don't depend on each other +- Track dependencies between tool calls +- Merge results appropriately ### 7.4 Planning -- Agent can propose one or more plans before running tools. -- Plans show tool order, purpose, expected outputs, and confidence. -- User can select a plan or ask for another. -- Planning can be auto-run for complex tasks or when explicitly requested. + +#### Plan Generation +- Agent can propose one or more plans (default: 2-3) before running tools +- Plans show tool order, purpose, expected outputs, and confidence +- User can select a plan or ask for another +- Planning auto-runs for complex tasks or when explicitly requested + +#### Plan Contents +```typescript +interface ExecutionPlan { + steps: Array<{ + stepNumber: number; + toolName: string; + purpose: string; + expectedOutputs: string; + dependencies: number[]; // Step numbers this depends on + }>; + confidence: number; // 0-1 + reasoning: string; + estimatedTokens?: number; + estimatedCostCents?: number; + warnings?: string[]; +} +``` + +#### Re-Planning +- If tool results are unexpected, agent can propose revised plan +- Track replan count and reasons +- User can force re-plan at any step ### 7.5 Credentials / Missing Env Vars -- Detect missing env vars for tools. -- Pause execution when required env vars missing. -- Provide a credential prompt in UI (inline modal). -- Resume execution after credentials are supplied. -- Allow API callers to supply env vars in requests. -- Store credentials encrypted per-user in Omega settings. + +#### Detection & Pause +- Detect missing env vars for tools before execution +- Pause execution when required env vars missing +- Return structured `MISSING_ENV_VARS` response + +#### Credential Prompt +- Provide a credential prompt in UI (inline modal) +- Show key name, description, and where to get it +- Resume execution after credentials are supplied +- Allow API callers to supply env vars in requests + +#### Storage +- Store credentials encrypted per-user in Omega settings (AES-256-GCM) +- Scope credentials to Omega (separate from global user credentials) +- Support key rotation with grace period +- Never pass raw credentials to LLM context (use references) + +### 7.6 System Prompt and Safety Controls + +#### Default System Prompt +- Omega ships with a comprehensive default system prompt +- Versioned and updated independently of the product +- Users can extend/override with a custom prompt in Omega settings + +#### Safe Mode +- Toggle: require explicit approval before tool execution (human-in-the-loop) +- Per-tool approval settings for high-risk operations: + ```typescript + { + "requireApproval": { + "all": false, + "categories": ["destructive", "external_api"], + "tools": ["deleteFile", "sendEmail", "makePayment"] + } + } + ``` + +#### Proactiveness Slider (Future) +- **Conservative**: Ask before every tool call +- **Balanced**: Ask for destructive/external actions only (default) +- **Autonomous**: Only ask when credentials missing + +--- ## 8) UX Flows ### Flow A: Standard Chat -1. User sends a message. -2. Agent responds, streams tokens. -3. Tool calls appear in tool panel. -4. Tool results appear inline. -5. Conversation persists. +1. User sends a message +2. Agent searches registry for relevant tools (Tool Search Tool) +3. Agent responds with plan or starts execution +4. Tool calls appear in tool panel with real-time status +5. Tool results appear inline +6. Agent synthesizes final response +7. Conversation persists ### Flow B: Missing Credentials -1. Tool requires env vars. -2. Agent pauses and returns `MISSING_ENV_VARS`. -3. UI displays required keys and descriptions. -4. User provides keys. -5. Execution resumes from same step. +1. Tool requires env vars +2. Agent pauses and returns `MISSING_ENV_VARS` +3. UI displays required keys with descriptions +4. User provides keys via modal +5. Execution resumes from same step +6. Tool executes with provided credentials ### Flow C: Planning -1. User sends a request. -2. Agent responds with one or more plans. -3. User selects a plan or asks for changes. -4. Execution starts. +1. User sends a request +2. Agent generates 2-3 plans with confidence scores +3. UI displays plans with cost estimates +4. User selects a plan or asks for changes +5. Execution starts with selected plan +6. Progress tracked against plan steps ### Flow D: Tool Failure + Alternatives -1. Tool fails or missing credentials. -2. Agent searches for alternative tools. -3. Agent presents options and required keys in a table. -4. User selects a tool; execution resumes. +1. Tool fails or returns error +2. Agent searches for alternative tools +3. Agent presents options and required keys in a table +4. User selects a tool or provides guidance +5. Execution resumes with alternative -## 9) Data Model (High-Level) -- Conversation: id, userId (optional), createdAt, updatedAt, title -- Message: id, conversationId, role, content, createdAt -- ToolRun: id, conversationId, messageId, toolId, input, output, status, duration -- Plan: id, conversationId, messageId, steps, selectedPlanId, createdAt -- CredentialRequest: id, conversationId, toolRunId, requiredKeys, status, createdAt -- CredentialStore: userId, keyName, encryptedValue, scope (global/tool) +### Flow E: Multi-User Collaboration +1. Owner creates conversation +2. Owner shares URL with collaborators +3. Collaborators join and see participant list +4. Any participant can send messages +5. Messages attributed to sender +6. Only participants with credentials can trigger tool execution + +--- + +## 9) Data Model + +### New Models Required + +```prisma +// Multi-user support +model ConversationParticipant { + id String @id @default(cuid()) + conversationId String + userId String? + apiKeyId String? + displayName String @db.VarChar(100) + email String? + role String @default("collaborator") // owner, collaborator, viewer + joinedAt DateTime @default(now()) + + @@unique([conversationId, userId, apiKeyId]) + @@index([conversationId]) +} + +// Tool execution audit trail +model ConversationToolRun { + id String @id @default(cuid()) + conversationId String + messageId String + toolId String? + toolName String @db.VarChar(200) + input Json @db.JsonB + output Json? @db.JsonB + error String? @db.Text + errorType String? // timeout, auth, validation, execution + status String @db.VarChar(20) // pending, running, success, error, timeout + startedAt DateTime @default(now()) + completedAt DateTime? + executionTimeMs Int? + retryCount Int @default(0) + inputTokens Int? + outputTokens Int? + estimatedCost Decimal? @db.Decimal(10, 6) + + @@index([conversationId]) + @@index([status]) + @@index([toolName]) +} + +// Credential request tracking +model CredentialRequest { + id String @id @default(cuid()) + conversationId String + toolRunId String? + userId String? + requiredKeys Json @db.JsonB // [{ name, description, url }] + providedKeys Json? @db.JsonB + status String @db.VarChar(20) // pending, provided, expired, cancelled + expiresAt DateTime? + providedAt DateTime? + toolName String? @db.VarChar(200) + createdAt DateTime @default(now()) + + @@index([conversationId]) + @@index([status]) +} + +// Plan storage +model ConversationPlan { + id String @id @default(cuid()) + conversationId String + messageId String + steps Json @db.JsonB + confidence Float? + reasoning String? @db.Text + isSelected Boolean @default(false) + selectedAt DateTime? + executionStartedAt DateTime? + executionCompletedAt DateTime? + executionStatus String? @db.VarChar(20) + estimatedTokens Int? + estimatedCost Decimal? @db.Decimal(10, 6) + actualTokensUsed Int? + actualCost Decimal? @db.Decimal(10, 6) + successRate Float? + createdAt DateTime @default(now()) + + @@index([conversationId]) + @@index([isSelected]) +} + +// Omega user settings +model OmegaUserSettings { + id String @id @default(cuid()) + userId String @unique + omegaEnvVars Json? @db.JsonB // Encrypted credentials + pinnedToolIds String[] @default([]) + blockedToolIds String[] @default([]) + enableSafeMode Boolean @default(false) + customSystemPrompt String? @db.Text + preferredModel String? @db.VarChar(100) + preferredProvider String? @db.VarChar(50) + autoGenerateTitle Boolean @default(true) + showDebugMode Boolean @default(false) + proactivenessLevel String @default("balanced") // conservative, balanced, autonomous + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([userId]) +} + +// Security audit log +model OmegaAuditLog { + id String @id @default(cuid()) + userId String + conversationId String? + eventType String @db.VarChar(50) // CREDENTIAL_ACCESS, TOOL_EXECUTED, etc. + resourceType String? + resourceId String? + success Boolean + errorCode String? + metadata Json? @db.JsonB + ipAddress String? + userAgent String? + previousEventHash String? + eventHash String + createdAt DateTime @default(now()) + + @@index([userId]) + @@index([conversationId]) + @@index([eventType]) + @@index([createdAt]) +} +``` + +### Extended Existing Models + +```prisma +// Extend Message model +model Message { + // ... existing fields ... + authorId String? + authorEmail String? + authorName String? + + // Relations + toolRuns ConversationToolRun[] + plans ConversationPlan[] +} + +// Extend Conversation model +model Conversation { + // ... existing fields ... + executionState String? // idle, running, paused, cancelled + currentMessageId String? + cancelledAt DateTime? + inputTokensTotal Int @default(0) + outputTokensTotal Int @default(0) + costEstimate Decimal? @db.Decimal(10, 6) + + // Relations + participants ConversationParticipant[] + toolRuns ConversationToolRun[] + credentialRequests CredentialRequest[] + plans ConversationPlan[] +} +``` + +--- ## 10) Security and Privacy -- Encrypt stored credentials at rest. -- Never expose full secret values in UI or logs. -- Redact secrets from tool outputs when needed. -- Rate limit API endpoints to prevent abuse. -- Log rate limit events (even if limits are effectively unlimited at launch). -- Audit logs for tool calls and credential access. + +### Credential Security +- Encrypt stored credentials at rest (AES-256-GCM) +- Never expose full secret values in UI or logs +- Use credential references in LLM context, not raw values +- Output filtering for credential patterns before returning to users +- Credential isolation between users (no sharing in shared conversations) +- Session-scoped credentials that expire with conversation + +### Tool Execution Security +- Execute tools in sandboxed environments with resource limits +- Plan-then-execute pattern (immutable plan, non-LLM executor) +- Input validation with injection pattern detection +- Output sanitization for all contexts (HTML, SQL, shell) +- Never execute LLM output directly as code without validation + +### Rate Limiting +- Tiered limits by user tier (FREE: 100/hr, PRO: 1000/hr) +- Token-aware rate limiting for AI operations +- Cost-based limiting (weight by computational cost) +- Graceful degradation when approaching limits +- Log rate limit events for abuse detection + +### Audit Logging +- Immutable, append-only logs with cryptographic chaining +- Required events: CREDENTIAL_ACCESS, TOOL_EXECUTED, AUTH_FAILURE, PERMISSION_CHANGED +- 7-year retention for compliance +- Real-time alerting for suspicious patterns + +### Multi-User Security +- Only owner-stored credentials may run tools in shared conversations +- Every tool execution attributed to specific user +- Credential isolation (compromise of one user never cascades) +- Role-based permissions (owner, collaborator, viewer) + +--- ## 11) Technical Foundations (Reuse Existing) -From current codebase: -- Chat + SSE (agent conversations, tool-call streaming, message persistence): - `apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts` -- Pretty URL variant for agent chat (API key scopes + owner/public checks): - `apps/web/src/app/api/[username]/agents/[agentSlug]/conversation/[conversationId]/route.ts` -- Chat UI with tool panel + streaming + Virtuoso pagination: - `apps/web/src/app/agents/[id]/chat/[chatId]/page.tsx` -- Tool execution wrapper + AI SDK tool definition builder: - `apps/web/src/lib/ai-agent/tool-executor-agent.ts` -- Executor resolution + sandbox execution: - `apps/web/src/lib/executors/index.ts` -- Registry meta-tools for search + execute: - `packages/tools/registrySearch`, `packages/tools/registryExecute` -- Env var detection and missing var reporting: - `apps/web/src/lib/agents/env-helpers.ts` -- Env var cascade and caller-provided env support: - `apps/web/src/lib/agents/build-tools.ts` -- MCP handler with env-var validation for non-owners: - `apps/web/src/lib/mcp/handlers.ts` -- Credential encryption utilities and API key storage: - `apps/web/src/lib/crypto/api-keys.ts`, `apps/web/src/app/api/user/api-keys/route.ts` -- Existing planning tool for generatePlans: - `packages/tools/official/tool-selection-plan` + +### From Current Codebase + +| Component | Location | Capability | +|-----------|----------|------------| +| Chat + SSE | `apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts` | Agent conversations, tool-call streaming, message persistence | +| Chat UI | `apps/playground/src/components/chat/` | MessageBubble, ChatMessages, streaming patterns | +| Tool Execution | `apps/web/src/lib/ai-agent/tool-executor-agent.ts` | AI SDK tool definition builder | +| Executor | `apps/web/src/lib/executors/index.ts` | Sandbox execution, custom executor support | +| Registry Tools | `packages/tools/registrySearch`, `registryExecute` | Search + execute from registry | +| Env Handling | `apps/web/src/lib/agents/env-helpers.ts`, `build-tools.ts` | Missing var detection, env cascade | +| MCP Handler | `apps/web/src/lib/mcp/handlers.ts` | Tool conversion, env validation | +| Encryption | `apps/web/src/lib/crypto/api-keys.ts` | AES-256-GCM encryption | +| Planning | `packages/tools/official/tool-selection-plan` | Plan generation | +| Rate Limiting | `apps/web/src/lib/rate-limit.ts` | Distributed rate limiting | +| Auth | `apps/web/src/lib/api-keys/middleware.ts` | Dual auth (session + API key) | + +### UI Components Available + +47 production-ready components in `@tpmjs/ui`: +- Forms: Input, Textarea, Select, Checkbox, Radio, Switch, Slider +- Display: Badge, Card, Table, CodeBlock, Icon (50+ icons) +- Feedback: Modal, Drawer, Toast, Skeleton, Spinner +- Navigation: Tabs, Breadcrumbs, Pagination +- Specialized: ToolCard, QualityScore, ActivityStream + +### Key Dependencies +- `react-virtuoso` (v4.18.1) - For lazy-loading long conversations +- `streamdown` (v1.6.11) - Markdown rendering with streaming +- `@ai-sdk/react` - Chat hooks and streaming +- Vercel AI SDK - Tool execution and streaming + +--- ## 12) Product Decisions (Initial) -- Primary audience: developers. -- Auth: login required (v0). -- Tool access: entire registry via registry search + execute. -- Model: default `gpt-4.1-mini`; users choose provider/model if they have keys. -- Planning: only on explicit request or when task complexity is high. -- Credentials: stored per-user, scoped to Omega, managed in Omega settings. -- Safety: no additional tool restrictions beyond existing TPMJS policies. -- Sharing: anyone with conversation URL can participate. -- Retention: conversations are stored indefinitely. -- Tool output: summarized by default with "view raw" toggle. -- Cancel: users can stop a running tool or full run. -- Rate limits: system in place, effectively unlimited at launch. -- Participants: show display names + Gravatar avatars. -- API auth: requires standard TPMJS API key. -- Tool selection UX: agent-only (no manual marketplace UI for v0). -- Files: uploads deferred to a later phase. -- Streaming: stream tool outputs where supported by the AI SDK. -- UI: follow TPMJS style guide (no bespoke theme for v0). -- Conversation IDs: UUIDs. -- Credentials: per-user only (no team/workspace creds in v0). -- Conversation context: keep full history (no auto-summarization in v0). -- Compliance: no special compliance requirements at launch. -- Tool budget indicator: not required. -- Shared conversation execution: only owner-stored credentials may run tools. -- Large outputs: lazy-load via virtualization. -- Export: JSON and Markdown supported. -- Memory: per-conversation only. -- Conversation titles: auto-generated after first response (AI SDK). -- Reactions: not required for v0. -- Tool reasoning: show only when model returns reasoning tokens. -- Deletion: users cannot delete conversations in v0. -- API keys: use existing TPMJS API keys only. -- Discovery: conversations are not listed; URL-only access. -- Context control: no reset button, use new chat instead. -- Tool allowlist: no allowlist (pins only). -- Audit/log retention: store all tool I/O indefinitely. -- Shared chat auth: accept session auth or TPMJS API key. -- Landing: intro view before first chat. -- Empty state: creative sample prompts. -- Theme: light/dark via style guide. + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Primary audience | Developers | v0 focus on technical users | +| Auth | Login required | Security and attribution | +| Tool access | Entire registry via Tool Search Tool | Scale to 1M+ tools | +| Model | Default `gpt-4.1-mini`; users choose if they have keys | Balance cost and capability | +| Planning | Only on explicit request or high complexity | Avoid overhead for simple tasks | +| Credentials | Per-user, scoped to Omega | Isolation and security | +| Safety | No additional restrictions beyond TPMJS policies | Developer-focused | +| Sharing | Anyone with URL can participate | Collaboration | +| Retention | Conversations stored indefinitely | Auditability | +| Tool output | Summarized by default with "view raw" | Reduce noise | +| Cancel | Users can stop running tool or full run | Control | +| Rate limits | System in place, generous at launch | Growth | +| Participants | Show display names + Gravatar | Attribution | +| API auth | Standard TPMJS API keys | Simplicity | +| Tool selection | Agent-only (no manual marketplace UI) | Showcase AI | +| Files | Deferred to later phase | Scope | +| Streaming | Stream tool outputs where supported | Responsiveness | +| UI | TPMJS style guide (no bespoke theme) | Consistency | +| IDs | UUIDs | Standard | +| Context | Keep full history (no auto-summarization) | Accuracy | +| Deletion | Users can delete their conversations | Privacy | +| Audit logs | Store all tool I/O indefinitely | Compliance | + +--- ## 13) API Response Contracts (Draft) + ### Missing Env Vars ```json { @@ -236,10 +608,16 @@ From current codebase: "error": "Missing required environment variables", "details": { "code": "MISSING_ENV_VARS", + "conversationId": "conv_abc123", + "toolRunId": "run_xyz789", "missingVars": [ - { "name": "FIRECRAWL_API_KEY", "description": "API key for Firecrawl" } + { + "name": "FIRECRAWL_API_KEY", + "description": "API key for Firecrawl web scraping", + "url": "https://firecrawl.dev/api-keys" + } ], - "hint": "Provide these variables in the 'env' field of your request" + "hint": "Provide these variables via POST /api/omega/conversations/:id/credentials" } } ``` @@ -247,41 +625,211 @@ From current codebase: ### Plan Response ```json { - "plan": [ - { "stepNumber": 1, "toolName": "registrySearchTool", "purpose": "Find tools for web scraping" }, - { "stepNumber": 2, "toolName": "registryExecuteTool", "purpose": "Run the chosen tool" } - ], - "confidence": 0.74, - "reasoning": "Found 2 relevant tools for this task" + "plans": [ + { + "id": "plan_001", + "steps": [ + { "stepNumber": 1, "toolName": "registrySearchTool", "purpose": "Find web scraping tools", "dependencies": [] }, + { "stepNumber": 2, "toolName": "firecrawl--scrape", "purpose": "Scrape the target URL", "dependencies": [1] }, + { "stepNumber": 3, "toolName": "openai--summarize", "purpose": "Summarize content", "dependencies": [2] } + ], + "confidence": 0.85, + "reasoning": "Found 3 relevant tools for web scraping and summarization", + "estimatedTokens": 4500, + "estimatedCostCents": 3, + "warnings": [] + }, + { + "id": "plan_002", + "steps": [ + { "stepNumber": 1, "toolName": "browser--fetch", "purpose": "Fetch page HTML", "dependencies": [] }, + { "stepNumber": 2, "toolName": "html--parse", "purpose": "Extract text content", "dependencies": [1] } + ], + "confidence": 0.72, + "reasoning": "Alternative approach using basic fetch", + "estimatedTokens": 2000, + "estimatedCostCents": 1, + "warnings": ["May not handle JavaScript-rendered content"] + } + ] } ``` +### Tool Execution Event (SSE) +```json +{ + "event": "run.step.tool.completed", + "data": { + "stepId": "step_001", + "toolRunId": "run_xyz789", + "toolName": "firecrawl--scrape", + "status": "success", + "executionTimeMs": 2340, + "outputSummary": "Scraped 5 pages, extracted 12,000 characters", + "outputFull": { ... }, + "tokensUsed": 450 + } +} +``` + +--- + ## 14) Performance and Reliability -- SSE streams should stay responsive under load. -- Tool execution timeouts (default 5 minutes). -- Concurrency controls per conversation. -- Circuit breaker for broken tools or executors. + +### Response Times +| Operation | Target P50 | Target P95 | +|-----------|------------|------------| +| Conversation create | <200ms | <500ms | +| First token (streaming) | <1s | <2s | +| Tool search | <500ms | <1s | +| Tool execution | <5s | <30s | +| Plan generation | <2s | <5s | + +### Reliability +- SSE streams should stay responsive under load +- Tool execution timeouts: default 5 minutes, per-tool configurable +- Concurrency controls per conversation (1 active execution) +- Circuit breaker for broken tools or executors +- Adaptive retry with exponential backoff + jitter for transient errors + +### Graceful Degradation +- When rate limits approaching: reduce context window, use smaller model +- When tool fails: suggest alternatives, allow user override +- When executor unhealthy: queue requests, show status banner + +--- ## 15) Observability -- Log tool calls and errors. -- Track token usage per conversation. -- Surface tool success/failure rates. -- Store conversation logs for debugging. + +### Metrics to Track +- Tool calls and errors (count, latency, success rate) +- Token usage per conversation (input/output breakdown) +- Tool success/failure rates by tool +- P50/P95/P99 latency per tool +- Conversation completion rate +- Credential request frequency +- Replan frequency + +### OpenTelemetry Integration +Emit traces in OpenTelemetry format for export to: +- LangSmith +- Arize Phoenix +- Langfuse +- Custom observability stacks + +### Logging +- Store conversation logs for debugging +- Tool I/O logged for audit trail +- Security events logged separately (immutable) + +--- ## 16) Risks and Mitigations -- Tool quality variance: provide confidence and fallbacks. -- Missing credentials: pause/resume flow with clear UX. -- Abuse risk: rate limiting + auth. -- Tool execution cost: usage tracking + limits. + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Tool quality variance | User frustration | Confidence scores, fallbacks, tool ratings | +| Missing credentials | Blocked execution | Clear pause/resume UX, credential storage | +| Abuse risk | Cost, reputation | Rate limiting, auth, monitoring | +| Tool execution cost | Financial | Usage tracking, limits, cost estimates | +| Prompt injection | Security | Input validation, output filtering, context isolation | +| Credential leakage | Security | Never pass raw creds to LLM, output scanning | +| Tool failures cascade | Poor UX | Circuit breakers, alternatives, graceful degradation | +| Context window exhaustion | Quality degradation | Tool Search Tool pattern, summarization | + +--- ## 17) Rollout Plan -1. Internal alpha (team-only) -2. Public beta with rate limits -3. Full public launch -## 18) Open Questions -- System prompt ownership and defaults (versioning and updates)? -- Safe Mode default on/off? -- How many plans should Omega propose by default? - - Webhook support for API events (future). - - Profiles for system prompts (future). +### Phase 1: Internal Alpha (2 weeks) +- Core chat UI and API +- Tool search and execution +- Basic credential handling +- Team-only access + +### Phase 2: Private Beta (4 weeks) +- Multi-user conversations +- Planning panel +- Full credential management +- Invite-only access with rate limits + +### Phase 3: Public Beta (4 weeks) +- Full feature set +- Public access with rate limits +- Feedback collection +- Performance optimization + +### Phase 4: General Availability +- Rate limits relaxed +- SLA commitments +- Documentation and tutorials +- Integration guides + +--- + +## 18) Future Enhancements (Post-v0) + +### High Priority +- **Webhooks** for API events (conversation.created, tool.completed, etc.) +- **Conversation branching** - Fork at any message to explore alternatives +- **Cost estimation UI** - Show estimated cost before execution +- **Execution timeout per-tool** - Configurable based on tool metadata + +### Medium Priority +- **File uploads and handling** - Inline preview, versioning, type detection +- **Workflow/DAG view** - Visual tool execution graph +- **User-level persistent memory** - Remember preferences across conversations +- **Semantic search over history** - "What did we discuss about X?" + +### Lower Priority +- **Conversation templates** - Pre-configured starting points +- **Multi-agent orchestration** - Specialist sub-agents for categories +- **mTLS for tool integrations** - High-security external services +- **Behavioral anomaly detection** - ML-based unusual pattern detection + +--- + +## 19) Open Questions + +1. **System prompt ownership** - How do we version and update the default system prompt? +2. **Safe Mode default** - Should Safe Mode be on or off by default? +3. **Number of plans** - How many plans should Omega propose by default (2? 3?)? +4. **Plan model differentiation** - Should planning use a premium model and execution use cheaper? +5. **Conversation discovery** - Should users be able to list their conversations, or URL-only? +6. **Tool blocklist scope** - Global blocklist vs. per-user vs. per-conversation? + +--- + +## 20) Appendix: OWASP Agentic AI Security Checklist + +Based on [OWASP Top 10 for Agentic Applications 2026](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/): + +| Risk | Implemented | +|------|-------------| +| ASI01: Agent Goal Hijack | [ ] Prompt injection defenses, context isolation | +| ASI02: Tool Misuse & Exploitation | [ ] Least privilege, sandboxing, approval flows | +| ASI03: Identity & Privilege Abuse | [ ] Short-lived credentials, task-scoped permissions | +| ASI04: Supply Chain Vulnerabilities | [ ] Tool verification, signed packages | +| ASI05: Unexpected Code Execution | [ ] Sandboxing, no eval(), output validation | +| ASI06: Memory & Context Poisoning | [ ] Isolated sessions, validated context | +| ASI07: Insecure Inter-Agent Communication | [ ] N/A (single agent in v0) | +| ASI08: Cascading Failures | [ ] Circuit breakers, graceful degradation | +| ASI09: Human-Agent Trust Exploitation | [ ] Clear agent identification, human-in-loop | +| ASI10: Rogue Agents | [ ] Monitoring, kill switches | + +--- + +## 21) References + +### Internal +- TPMJS Codebase: Agent conversation system, tool execution, MCP handlers +- Database Schema: `packages/db/prisma/schema.prisma` +- UI Components: `packages/ui/src/` + +### External +- [OpenAI Assistants API](https://platform.openai.com/docs/assistants) +- [Anthropic Tool Use](https://www.anthropic.com/engineering/advanced-tool-use) +- [LangGraph Plan-and-Execute](https://langchain-ai.github.io/langgraph/tutorials/plan-and-execute/) +- [MCP Specification](https://modelcontextprotocol.io/specification/) +- [OWASP GenAI Security](https://genai.owasp.org/) +- [OWASP LLM Top 10](https://owasp.org/www-project-top-10-for-large-language-model-applications/) diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 7c88b5f..26946c9 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -4,7 +4,9 @@ "aliases": [], "args": {}, "description": "Run diagnostic checks for TPMJS CLI", - "examples": ["<%= config.bin %> <%= command.id %>"], + "examples": [ + "<%= config.bin %> <%= command.id %>" + ], "flags": { "json": { "description": "Output in JSON format", @@ -29,7 +31,11 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "doctor.js"] + "relativePath": [ + "dist", + "commands", + "doctor.js" + ] }, "playground": { "aliases": [], @@ -73,18 +79,37 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "playground.js"] + "relativePath": [ + "dist", + "commands", + "playground.js" + ] }, "run": { "aliases": [], "args": {}, "description": "Execute a tool from a collection via MCP", "examples": [ - "<%= config.bin %> run -c ajax/unsandbox -t execute --args '{\"code\":\"print(1)\",\"language\":\"python\"}'", - "<%= config.bin %> run --collection ajax/ajax-collection --tool base64Encode --args '{\"data\":\"hello\"}'", - "OPENAI_API_KEY=xxx <%= config.bin %> run -c ajax/my-collection -t myTool", - "<%= config.bin %> run -c ajax/tools -t search --args '{\"query\":\"test\"}' --json", - "<%= config.bin %> run -c ajax/tools -t search --env API_KEY=xxx --env DEBUG=true" + { + "description": "First, list all tools in a collection", + "command": "<%= config.bin %> collection info ajax/unsandbox" + }, + { + "description": "Execute Python code in the unsandbox collection", + "command": "<%= config.bin %> run -c ajax/unsandbox -t unsandbox--execute --args '{\"language\":\"python\",\"code\":\"print(42)\"}'" + }, + { + "description": "Pass environment variables for tool authentication", + "command": "<%= config.bin %> run -c ajax/unsandbox -t unsandbox--execute -e UNSANDBOX_PUBLIC_KEY=xxx -e UNSANDBOX_SECRET_KEY=xxx --args '{\"language\":\"python\",\"code\":\"print(1)\"}'" + }, + { + "description": "Output result as JSON", + "command": "<%= config.bin %> run -c ajax/tools -t search --args '{\"query\":\"test\"}' --json" + }, + { + "description": "Show verbose output for debugging", + "command": "<%= config.bin %> run -c ajax/unsandbox -t unsandbox--healthCheck --args '{}' -v" + } ], "flags": { "collection": { @@ -153,13 +178,19 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "run.js"] + "relativePath": [ + "dist", + "commands", + "run.js" + ] }, "update": { "aliases": [], "args": {}, "description": "Update the TPMJS CLI to the latest version", - "examples": ["<%= config.bin %> <%= command.id %>"], + "examples": [ + "<%= config.bin %> <%= command.id %>" + ], "flags": { "json": { "description": "Output in JSON format", @@ -190,7 +221,178 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "update.js"] + "relativePath": [ + "dist", + "commands", + "update.js" + ] + }, + "auth:login": { + "aliases": [], + "args": { + "key": { + "description": "API key (alternative to --api-key flag)", + "name": "key", + "required": false + } + }, + "description": "Authenticate with TPMJS", + "examples": [ + "<%= config.bin %> <%= command.id %> --api-key tpm_xxxxx", + "<%= config.bin %> <%= command.id %> --browser" + ], + "flags": { + "api-key": { + "char": "k", + "description": "API key (or set TPMJS_API_KEY environment variable)", + "name": "api-key", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, + "browser": { + "char": "b", + "description": "Open browser for OAuth authentication", + "name": "browser", + "allowNo": false, + "type": "boolean" + }, + "json": { + "description": "Output in JSON format", + "name": "json", + "allowNo": false, + "type": "boolean" + }, + "verbose": { + "char": "v", + "description": "Show verbose output", + "name": "verbose", + "allowNo": false, + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "auth:login", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "auth", + "login.js" + ] + }, + "auth:logout": { + "aliases": [], + "args": {}, + "description": "Log out from TPMJS", + "examples": [ + "<%= config.bin %> <%= command.id %>" + ], + "flags": { + "json": { + "description": "Output in JSON format", + "name": "json", + "allowNo": false, + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "auth:logout", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "auth", + "logout.js" + ] + }, + "auth:status": { + "aliases": [], + "args": {}, + "description": "Show authentication status", + "examples": [ + "<%= config.bin %> <%= command.id %>" + ], + "flags": { + "json": { + "description": "Output in JSON format", + "name": "json", + "allowNo": false, + "type": "boolean" + }, + "verbose": { + "char": "v", + "description": "Show verbose output", + "name": "verbose", + "allowNo": false, + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "auth:status", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "auth", + "status.js" + ] + }, + "auth:whoami": { + "aliases": [], + "args": {}, + "description": "Show current user information", + "examples": [ + "<%= config.bin %> <%= command.id %>" + ], + "flags": { + "json": { + "description": "Output in JSON format", + "name": "json", + "allowNo": false, + "type": "boolean" + }, + "verbose": { + "char": "v", + "description": "Show verbose output", + "name": "verbose", + "allowNo": false, + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "auth:whoami", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "auth", + "whoami.js" + ] }, "agent:chat": { "aliases": [], @@ -250,7 +452,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "agent", "chat.js"] + "relativePath": [ + "dist", + "commands", + "agent", + "chat.js" + ] }, "agent:create": { "aliases": [], @@ -292,7 +499,13 @@ "required": true, "hasDynamicHelp": false, "multiple": false, - "options": ["ANTHROPIC", "OPENAI", "GOOGLE", "GROQ", "MISTRAL"], + "options": [ + "ANTHROPIC", + "OPENAI", + "GOOGLE", + "GROQ", + "MISTRAL" + ], "type": "option" }, "model": { @@ -350,7 +563,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "agent", "create.js"] + "relativePath": [ + "dist", + "commands", + "agent", + "create.js" + ] }, "agent:delete": { "aliases": [], @@ -397,7 +615,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "agent", "delete.js"] + "relativePath": [ + "dist", + "commands", + "agent", + "delete.js" + ] }, "agent:list": { "aliases": [], @@ -449,7 +672,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "agent", "list.js"] + "relativePath": [ + "dist", + "commands", + "agent", + "list.js" + ] }, "agent:update": { "aliases": [], @@ -495,7 +723,13 @@ "name": "provider", "hasDynamicHelp": false, "multiple": false, - "options": ["ANTHROPIC", "OPENAI", "GOOGLE", "GROQ", "MISTRAL"], + "options": [ + "ANTHROPIC", + "OPENAI", + "GOOGLE", + "GROQ", + "MISTRAL" + ], "type": "option" }, "model": { @@ -551,269 +785,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "agent", "update.js"] - }, - "auth:login": { - "aliases": [], - "args": { - "key": { - "description": "API key (alternative to --api-key flag)", - "name": "key", - "required": false - } - }, - "description": "Authenticate with TPMJS", - "examples": [ - "<%= config.bin %> <%= command.id %> --api-key tpm_xxxxx", - "<%= config.bin %> <%= command.id %> --browser" - ], - "flags": { - "api-key": { - "char": "k", - "description": "API key (or set TPMJS_API_KEY environment variable)", - "name": "api-key", - "hasDynamicHelp": false, - "multiple": false, - "type": "option" - }, - "browser": { - "char": "b", - "description": "Open browser for OAuth authentication", - "name": "browser", - "allowNo": false, - "type": "boolean" - }, - "json": { - "description": "Output in JSON format", - "name": "json", - "allowNo": false, - "type": "boolean" - }, - "verbose": { - "char": "v", - "description": "Show verbose output", - "name": "verbose", - "allowNo": false, - "type": "boolean" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [], - "id": "auth:login", - "pluginAlias": "@tpmjs/cli", - "pluginName": "@tpmjs/cli", - "pluginType": "core", - "strict": true, - "enableJsonFlag": false, - "isESM": true, - "relativePath": ["dist", "commands", "auth", "login.js"] - }, - "auth:logout": { - "aliases": [], - "args": {}, - "description": "Log out from TPMJS", - "examples": ["<%= config.bin %> <%= command.id %>"], - "flags": { - "json": { - "description": "Output in JSON format", - "name": "json", - "allowNo": false, - "type": "boolean" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [], - "id": "auth:logout", - "pluginAlias": "@tpmjs/cli", - "pluginName": "@tpmjs/cli", - "pluginType": "core", - "strict": true, - "enableJsonFlag": false, - "isESM": true, - "relativePath": ["dist", "commands", "auth", "logout.js"] - }, - "auth:status": { - "aliases": [], - "args": {}, - "description": "Show authentication status", - "examples": ["<%= config.bin %> <%= command.id %>"], - "flags": { - "json": { - "description": "Output in JSON format", - "name": "json", - "allowNo": false, - "type": "boolean" - }, - "verbose": { - "char": "v", - "description": "Show verbose output", - "name": "verbose", - "allowNo": false, - "type": "boolean" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [], - "id": "auth:status", - "pluginAlias": "@tpmjs/cli", - "pluginName": "@tpmjs/cli", - "pluginType": "core", - "strict": true, - "enableJsonFlag": false, - "isESM": true, - "relativePath": ["dist", "commands", "auth", "status.js"] - }, - "auth:whoami": { - "aliases": [], - "args": {}, - "description": "Show current user information", - "examples": ["<%= config.bin %> <%= command.id %>"], - "flags": { - "json": { - "description": "Output in JSON format", - "name": "json", - "allowNo": false, - "type": "boolean" - }, - "verbose": { - "char": "v", - "description": "Show verbose output", - "name": "verbose", - "allowNo": false, - "type": "boolean" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [], - "id": "auth:whoami", - "pluginAlias": "@tpmjs/cli", - "pluginName": "@tpmjs/cli", - "pluginType": "core", - "strict": true, - "enableJsonFlag": false, - "isESM": true, - "relativePath": ["dist", "commands", "auth", "whoami.js"] - }, - "mcp:config": { - "aliases": [], - "args": { - "collection": { - "description": "Collection path (username/slug)", - "name": "collection", - "required": true - } - }, - "description": "Generate MCP configuration for AI clients", - "examples": [ - "<%= config.bin %> <%= command.id %> ajax/ajax-collection", - "<%= config.bin %> <%= command.id %> ajax/ajax-collection --client cursor", - "<%= config.bin %> <%= command.id %> ajax/ajax-collection --output ~/Library/Application\\ Support/Claude/claude_desktop_config.json" - ], - "flags": { - "client": { - "char": "c", - "description": "Target client (claude, cursor, windsurf, generic)", - "name": "client", - "default": "claude", - "hasDynamicHelp": false, - "multiple": false, - "options": ["claude", "cursor", "windsurf", "generic"], - "type": "option" - }, - "output": { - "char": "o", - "description": "Output file path (will merge with existing config)", - "name": "output", - "hasDynamicHelp": false, - "multiple": false, - "type": "option" - }, - "json": { - "description": "Output in JSON format", - "name": "json", - "allowNo": false, - "type": "boolean" - }, - "api-key": { - "char": "k", - "description": "API key to include in config (optional)", - "name": "api-key", - "hasDynamicHelp": false, - "multiple": false, - "type": "option" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [], - "id": "mcp:config", - "pluginAlias": "@tpmjs/cli", - "pluginName": "@tpmjs/cli", - "pluginType": "core", - "strict": true, - "enableJsonFlag": false, - "isESM": true, - "relativePath": ["dist", "commands", "mcp", "config.js"] - }, - "mcp:serve": { - "aliases": [], - "args": {}, - "description": "Run as a local MCP server", - "examples": [ - "<%= config.bin %> <%= command.id %>", - "<%= config.bin %> <%= command.id %> --port 8080", - "<%= config.bin %> <%= command.id %> --stdio", - "<%= config.bin %> <%= command.id %> --collection my-collection" - ], - "flags": { - "port": { - "char": "p", - "description": "Port to run the server on (HTTP mode)", - "name": "port", - "default": 3333, - "hasDynamicHelp": false, - "multiple": false, - "type": "option" - }, - "stdio": { - "description": "Use stdio transport instead of HTTP", - "name": "stdio", - "allowNo": false, - "type": "boolean" - }, - "collection": { - "char": "c", - "description": "Serve tools from a specific collection", - "name": "collection", - "hasDynamicHelp": false, - "multiple": false, - "type": "option" - }, - "tool": { - "char": "t", - "description": "Serve specific tools (comma-separated)", - "name": "tool", - "hasDynamicHelp": false, - "multiple": true, - "type": "option" - }, - "verbose": { - "char": "v", - "description": "Show verbose output", - "name": "verbose", - "allowNo": false, - "type": "boolean" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [], - "id": "mcp:serve", - "pluginAlias": "@tpmjs/cli", - "pluginName": "@tpmjs/cli", - "pluginType": "core", - "strict": true, - "enableJsonFlag": false, - "isESM": true, - "relativePath": ["dist", "commands", "mcp", "serve.js"] + "relativePath": [ + "dist", + "commands", + "agent", + "update.js" + ] }, "collection:add": { "aliases": [], @@ -853,7 +830,12 @@ "strict": false, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "add.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "add.js" + ] }, "collection:create": { "aliases": [], @@ -910,7 +892,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "create.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "create.js" + ] }, "collection:delete": { "aliases": [], @@ -957,7 +944,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "delete.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "delete.js" + ] }, "collection:import": { "aliases": [], @@ -1006,7 +998,58 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "import.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "import.js" + ] + }, + "collection:info": { + "aliases": [], + "args": { + "collection": { + "description": "Collection identifier (username/slug)", + "name": "collection", + "required": true + } + }, + "description": "Show collection details and list all available tools", + "examples": [ + "<%= config.bin %> collection info ajax/unsandbox", + "<%= config.bin %> collection info ajax/unsandbox --json", + "<%= config.bin %> collection info ajax/unsandbox --verbose" + ], + "flags": { + "json": { + "description": "Output in JSON format", + "name": "json", + "allowNo": false, + "type": "boolean" + }, + "verbose": { + "char": "v", + "description": "Show tool input schemas", + "name": "verbose", + "allowNo": false, + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "collection:info", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "collection", + "info.js" + ] }, "collection:list": { "aliases": [], @@ -1058,7 +1101,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "list.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "list.js" + ] }, "collection:remove": { "aliases": [], @@ -1075,7 +1123,9 @@ } }, "description": "Remove a tool from a collection", - "examples": ["<%= config.bin %> <%= command.id %> my-collection tool-id-1"], + "examples": [ + "<%= config.bin %> <%= command.id %> my-collection tool-id-1" + ], "flags": { "json": { "description": "Output in JSON format", @@ -1100,7 +1150,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "remove.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "remove.js" + ] }, "collection:update": { "aliases": [], @@ -1162,7 +1217,148 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "collection", "update.js"] + "relativePath": [ + "dist", + "commands", + "collection", + "update.js" + ] + }, + "mcp:config": { + "aliases": [], + "args": { + "collection": { + "description": "Collection path (username/slug)", + "name": "collection", + "required": true + } + }, + "description": "Generate MCP configuration for AI clients", + "examples": [ + "<%= config.bin %> <%= command.id %> ajax/ajax-collection", + "<%= config.bin %> <%= command.id %> ajax/ajax-collection --client cursor", + "<%= config.bin %> <%= command.id %> ajax/ajax-collection --output ~/Library/Application\\ Support/Claude/claude_desktop_config.json" + ], + "flags": { + "client": { + "char": "c", + "description": "Target client (claude, cursor, windsurf, generic)", + "name": "client", + "default": "claude", + "hasDynamicHelp": false, + "multiple": false, + "options": [ + "claude", + "cursor", + "windsurf", + "generic" + ], + "type": "option" + }, + "output": { + "char": "o", + "description": "Output file path (will merge with existing config)", + "name": "output", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, + "json": { + "description": "Output in JSON format", + "name": "json", + "allowNo": false, + "type": "boolean" + }, + "api-key": { + "char": "k", + "description": "API key to include in config (optional)", + "name": "api-key", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "mcp:config", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "mcp", + "config.js" + ] + }, + "mcp:serve": { + "aliases": [], + "args": {}, + "description": "Run as a local MCP server", + "examples": [ + "<%= config.bin %> <%= command.id %>", + "<%= config.bin %> <%= command.id %> --port 8080", + "<%= config.bin %> <%= command.id %> --stdio", + "<%= config.bin %> <%= command.id %> --collection my-collection" + ], + "flags": { + "port": { + "char": "p", + "description": "Port to run the server on (HTTP mode)", + "name": "port", + "default": 3333, + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, + "stdio": { + "description": "Use stdio transport instead of HTTP", + "name": "stdio", + "allowNo": false, + "type": "boolean" + }, + "collection": { + "char": "c", + "description": "Serve tools from a specific collection", + "name": "collection", + "hasDynamicHelp": false, + "multiple": false, + "type": "option" + }, + "tool": { + "char": "t", + "description": "Serve specific tools (comma-separated)", + "name": "tool", + "hasDynamicHelp": false, + "multiple": true, + "type": "option" + }, + "verbose": { + "char": "v", + "description": "Show verbose output", + "name": "verbose", + "allowNo": false, + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [], + "id": "mcp:serve", + "pluginAlias": "@tpmjs/cli", + "pluginName": "@tpmjs/cli", + "pluginType": "core", + "strict": true, + "enableJsonFlag": false, + "isESM": true, + "relativePath": [ + "dist", + "commands", + "mcp", + "serve.js" + ] }, "publish:check": { "aliases": [], @@ -1202,7 +1398,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "publish", "check.js"] + "relativePath": [ + "dist", + "commands", + "publish", + "check.js" + ] }, "publish:preview": { "aliases": [], @@ -1245,7 +1446,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "publish", "preview.js"] + "relativePath": [ + "dist", + "commands", + "publish", + "preview.js" + ] }, "scenario:generate": { "aliases": [], @@ -1301,7 +1507,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "scenario", "generate.js"] + "relativePath": [ + "dist", + "commands", + "scenario", + "generate.js" + ] }, "scenario:info": { "aliases": [], @@ -1351,7 +1562,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "scenario", "info.js"] + "relativePath": [ + "dist", + "commands", + "scenario", + "info.js" + ] }, "scenario:list": { "aliases": [], @@ -1418,7 +1634,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "scenario", "list.js"] + "relativePath": [ + "dist", + "commands", + "scenario", + "list.js" + ] }, "scenario:run": { "aliases": [], @@ -1468,7 +1689,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "scenario", "run.js"] + "relativePath": [ + "dist", + "commands", + "scenario", + "run.js" + ] }, "scenario:test": { "aliases": [], @@ -1509,7 +1735,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "scenario", "test.js"] + "relativePath": [ + "dist", + "commands", + "scenario", + "test.js" + ] }, "tool:execute": { "aliases": [], @@ -1582,7 +1813,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "tool", "execute.js"] + "relativePath": [ + "dist", + "commands", + "tool", + "execute.js" + ] }, "tool:info": { "aliases": [], @@ -1627,7 +1863,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "tool", "info.js"] + "relativePath": [ + "dist", + "commands", + "tool", + "info.js" + ] }, "tool:init": { "aliases": [], @@ -1652,7 +1893,10 @@ "default": "minimal", "hasDynamicHelp": false, "multiple": false, - "options": ["minimal", "rich"], + "options": [ + "minimal", + "rich" + ], "type": "option" }, "category": { @@ -1709,7 +1953,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "tool", "init.js"] + "relativePath": [ + "dist", + "commands", + "tool", + "init.js" + ] }, "tool:search": { "aliases": [], @@ -1776,7 +2025,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "tool", "search.js"] + "relativePath": [ + "dist", + "commands", + "tool", + "search.js" + ] }, "tool:trending": { "aliases": [], @@ -1819,7 +2073,12 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "tool", "trending.js"] + "relativePath": [ + "dist", + "commands", + "tool", + "trending.js" + ] }, "tool:validate": { "aliases": [], @@ -1862,8 +2121,13 @@ "strict": true, "enableJsonFlag": false, "isESM": true, - "relativePath": ["dist", "commands", "tool", "validate.js"] + "relativePath": [ + "dist", + "commands", + "tool", + "validate.js" + ] } }, - "version": "0.1.4" -} + "version": "0.1.5" +} \ No newline at end of file