Files
asin-check/src/index.ts
Victor Noguera 95cebaa27c feat: add support for Claude LLM integration across multiple modules
- Introduced `useClaude` option in `AnalysisPipelineOptions` to toggle Claude LLM usage.
- Updated `processProductChunk` and `analyzeProducts` functions to accept and handle `useClaude` parameter.
- Modified argument parsing in various scripts (`bestsellers-by-category`, `mid-range-sellers-by-category`, `top-monthly-sold-by-category`, etc.) to include `--claude` flag.
- Enhanced `analyzeProductsInternal` to differentiate between LLM providers and handle requests to Claude API.
- Added error handling for Claude API responses and ensured proper configuration for using Claude.
- Updated documentation and usage messages to reflect the new `--claude` flag.
2026-05-21 19:57:46 -04:00

170 lines
4.7 KiB
TypeScript

import { readProducts } from "./reader.ts";
import { connectCache, disconnectCache } from "./cache.ts";
import {
printResults,
writeResultsToDb,
writeResultsWorkbook,
} from "./writer.ts";
import { initDb, closeDb } from "./database.ts";
import {
chunkArray,
processProductChunk,
type SellabilityFilter,
} from "./analysis-pipeline.ts";
import path from "node:path";
import type { AnalysisResult } from "./types.ts";
const DB_PATH = process.env.RESULTS_DB_PATH || path.join("db", "results.db");
const INPUT_BATCH_SIZE = 50;
function parseSellabilityArg(args: string[]): SellabilityFilter {
const sellabilityArg = args.find((a) => a.startsWith("--sellability="));
const sellabilityValueFromEquals = sellabilityArg?.split("=")[1];
const sellabilityIdx = args.indexOf("--sellability");
const sellabilityValueFromNext =
sellabilityIdx !== -1 ? args[sellabilityIdx + 1] : undefined;
const rawSellability = sellabilityValueFromEquals ?? sellabilityValueFromNext;
if (!rawSellability) return "available";
const normalized = rawSellability.toLowerCase();
if (normalized === "available" || normalized === "all") {
return normalized;
}
console.error(
`Invalid --sellability value: \"${rawSellability}\". Use \"available\" or \"all\".`,
);
process.exit(1);
}
function parseArgs(): {
inputFile: string;
outputFile?: string;
sellability: SellabilityFilter;
useClaude: boolean;
} {
const args = process.argv.slice(2);
const outputFile = readFlagValue(args, "--out", "--output");
const useClaude = args.includes("--claude");
const inputFile = readInputFileArg(
args,
"--out",
"--output",
"--sellability",
);
const sellability = parseSellabilityArg(args);
if (!inputFile) {
console.error(
"Usage: bun run src/index.ts <input.csv|xlsx> [--out results.xlsx|--output results.xlsx] [--sellability available|all] [--claude]",
);
process.exit(1);
}
return { inputFile, outputFile, sellability, useClaude };
}
function readFlagValue(args: string[], ...flags: string[]): string | undefined {
for (const flag of flags) {
const equalsArg = args.find((arg) => arg.startsWith(`${flag}=`));
if (equalsArg) {
const value = equalsArg.slice(flag.length + 1);
if (value) return value;
}
const flagIdx = args.indexOf(flag);
if (flagIdx !== -1) {
return args[flagIdx + 1];
}
}
return undefined;
}
function readInputFileArg(
args: string[],
...flagsWithValues: string[]
): string | undefined {
for (let i = 0; i < args.length; i++) {
const arg = args[i]!;
if (flagsWithValues.includes(arg)) {
i++;
continue;
}
if (flagsWithValues.some((flag) => arg.startsWith(`${flag}=`))) {
continue;
}
if (!arg.startsWith("--")) {
return arg;
}
}
return undefined;
}
function resolveBaseOutputPath(inputFile: string, outputFile?: string): string {
if (outputFile) return outputFile;
const parsedInput = path.parse(inputFile);
return path.join("output", `${parsedInput.name}_results.xlsx`);
}
async function main() {
const { inputFile, outputFile, sellability, useClaude } = parseArgs();
console.log(`Sellability filter: ${sellability}`);
console.log(`LLM provider: ${useClaude ? "claude" : "local"}`);
console.log("Connecting to Redis...");
await connectCache();
console.log("Initializing SQLite database...");
initDb(DB_PATH);
try {
console.log(`\nReading ${inputFile}...`);
const products = readProducts(inputFile);
if (products.length === 0) {
console.error("No valid products found in input file.");
process.exit(1);
}
const productChunks = chunkArray(products, INPUT_BATCH_SIZE);
const allResults: AnalysisResult[] = [];
const resolvedBaseOutputPath = resolveBaseOutputPath(inputFile, outputFile);
if (productChunks.length > 1) {
console.log(
`\nLarge input detected (${products.length} products). Processing in chunks of ${INPUT_BATCH_SIZE}.`,
);
console.log(`Output base path: ${resolvedBaseOutputPath}`);
}
for (let chunkIndex = 0; chunkIndex < productChunks.length; chunkIndex++) {
const chunk = productChunks[chunkIndex]!;
console.log(
`\n=== Input chunk ${chunkIndex + 1}/${productChunks.length} (${chunk.length} products) ===`,
);
const chunkResults = await processProductChunk(chunk, {
sellability,
useClaude,
});
allResults.push(...chunkResults);
}
printResults(allResults);
writeResultsWorkbook(allResults, resolvedBaseOutputPath);
writeResultsToDb(allResults, DB_PATH, inputFile, resolvedBaseOutputPath);
} finally {
await disconnectCache();
closeDb();
}
}
main().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});