- Add agent-agnostic tool registry (tool-registry.ts) that discovers tools from disk - Embed tool-loader extension as platform infrastructure (ensure-tool-loader.ts) - Inject tool context into Claude prompts on first message - Add marketplace tool sync (sync-marketplace.ts) - Fix model selector defaulting to claude-code when no model explicitly selected - Exclude tool-loader-source.ts from tsconfig (Pi-specific deps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { DATA_PATH } from './data-path';
|
|
import { parseSeedVersion } from './sync-version';
|
|
|
|
const MARKETPLACE_URL = process.env.MARKETPLACE_URL ?? 'https://marketplace.officer.dev';
|
|
const GLOBAL_TOOLS_DIR = join(DATA_PATH, 'tools');
|
|
|
|
type ToolInput = {
|
|
type: string;
|
|
description: string;
|
|
values?: string;
|
|
};
|
|
|
|
type MarketplaceTool = {
|
|
dirName: string;
|
|
name: string;
|
|
label: string;
|
|
description: string;
|
|
body: string;
|
|
version: number;
|
|
language: string;
|
|
inputs: Record<string, ToolInput>;
|
|
implementation: string;
|
|
};
|
|
|
|
type MarketplaceResponse = {
|
|
categories: Array<{ name: string; tools: MarketplaceTool[] }>;
|
|
uncategorized: MarketplaceTool[];
|
|
};
|
|
|
|
function buildToolMd(tool: MarketplaceTool): string {
|
|
const lines = ['---'];
|
|
lines.push(`name: ${tool.name}`);
|
|
lines.push(`label: ${tool.label}`);
|
|
lines.push(`description: ${tool.description}`);
|
|
lines.push(`version: ${tool.version}`);
|
|
lines.push(`language: ${tool.language}`);
|
|
|
|
if (tool.inputs && Object.keys(tool.inputs).length > 0) {
|
|
lines.push('inputs:');
|
|
for (const [key, input] of Object.entries(tool.inputs)) {
|
|
lines.push(` ${key}:`);
|
|
lines.push(` type: ${input.type}`);
|
|
if (input.values) {
|
|
lines.push(` values: ${input.values}`);
|
|
}
|
|
lines.push(` description: "${input.description}"`);
|
|
}
|
|
}
|
|
|
|
lines.push('---');
|
|
lines.push('');
|
|
lines.push(tool.body);
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export async function syncMarketplaceTools(): Promise<void> {
|
|
try {
|
|
const res = await fetch(`${MARKETPLACE_URL}/api/tools/native`);
|
|
if (!res.ok) {
|
|
console.error(`[marketplace] Failed to fetch tools: ${res.status} ${res.statusText}`);
|
|
return;
|
|
}
|
|
|
|
const data = (await res.json()) as MarketplaceResponse;
|
|
|
|
mkdirSync(GLOBAL_TOOLS_DIR, { recursive: true });
|
|
|
|
const allTools: MarketplaceTool[] = [];
|
|
for (const category of data.categories) {
|
|
allTools.push(...category.tools);
|
|
}
|
|
if (data.uncategorized) {
|
|
allTools.push(...data.uncategorized);
|
|
}
|
|
|
|
for (const tool of allTools) {
|
|
const targetDir = join(GLOBAL_TOOLS_DIR, tool.dirName);
|
|
const targetToolMd = join(targetDir, 'TOOL.md');
|
|
|
|
if (existsSync(targetToolMd)) {
|
|
const existingVersion = parseSeedVersion(readFileSync(targetToolMd, 'utf-8'));
|
|
if (tool.version <= existingVersion) continue;
|
|
}
|
|
|
|
mkdirSync(targetDir, { recursive: true });
|
|
writeFileSync(targetToolMd, buildToolMd(tool), 'utf-8');
|
|
writeFileSync(join(targetDir, 'index.ts'), tool.implementation, 'utf-8');
|
|
|
|
console.log(`[marketplace] Synced tool: ${tool.dirName} (v${tool.version})`);
|
|
}
|
|
} catch (err) {
|
|
console.error('[marketplace] Failed to sync tools:', err instanceof Error ? err.message : err);
|
|
}
|
|
}
|