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}" \\
-
setShowApiDocs(!showApiDocs)}
- >
+ setShowApiDocs(!showApiDocs)}>
API
@@ -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.
- setShowApiDocs(!showApiDocs)}
- >
+ setShowApiDocs(!showApiDocs)}>
Get Started
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