()
| 588 | // --------------------------------------------------------------------------- |
| 589 | |
| 590 | async function main() { |
| 591 | const args = process.argv.slice(2); |
| 592 | const dryRun = args.includes("--dry-run"); |
| 593 | const newOnly = args.includes("--new-only"); |
| 594 | |
| 595 | // Resolve API key |
| 596 | const apiKeyArg = args.find((a) => a.startsWith("--api-key")); |
| 597 | const apiKey = |
| 598 | (apiKeyArg?.includes("=") ? apiKeyArg.split("=")[1] : args[args.indexOf(apiKeyArg!) + 1]) ?? |
| 599 | process.env.DIGITALOCEAN_API_TOKEN; |
| 600 | |
| 601 | if (!apiKey) { |
| 602 | console.error("Error: DIGITALOCEAN_API_TOKEN is required (or pass --api-key=<key>)"); |
| 603 | console.error("Get one from: https://cloud.digitalocean.com/account/api/tokens"); |
| 604 | process.exit(1); |
| 605 | } |
| 606 | |
| 607 | const modelsDir = path.join(import.meta.dirname, "..", "..", "..", "providers", "digitalocean", "models"); |
| 608 | |
| 609 | const prefix = dryRun ? "[DRY RUN] " : ""; |
| 610 | console.log(`${prefix}Fetching DigitalOcean models from API...`); |
| 611 | |
| 612 | // Fetch both APIs in parallel |
| 613 | const [modelsRes, pricingRes] = await Promise.all([ |
| 614 | fetch(MODELS_API, { headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" } }), |
| 615 | fetch(PRICING_API, { headers: { "User-Agent": "models.dev/digitalocean-sync" } }), |
| 616 | ]); |
| 617 | |
| 618 | if (!modelsRes.ok) { |
| 619 | console.error(`Failed to fetch models API: ${modelsRes.status} ${modelsRes.statusText}`); |
| 620 | if (modelsRes.status === 401 || modelsRes.status === 403) |
| 621 | console.error("Check your DIGITALOCEAN_API_TOKEN has read access."); |
| 622 | process.exit(1); |
| 623 | } |
| 624 | |
| 625 | if (!pricingRes.ok) { |
| 626 | console.error(`Failed to fetch pricing API: ${pricingRes.status} ${pricingRes.statusText}`); |
| 627 | process.exit(1); |
| 628 | } |
| 629 | |
| 630 | const modelsParsed = DoModelsResponse.safeParse(await modelsRes.json()); |
| 631 | if (!modelsParsed.success) { |
| 632 | console.error("Unexpected models API response:", modelsParsed.error.errors); |
| 633 | process.exit(1); |
| 634 | } |
| 635 | |
| 636 | const pricingParsed = StaticContentResponse.safeParse(await pricingRes.json()); |
| 637 | if (!pricingParsed.success) { |
| 638 | console.error("Unexpected pricing API response:", pricingParsed.error.errors); |
| 639 | process.exit(1); |
| 640 | } |
| 641 | |
| 642 | const apiModels = modelsParsed.data.models; |
| 643 | const pricingMap = buildPricingMap(pricingParsed.data.gradient.models); |
| 644 | |
| 645 | // Collect existing TOML filenames for orphan detection |
| 646 | const existingFiles = new Set<string>(); |
| 647 | for await (const file of new Bun.Glob("**/*.toml").scan({ cwd: modelsDir, absolute: false })) { |
no test coverage detected