From 27b960b4cfa1b68d26448e9125a9f4554cc6ba88 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:43:48 -0400 Subject: [PATCH] feat: add predict and predictions commands recoup predict --url --modality runs a neural engagement prediction. recoup predictions list/get manages history. Made-with: Cursor --- src/bin.ts | 4 ++ src/commands/predict.ts | 67 ++++++++++++++++++++++++++ src/commands/predictions.ts | 96 +++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 src/commands/predict.ts create mode 100644 src/commands/predictions.ts diff --git a/src/bin.ts b/src/bin.ts index 5608963..3f6426f 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -10,6 +10,8 @@ import { notificationsCommand } from "./commands/notifications.js"; import { orgsCommand } from "./commands/orgs.js"; import { contentCommand } from "./commands/content.js"; import { tasksCommand } from "./commands/tasks.js"; +import { predictCommand } from "./commands/predict.js"; +import { predictionsCommand } from "./commands/predictions.js"; const pkgPath = join(__dirname, "..", "package.json"); const { version } = JSON.parse(readFileSync(pkgPath, "utf-8")); @@ -30,5 +32,7 @@ program.addCommand(sandboxesCommand); program.addCommand(orgsCommand); program.addCommand(tasksCommand); program.addCommand(contentCommand); +program.addCommand(predictCommand); +program.addCommand(predictionsCommand); program.parse(); diff --git a/src/commands/predict.ts b/src/commands/predict.ts new file mode 100644 index 0000000..71152b4 --- /dev/null +++ b/src/commands/predict.ts @@ -0,0 +1,67 @@ +import { Command } from "commander"; +import { post } from "../client.js"; +import { printJson, printError } from "../output.js"; + +/** + * `recoup predict` — run a neural engagement prediction on content. + * + * Sends the file URL and modality to POST /api/predictions, then + * displays the engagement score, peak moments, and weak spots. + */ +export const predictCommand = new Command("predict") + .description("Run a neural engagement prediction on video, audio, or text content") + .requiredOption("--url ", "Public URL to the content file") + .requiredOption( + "--modality ", + "Content type: video, audio, or text", + ) + .option("--json", "Output as JSON") + .action(async (opts) => { + try { + const data = await post("/api/predictions", { + file_url: opts.url, + modality: opts.modality, + }); + + if (opts.json) { + printJson(data); + return; + } + + console.log(); + console.log(` Engagement Score: ${data.engagement_score}`); + console.log(` Modality: ${data.modality}`); + console.log( + ` Duration: ${data.total_duration_seconds}s`, + ); + console.log(` Inference Time: ${data.elapsed_seconds}s`); + console.log(); + + const peaks = data.peak_moments as + | { time_seconds: number; score: number }[] + | undefined; + if (peaks && peaks.length > 0) { + console.log(" Peak Moments:"); + for (const p of peaks) { + console.log(` ${p.time_seconds}s → ${p.score}`); + } + console.log(); + } + + const weak = data.weak_spots as + | { time_seconds: number; score: number }[] + | undefined; + if (weak && weak.length > 0) { + console.log(" Weak Spots:"); + for (const w of weak) { + console.log(` ${w.time_seconds}s → ${w.score}`); + } + console.log(); + } + + console.log(` ID: ${data.id}`); + console.log(); + } catch (err) { + printError((err as Error).message); + } + }); diff --git a/src/commands/predictions.ts b/src/commands/predictions.ts new file mode 100644 index 0000000..35165ef --- /dev/null +++ b/src/commands/predictions.ts @@ -0,0 +1,96 @@ +import { Command } from "commander"; +import { get } from "../client.js"; +import { printJson, printTable, printError } from "../output.js"; + +const listCommand = new Command("list") + .description("List past engagement predictions") + .option("--json", "Output as JSON") + .option("--limit ", "Maximum results to return", "20") + .option("--offset ", "Number of results to skip", "0") + .action(async (opts) => { + try { + const params: Record = {}; + if (opts.limit) params.limit = opts.limit; + if (opts.offset) params.offset = opts.offset; + + const data = await get("/api/predictions", params); + const predictions = + (data.predictions as Record[]) || []; + + if (opts.json) { + printJson(predictions); + } else { + printTable(predictions, [ + { key: "id", label: "ID" }, + { key: "modality", label: "MODALITY" }, + { key: "engagement_score", label: "SCORE" }, + { key: "created_at", label: "CREATED" }, + ]); + } + } catch (err) { + printError((err as Error).message); + } + }); + +const getCommand = new Command("get") + .description("Get a specific prediction by ID") + .argument("", "Prediction UUID") + .option("--json", "Output as JSON") + .action(async (id: string, opts) => { + try { + const data = await get(`/api/predictions/${id}`); + + if (opts.json) { + printJson(data); + return; + } + + console.log(); + console.log(` ID: ${data.id}`); + console.log(` Engagement Score: ${data.engagement_score}`); + console.log(` Modality: ${data.modality}`); + console.log(` File URL: ${data.file_url}`); + console.log( + ` Duration: ${data.total_duration_seconds}s`, + ); + console.log(` Inference Time: ${data.elapsed_seconds}s`); + console.log(` Created: ${data.created_at}`); + console.log(); + + const peaks = data.peak_moments as + | { time_seconds: number; score: number }[] + | undefined; + if (peaks && peaks.length > 0) { + console.log(" Peak Moments:"); + for (const p of peaks) { + console.log(` ${p.time_seconds}s → ${p.score}`); + } + console.log(); + } + + const weak = data.weak_spots as + | { time_seconds: number; score: number }[] + | undefined; + if (weak && weak.length > 0) { + console.log(" Weak Spots:"); + for (const w of weak) { + console.log(` ${w.time_seconds}s → ${w.score}`); + } + console.log(); + } + } catch (err) { + printError((err as Error).message); + } + }); + +/** + * `recoup predictions` — manage engagement prediction history. + * + * Subcommands: + * list — List past predictions with scores + * get — Get full prediction detail by ID + */ +export const predictionsCommand = new Command("predictions") + .description("Manage engagement prediction history") + .addCommand(listCommand) + .addCommand(getCommand);