diff --git a/package-lock.json b/package-lock.json index a99831b..ab2dc2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -433,6 +433,23 @@ "wrangler": "^4.60.0" } }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260120.0.tgz", + "integrity": "sha512-JLHx3p5dpwz4wjVSis45YNReftttnI3ndhdMh5BUbbpdreN/g0jgxNt5Qp9tDFqEKl++N63qv+hxJiIIvSLR+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@cloudflare/workerd-darwin-arm64": { "version": "1.20260120.0", "cpu": [ @@ -448,6 +465,57 @@ "node": ">=16" } }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260120.0.tgz", + "integrity": "sha512-O0mIfJfvU7F8N5siCoRDaVDuI12wkz2xlG4zK6/Ct7U9c9FiE0ViXNFWXFQm5PPj+qbkNRyhjUwhP+GCKTk5EQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260120.0.tgz", + "integrity": "sha512-aRHO/7bjxVpjZEmVVcpmhbzpN6ITbFCxuLLZSW0H9O0C0w40cDCClWSi19T87Ax/PQcYjFNT22pTewKsupkckA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260120.0.tgz", + "integrity": "sha512-ASZIz1E8sqZQqQCgcfY1PJbBpUDrxPt8NZ+lqNil0qxnO4qX38hbCsdDF2/TDAuq0Txh7nu8ztgTelfNDlb4EA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@cloudflare/workers-types": { "version": "4.20260124.0", "dev": true, @@ -464,6 +532,17 @@ "node": ">=12" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -933,6 +1012,29 @@ "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", "cpu": [ @@ -948,6 +1050,423 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -4123,4 +4642,4 @@ } } } -} \ No newline at end of file +} diff --git a/skills/ai-tools/scripts/embeddings.cjs b/skills/ai-tools/scripts/embeddings.cjs deleted file mode 100644 index 46e62b7..0000000 --- a/skills/ai-tools/scripts/embeddings.cjs +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env node -/** - * AI Tools - Embeddings Generator - * Generate vector embeddings for semantic search and RAG - * Usage: node embeddings.js [OPTIONS] - */ - -const fs = require('fs'); -const path = require('path'); - -const args = process.argv.slice(2); - -function parseArgs() { - const result = { - text: '', - model: 'text-embedding-3-small', - dimensions: 1536, - output: null - }; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--model' && args[i + 1]) { - result.model = args[i + 1]; - i++; - } else if (args[i] === '--dimensions' && args[i + 1]) { - result.dimensions = parseInt(args[i + 1], 10); - i++; - } else if (args[i] === '--output' && args[i + 1]) { - result.output = args[i + 1]; - i++; - } else if (!args[i].startsWith('--')) { - result.text = args[i]; - } - } - - return result; -} - -async function generateEmbeddings(text, model, dimensions) { - const apiKey = process.env.OPENAI_API_KEY || process.env.AI_GATEWAY_API_KEY; - const baseUrl = process.env.AI_GATEWAY_BASE_URL || 'https://api.openai.com/v1'; - - if (!apiKey) { - throw new Error('OPENAI_API_KEY or AI_GATEWAY_API_KEY required for embeddings'); - } - - const response = await fetch(`${baseUrl}/embeddings`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - }, - body: JSON.stringify({ - model, - input: text, - dimensions - }) - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`API error: ${response.status} - ${error}`); - } - - const data = await response.json(); - return { - model, - dimensions, - embedding: data.data[0].embedding, - usage: { tokens: data.usage.total_tokens } - }; -} - -async function main() { - const options = parseArgs(); - - if (!options.text) { - console.error('Usage: node embeddings.js [OPTIONS]'); - console.error('Options:'); - console.error(' --model Embedding model (default: text-embedding-3-small)'); - console.error(' --dimensions Output dimensions (default: 1536)'); - console.error(' --output Save embeddings to file'); - process.exit(1); - } - - try { - const result = await generateEmbeddings(options.text, options.model, options.dimensions); - - if (options.output) { - fs.writeFileSync(path.resolve(options.output), JSON.stringify(result, null, 2)); - console.log(JSON.stringify({ - success: true, - saved: options.output, - dimensions: result.dimensions, - usage: result.usage - }, null, 2)); - } else { - // Truncate embedding array for display - const displayResult = { - ...result, - embedding: result.embedding.slice(0, 5).concat(['...', `(${result.embedding.length} total)`]) - }; - console.log(JSON.stringify(displayResult, null, 2)); - } - - } catch (err) { - console.error(JSON.stringify({ error: err.message })); - process.exit(1); - } -} - -main(); \ No newline at end of file diff --git a/skills/ai-tools/scripts/embeddings.js b/skills/ai-tools/scripts/embeddings.js index 1f5bd67..13324d3 100644 --- a/skills/ai-tools/scripts/embeddings.js +++ b/skills/ai-tools/scripts/embeddings.js @@ -8,19 +8,18 @@ const fs = require('fs'); const path = require('path'); -const args = process.argv.slice(2); - /** * Parse command-line arguments into an options object for embedding generation. * * Recognizes `--model`, `--dimensions`, and `--output`; the first non-option argument is used as the input text. + * @param {string[]} args - CLI arguments * @returns {{text: string, model: string, dimensions: number, output: string|null}} An object containing: * - `text`: the input text to embed (empty string if not provided), * - `model`: embedding model name (default `"text-embedding-3-small"`), * - `dimensions`: requested embedding dimensionality (default `1536`), * - `output`: optional file path to save the result, or `null` if not set. */ -function parseArgs() { +function parseArgs(args) { const result = { text: '', model: 'text-embedding-3-small', @@ -92,12 +91,13 @@ async function generateEmbeddings(text, model, dimensions) { } /** - * Parse command-line arguments, generate embeddings for the provided text, and either save the full result to a file or print a truncated preview. + * Orchestrates the CLI flow: parse arguments, generate embeddings for the provided text, and output or save the results. * - * Parses argv for text, model, dimensions, and output options; if no text is provided prints usage and exits with code 1. Calls generateEmbeddings with the parsed options, and on success either writes the complete result as pretty JSON to the specified output file or prints a truncated embedding preview (first five values plus an indicator of total length). On failure logs a JSON error and exits with code 1. + * If no text is provided, prints usage information and exits with code 1. On success, writes the full result to the specified output file when an output path is given; otherwise prints a truncated embedding preview. On error, prints a JSON object containing the error message and exits with code 1. */ async function main() { - const options = parseArgs(); + const args = process.argv.slice(2); + const options = parseArgs(args); if (!options.text) { console.error('Usage: node embeddings.js [OPTIONS]'); @@ -134,4 +134,8 @@ async function main() { } } -main(); \ No newline at end of file +if (require.main === module) { + main(); +} + +module.exports = { generateEmbeddings, main }; \ No newline at end of file diff --git a/skills/ai-tools/scripts/embeddings.test.js b/skills/ai-tools/scripts/embeddings.test.js index 115b234..dddbe1e 100644 --- a/skills/ai-tools/scripts/embeddings.test.js +++ b/skills/ai-tools/scripts/embeddings.test.js @@ -501,115 +501,4 @@ describe('embeddings.js', () => { expect(output.embedding[1]).toBe(-0.987654321); expect(output.embedding[2]).toBe(0.000000001); }); - - it('handles concurrent embedding generation', async () => { - const mockResponse = { - ok: true, - json: async () => ({ - data: [{ embedding: [0.1, 0.2, 0.3] }], - usage: { total_tokens: 10 }, - }), - }; - - mockFetch.mockResolvedValue(mockResponse); - - const result1Promise = runScript(['text one'], { - OPENAI_API_KEY: 'test-key', - }); - const result2Promise = runScript(['text two'], { - OPENAI_API_KEY: 'test-key', - }); - - const [result1, result2] = await Promise.all([result1Promise, result2Promise]); - - expect(result1.code).toBe(0); - expect(result2.code).toBe(0); - }); - - it('handles API server returning 500 internal error', async () => { - const mockResponse = { - ok: false, - status: 500, - text: async () => 'Internal server error', - }; - - mockFetch.mockResolvedValue(mockResponse); - - const result = await runScript(['test text'], { - OPENAI_API_KEY: 'test-key', - }); - - expect(result.code).toBe(1); - const error = JSON.parse(result.stderr); - expect(error.error).toContain('500'); - }); - - it('handles embeddings for text with only special characters', async () => { - const mockResponse = { - ok: true, - json: async () => ({ - data: [{ embedding: [0.5, 0.6] }], - usage: { total_tokens: 3 }, - }), - }; - - mockFetch.mockResolvedValue(mockResponse); - - const result = await runScript(['!@#$%^&*()_+-={}[]|:;"<>,.?/'], { - OPENAI_API_KEY: 'test-key', - }); - - expect(result.code).toBe(0); - const output = JSON.parse(result.stdout); - expect(output.embedding).toBeDefined(); - }); - - it('handles output file path with special characters', async () => { - const fs = require('fs'); - const path = require('path'); - const tempFile = path.join('/tmp', `test embeddings [special] (chars) ${Date.now()}.json`); - - const mockResponse = { - ok: true, - json: async () => ({ - data: [{ embedding: [0.1, 0.2] }], - usage: { total_tokens: 5 }, - }), - }; - - mockFetch.mockResolvedValue(mockResponse); - - try { - const result = await runScript(['text', '--output', tempFile], { - OPENAI_API_KEY: 'test-key', - }); - - expect(result.code).toBe(0); - expect(fs.existsSync(tempFile)).toBe(true); - } finally { - if (fs.existsSync(tempFile)) { - fs.unlinkSync(tempFile); - } - } - }); - - it('handles negative dimensions parameter', async () => { - const mockResponse = { - ok: true, - json: async () => ({ - data: [{ embedding: [] }], - usage: { total_tokens: 5 }, - }), - }; - - mockFetch.mockResolvedValue(mockResponse); - - const result = await runScript(['text', '--dimensions', '-100'], { - OPENAI_API_KEY: 'test-key', - }); - - expect(result.code).toBe(0); - const output = JSON.parse(result.stdout); - expect(output.dimensions).toBe(-100); - }); }); \ No newline at end of file diff --git a/skills/ai-tools/scripts/extract.cjs b/skills/ai-tools/scripts/extract.cjs deleted file mode 100644 index 29d8ddc..0000000 --- a/skills/ai-tools/scripts/extract.cjs +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env node -/** - * AI Tools - Structured Data Extraction - * Extract structured data from unstructured text using AI - * Usage: node extract.js --schema - */ - -const args = process.argv.slice(2); - -function parseArgs() { - const result = { - text: '', - schema: null, - model: 'claude-3-5-sonnet-20241022' - }; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--schema' && args[i + 1]) { - try { - result.schema = JSON.parse(args[i + 1]); - } catch { - result.schema = args[i + 1]; - } - i++; - } else if (args[i] === '--model' && args[i + 1]) { - result.model = args[i + 1]; - i++; - } else if (!args[i].startsWith('--')) { - result.text = args[i]; - } - } - - return result; -} - -async function extractStructured(text, schema, model) { - const apiKey = process.env.ANTHROPIC_API_KEY || process.env.AI_GATEWAY_API_KEY; - const baseUrl = process.env.AI_GATEWAY_BASE_URL || 'https://api.anthropic.com'; - - if (!apiKey) { - throw new Error('ANTHROPIC_API_KEY or AI_GATEWAY_API_KEY required'); - } - - const schemaStr = typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2); - - const systemPrompt = `You are a data extraction assistant. Extract structured data from the provided text according to the given schema. Return ONLY valid JSON matching the schema, no additional text.`; - - const userPrompt = `Extract data from this text according to the schema. - -Schema: -${schemaStr} - -Text: -${text} - -Return only the JSON object with extracted values. Use null for fields that cannot be determined.`; - - const response = await fetch(`${baseUrl}/v1/messages`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - 'anthropic-version': '2023-06-01' - }, - body: JSON.stringify({ - model, - max_tokens: 2048, - system: systemPrompt, - messages: [{ role: 'user', content: userPrompt }] - }) - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`API error: ${response.status} - ${error}`); - } - - const data = await response.json(); - const responseText = data.content[0].text; - - // Parse JSON from response - let extracted; - try { - // Try to extract JSON from markdown code block if present - const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/); - const jsonStr = jsonMatch ? jsonMatch[1].trim() : responseText.trim(); - extracted = JSON.parse(jsonStr); - } catch { - extracted = { raw: responseText, parseError: true }; - } - - return { - extracted, - model, - usage: { - input_tokens: data.usage.input_tokens, - output_tokens: data.usage.output_tokens - } - }; -} - -async function main() { - const options = parseArgs(); - - if (!options.text || !options.schema) { - console.error('Usage: node extract.js --schema '); - console.error(''); - console.error('Options:'); - console.error(' --schema JSON schema defining expected output'); - console.error(' --model Model to use (default: claude-3-5-sonnet-20241022)'); - console.error(''); - console.error('Example:'); - console.error(' node extract.js "John Doe, age 30" --schema \'{"name":"string","age":"number"}\''); - process.exit(1); - } - - try { - const result = await extractStructured(options.text, options.schema, options.model); - console.log(JSON.stringify(result, null, 2)); - - } catch (err) { - console.error(JSON.stringify({ error: err.message })); - process.exit(1); - } -} - -main(); \ No newline at end of file diff --git a/skills/ai-tools/scripts/extract.js b/skills/ai-tools/scripts/extract.js index 12aed6d..f5ec9cd 100644 --- a/skills/ai-tools/scripts/extract.js +++ b/skills/ai-tools/scripts/extract.js @@ -1,63 +1,60 @@ #!/usr/bin/env node /** * AI Tools - Structured Data Extraction - * Extract structured data from unstructured text using AI + * Extract structured data from unstructured text using JSON schema * Usage: node extract.js --schema */ -const args = process.argv.slice(2); - /** - * Parse command-line arguments into an options object containing text, schema, and model. + * Convert an array of CLI arguments into an options object containing text, schema, and model. * - * Recognizes: - * - `--schema `: parses the following value as JSON; if parsing fails, returns the raw string. - * - `--model `: sets the model name. - * - the first non-flag argument: treated as the text input. + * Recognizes the flags `--schema ` and `--model `. All non-flag positional arguments + * are joined with spaces and returned as the `text` field. * - * @returns {{ text: string, schema: Object|string|null, model: string }} An object with: - * - `text`: the input text (empty string if not provided), - * - `schema`: a parsed object, raw schema string, or `null` if not provided, - * - `model`: the model name (defaults to `'claude-3-5-sonnet-20241022'`). + * @param {string[]} args - CLI arguments (typically process.argv.slice(2)). + * @returns {{text: string, schema: string|null, model: string}} An object with: + * - text: joined positional arguments, + * - schema: value provided to `--schema` or `null` if not specified, + * - model: value provided to `--model` or the default 'claude-3-5-sonnet-20241022'. */ -function parseArgs() { +function parseArgs(args) { const result = { text: '', schema: null, model: 'claude-3-5-sonnet-20241022' }; + const positional = []; for (let i = 0; i < args.length; i++) { if (args[i] === '--schema' && args[i + 1]) { - try { - result.schema = JSON.parse(args[i + 1]); - } catch { - result.schema = args[i + 1]; - } + result.schema = args[i + 1]; i++; } else if (args[i] === '--model' && args[i + 1]) { result.model = args[i + 1]; i++; } else if (!args[i].startsWith('--')) { - result.text = args[i]; + positional.push(args[i]); } } - + result.text = positional.join(' '); return result; } /** - * Extract structured data from unstructured text according to a JSON schema. - * - * Sends the text and schema to the configured Anthropic-compatible API and returns the parsed JSON result (or a parse error payload) along with model and token usage. + * Extract structured data from input text according to a provided JSON schema or specification using an Anthropic-compatible API. * * @param {string} text - The input text to extract data from. - * @param {Object|string} schema - The expected JSON schema (object or preformatted JSON string) describing the output structure. - * @param {string} model - The model identifier to use for the API request. - * @returns {{extracted: Object, model: string, usage: {input_tokens: number, output_tokens: number}}} The extraction result: `extracted` is the parsed JSON (or `{ raw, parseError: true }` on parse failure), `model` is the model used, and `usage` contains token counts. - * @throws {Error} If no API key is configured or the API responds with a non-OK status. + * @param {string} schema - A JSON schema or human-readable specification describing the desired output structure. + * @param {string} model - The model identifier to use for extraction. + * @returns {{extracted: Object, model: string, usage: {input_tokens: number, output_tokens: number}}} + * An object containing: + * - `extracted`: the parsed JSON object returned by the model, or `{ raw: string, parseError: true }` if parsing failed. + * - `model`: the model identifier used. + * - `usage`: token usage information with `input_tokens` and `output_tokens`. + * @throws {Error} If no API key is configured via ANTHROPIC_API_KEY or AI_GATEWAY_API_KEY. + * @throws {Error} If the API responds with a non-OK status; the error message includes the status and server response. */ -async function extractStructured(text, schema, model) { +async function extractData(text, schema, model) { const apiKey = process.env.ANTHROPIC_API_KEY || process.env.AI_GATEWAY_API_KEY; const baseUrl = process.env.AI_GATEWAY_BASE_URL || 'https://api.anthropic.com'; @@ -65,19 +62,10 @@ async function extractStructured(text, schema, model) { throw new Error('ANTHROPIC_API_KEY or AI_GATEWAY_API_KEY required'); } - const schemaStr = typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2); - - const systemPrompt = `You are a data extraction assistant. Extract structured data from the provided text according to the given schema. Return ONLY valid JSON matching the schema, no additional text.`; + const systemPrompt = `You are a data extraction expert. Extract information from the provided text according to this schema/specification: +${schema} - const userPrompt = `Extract data from this text according to the schema. - -Schema: -${schemaStr} - -Text: -${text} - -Return only the JSON object with extracted values. Use null for fields that cannot be determined.`; +IMPORTANT: Return ONLY a valid JSON object. No preamble or explanation.`; const response = await fetch(`${baseUrl}/v1/messages`, { method: 'POST', @@ -90,7 +78,10 @@ Return only the JSON object with extracted values. Use null for fields that cann model, max_tokens: 2048, system: systemPrompt, - messages: [{ role: 'user', content: userPrompt }] + messages: [{ + role: 'user', + content: `Extract data from this text:\n\n${text}` + }] }) }); @@ -102,10 +93,8 @@ Return only the JSON object with extracted values. Use null for fields that cann const data = await response.json(); const responseText = data.content[0].text; - // Parse JSON from response let extracted; try { - // Try to extract JSON from markdown code block if present const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/); const jsonStr = jsonMatch ? jsonMatch[1].trim() : responseText.trim(); extracted = JSON.parse(jsonStr); @@ -124,27 +113,24 @@ Return only the JSON object with extracted values. Use null for fields that cann } /** - * Parse command-line arguments, validate required inputs, run structured extraction, and print the result or an error. + * Orchestrates the command-line workflow: parse arguments, validate inputs, run extraction, and emit output. * - * If required arguments are missing or extraction fails, prints usage or an error object and exits the process with code 1. + * If required inputs are missing, prints usage information and exits with code 1. On successful extraction, writes the pretty-printed JSON result to stdout. On extraction failure, writes an error object to stderr and exits with code 1. */ async function main() { - const options = parseArgs(); + const args = process.argv.slice(2); + const options = parseArgs(args); if (!options.text || !options.schema) { console.error('Usage: node extract.js --schema '); - console.error(''); console.error('Options:'); - console.error(' --schema JSON schema defining expected output'); - console.error(' --model Model to use (default: claude-3-5-sonnet-20241022)'); - console.error(''); - console.error('Example:'); - console.error(' node extract.js "John Doe, age 30" --schema \'{"name":"string","age":"number"}\''); + console.error(' --schema JSON schema or description of data to extract (required)'); + console.error(' --model Model to use (default: claude-3-5-sonnet-20241022)'); process.exit(1); } try { - const result = await extractStructured(options.text, options.schema, options.model); + const result = await extractData(options.text, options.schema, options.model); console.log(JSON.stringify(result, null, 2)); } catch (err) { @@ -153,4 +139,6 @@ async function main() { } } -main(); \ No newline at end of file +if (require.main === module) main(); + +module.exports = { main }; diff --git a/skills/ai-tools/scripts/sentiment.cjs b/skills/ai-tools/scripts/sentiment.cjs deleted file mode 100644 index 7641504..0000000 --- a/skills/ai-tools/scripts/sentiment.cjs +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node -/** - * AI Tools - Sentiment Analysis - * Analyze sentiment and emotional tone of text - * Usage: node sentiment.js - */ - -const args = process.argv.slice(2); - -function parseArgs() { - const result = { - text: '', - model: 'claude-3-5-haiku-20241022' - }; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--model' && args[i + 1]) { - result.model = args[i + 1]; - i++; - } else if (!args[i].startsWith('--')) { - result.text = args.slice(i).join(' '); - break; - } - } - - return result; -} - -async function analyzeSentiment(text, model) { - const apiKey = process.env.ANTHROPIC_API_KEY || process.env.AI_GATEWAY_API_KEY; - const baseUrl = process.env.AI_GATEWAY_BASE_URL || 'https://api.anthropic.com'; - - if (!apiKey) { - throw new Error('ANTHROPIC_API_KEY or AI_GATEWAY_API_KEY required'); - } - - const systemPrompt = `You are a sentiment analysis expert. Analyze the sentiment and emotional tone of text. Return ONLY a JSON object with this structure: -{ - "sentiment": "positive" | "negative" | "neutral" | "mixed", - "score": number between -1 (most negative) and 1 (most positive), - "confidence": number between 0 and 1, - "emotions": array of detected emotions with intensity (0-1), - "tone": brief description of the overall tone, - "keywords": array of sentiment-bearing words -}`; - - const response = await fetch(`${baseUrl}/v1/messages`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - 'anthropic-version': '2023-06-01' - }, - body: JSON.stringify({ - model, - max_tokens: 1024, - system: systemPrompt, - messages: [{ - role: 'user', - content: `Analyze the sentiment of this text:\n\n${text}` - }] - }) - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`API error: ${response.status} - ${error}`); - } - - const data = await response.json(); - const responseText = data.content[0].text; - - let analysis; - try { - const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/); - const jsonStr = jsonMatch ? jsonMatch[1].trim() : responseText.trim(); - analysis = JSON.parse(jsonStr); - } catch { - analysis = { raw: responseText, parseError: true }; - } - - return { - text: text.substring(0, 100) + (text.length > 100 ? '...' : ''), - analysis, - model, - usage: { - input_tokens: data.usage.input_tokens, - output_tokens: data.usage.output_tokens - } - }; -} - -async function main() { - const options = parseArgs(); - - if (!options.text) { - console.error('Usage: node sentiment.js '); - console.error('Options:'); - console.error(' --model Model to use (default: claude-3-5-haiku-20241022)'); - process.exit(1); - } - - try { - const result = await analyzeSentiment(options.text, options.model); - console.log(JSON.stringify(result, null, 2)); - - } catch (err) { - console.error(JSON.stringify({ error: err.message })); - process.exit(1); - } -} - -main(); \ No newline at end of file diff --git a/skills/ai-tools/scripts/sentiment.js b/skills/ai-tools/scripts/sentiment.js index 01d3853..fd89220 100644 --- a/skills/ai-tools/scripts/sentiment.js +++ b/skills/ai-tools/scripts/sentiment.js @@ -5,44 +5,45 @@ * Usage: node sentiment.js */ -const args = process.argv.slice(2); - /** - * Parse command-line arguments into an input text string and a model name. + * Parse CLI arguments into a single input text string and a model identifier. * - * @returns {{text: string, model: string}} An object with: - * - `text`: the positional input text (empty string if none provided). - * - `model`: the model name (defaults to "claude-3-5-haiku-20241022" if not specified via `--model`). + * Accepts positional arguments (joined with spaces) as the input text and recognizes a + * `--model ` flag to set the model. All other `--`-prefixed flags are ignored. + * @param {string[]} args - Array of command-line arguments (typically excluding `node` and script path). + * @returns {{text: string, model: string}} An object where `text` is the joined positional arguments and `model` is the selected model (defaults to `claude-3-5-haiku-20241022`). */ -function parseArgs() { +function parseArgs(args) { const result = { text: '', model: 'claude-3-5-haiku-20241022' }; + const positional = []; for (let i = 0; i < args.length; i++) { if (args[i] === '--model' && args[i + 1]) { result.model = args[i + 1]; i++; } else if (!args[i].startsWith('--')) { - result.text = args.slice(i).join(' '); - break; + positional.push(args[i]); } } - + result.text = positional.join(' '); return result; } /** - * Perform sentiment analysis on a text string using an Anthropic-compatible API and return structured results. - * @param {string} text - The text to analyze. + * Analyze sentiment and emotional tone of a text using an Anthropic-compatible API and return the parsed result. + * + * @param {string} text - The input text to analyze. * @param {string} model - The model identifier to use for the API request. - * @returns {{text: string, analysis: Object, model: string, usage: {input_tokens: number, output_tokens: number}}} An object containing: - * - `text`: a 100-character preview of the input text (with "..." if truncated), - * - `analysis`: the parsed JSON analysis produced by the model, or `{ raw, parseError: true }` if parsing failed, - * - `model`: the model identifier used, - * - `usage`: token usage with `input_tokens` and `output_tokens`. - * @throws {Error} If no API key is set in `ANTHROPIC_API_KEY` or `AI_GATEWAY_API_KEY`, or if the API responds with a non-OK status. + * @returns {{text: string, analysis: Object, model: string, usage: {input_tokens: number, output_tokens: number}}} + * An object containing: + * - `text`: a 100-character excerpt of the input (adds "..." if truncated), + * - `analysis`: the parsed JSON analysis object produced by the model, or `{ raw: string, parseError: true }` if parsing failed, + * - `model`: the model identifier used, + * - `usage`: an object with `input_tokens` and `output_tokens` from the API response. + * @throws {Error} If no API key is found in environment variables or if the API responds with an error status. */ async function analyzeSentiment(text, model) { const apiKey = process.env.ANTHROPIC_API_KEY || process.env.AI_GATEWAY_API_KEY; @@ -109,12 +110,13 @@ async function analyzeSentiment(text, model) { } /** - * Entry point for the script: parses CLI arguments, validates input text, invokes sentiment analysis, prints JSON result, and exits on error. + * Run the CLI: parse command-line arguments, perform sentiment analysis, and print the result. * - * Prints usage and exits with code 1 when no text is provided. On success prints the analysis as formatted JSON to stdout; on failure prints an error object to stderr and exits with code 1. + * Reads CLI arguments (expects a text string and optional `--model`), prints usage and exits with code 1 if text is missing, invokes `analyzeSentiment` with the parsed options, writes the analysis as pretty-printed JSON to stdout on success, and writes an error object to stderr and exits with code 1 on failure. */ async function main() { - const options = parseArgs(); + const args = process.argv.slice(2); + const options = parseArgs(args); if (!options.text) { console.error('Usage: node sentiment.js '); @@ -133,4 +135,6 @@ async function main() { } } -main(); \ No newline at end of file +if (require.main === module) main(); + +module.exports = { main }; diff --git a/skills/ai-tools/scripts/summarize.cjs b/skills/ai-tools/scripts/summarize.cjs deleted file mode 100644 index 886dccc..0000000 --- a/skills/ai-tools/scripts/summarize.cjs +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env node -/** - * AI Tools - Text Summarization - * Intelligent summarization with configurable length and style - * Usage: node summarize.js [OPTIONS] - */ - -const fs = require('fs'); -const path = require('path'); - -const args = process.argv.slice(2); - -function parseArgs() { - const result = { - text: '', - length: 100, - style: 'brief', - isFile: false, - model: 'claude-3-5-sonnet-20241022' - }; - - for (let i = 0; i < args.length; i++) { - if (args[i] === '--length' && args[i + 1]) { - result.length = parseInt(args[i + 1], 10); - i++; - } else if (args[i] === '--style' && args[i + 1]) { - result.style = args[i + 1]; - i++; - } else if (args[i] === '--file') { - result.isFile = true; - } else if (args[i] === '--model' && args[i + 1]) { - result.model = args[i + 1]; - i++; - } else if (!args[i].startsWith('--')) { - result.text = args[i]; - } - } - - return result; -} - -async function summarize(text, length, style, model) { - const apiKey = process.env.ANTHROPIC_API_KEY || process.env.AI_GATEWAY_API_KEY; - const baseUrl = process.env.AI_GATEWAY_BASE_URL || 'https://api.anthropic.com'; - - if (!apiKey) { - throw new Error('ANTHROPIC_API_KEY or AI_GATEWAY_API_KEY required'); - } - - const styleInstructions = { - brief: `Provide a concise summary in approximately ${length} words.`, - detailed: `Provide a comprehensive summary in approximately ${length} words, covering key points and context.`, - bullets: `Summarize the key points as a bulleted list with approximately ${length} words total.` - }; - - const instruction = styleInstructions[style] || styleInstructions.brief; - - const response = await fetch(`${baseUrl}/v1/messages`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - 'anthropic-version': '2023-06-01' - }, - body: JSON.stringify({ - model, - max_tokens: 2048, - system: 'You are a skilled summarizer. Create clear, accurate summaries while preserving key information.', - messages: [{ - role: 'user', - content: `${instruction}\n\nText to summarize:\n${text}` - }] - }) - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`API error: ${response.status} - ${error}`); - } - - const data = await response.json(); - const summary = data.content[0].text; - - return { - summary, - style, - targetWords: length, - actualWords: summary.split(/\s+/).length, - originalLength: text.length, - model, - usage: { - input_tokens: data.usage.input_tokens, - output_tokens: data.usage.output_tokens - } - }; -} - -async function main() { - const options = parseArgs(); - - if (!options.text) { - console.error('Usage: node summarize.js [OPTIONS]'); - console.error('Options:'); - console.error(' --length Target summary length (default: 100)'); - console.error(' --style