Docs/Skills and code/global skill groq-transcribe

~/.claude/skills/groq-transcribe/scripts/transcribe.mjs

Code80 lines3.0 KBSkill: groq-transcribe

What the file says about itself

Groq Whisper transcription with word and segment timestamps. Node 18+ built-ins only. Usage: node transcribe.mjs <audio> <out.json> [--lang hi|en|...] [--model whisper-large-v3-turbo|whisper-large-v3] [--prompt "text"] [--prompt-file path]…

// Groq Whisper transcription with word and segment timestamps.
// Node 18+ built-ins only.
//
// Usage:
//   node transcribe.mjs <audio> <out.json> [--lang hi|en|...] [--model whisper-large-v3-turbo|whisper-large-v3]
//                        [--prompt "text"] [--prompt-file path] [--no-segments]
//
// Key lookup order: GROQ_API_KEY env var, then ~/.claude/secrets/groq-api-key.txt.

import { readFile, writeFile } from "node:fs/promises";
import { basename, join } from "node:path";
import { homedir } from "node:os";

const KEY_FILE = join(homedir(), ".claude", "secrets", "groq-api-key.txt");

function parseArgs(argv) {
  const pos = [];
  const opts = { lang: "hi", model: "whisper-large-v3-turbo", prompt: null, promptFile: null, segments: true };
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === "--lang") opts.lang = argv[++i];
    else if (a === "--model") opts.model = argv[++i];
    else if (a === "--prompt") opts.prompt = argv[++i];
    else if (a === "--prompt-file") opts.promptFile = argv[++i];
    else if (a === "--no-segments") opts.segments = false;
    else pos.push(a);
  }
  return { pos, opts };
}

async function loadKey() {
  if (process.env.GROQ_API_KEY) return process.env.GROQ_API_KEY.trim();
  try {
    const k = (await readFile(KEY_FILE, "utf8")).trim();
    if (k) return k;
  } catch {}
  throw new Error(`No Groq key. Set GROQ_API_KEY or put the key in ${KEY_FILE}`);
}

const { pos, opts } = parseArgs(process.argv.slice(2));
const [audioPath, outPath] = pos;
if (!audioPath || !outPath) {
  console.error("Usage: node transcribe.mjs <audio> <out.json> [--lang hi] [--model ...] [--prompt ...] [--prompt-file ...] [--no-segments]");
  process.exit(1);
}

const key = await loadKey();
const bytes = await readFile(audioPath);
let prompt = opts.prompt;
if (opts.promptFile) prompt = (await readFile(opts.promptFile, "utf8")).trim();

const form = new FormData();
form.append("file", new Blob([bytes], { type: "audio/wav" }), basename(audioPath));
form.append("model", opts.model);
form.append("response_format", "verbose_json");
form.append("timestamp_granularities[]", "word");
if (opts.segments) form.append("timestamp_granularities[]", "segment");
if (opts.lang) form.append("language", opts.lang);
form.append("temperature", "0");
if (prompt) form.append("prompt", prompt);

console.error(`Uploading ${basename(audioPath)} (${(bytes.length / 1e6).toFixed(1)} MB) to ${opts.model}`);
const started = Date.now();
const res = await fetch("https://api.groq.com/openai/v1/audio/transcriptions", {
  method: "POST",
  headers: { Authorization: `Bearer ${key}` },
  body: form,
});
const text = await res.text();
const secs = ((Date.now() - started) / 1000).toFixed(1);
if (!res.ok) {
  console.error(`HTTP ${res.status} after ${secs}s`);
  console.error(text.slice(0, 1000));
  process.exit(2);
}
await writeFile(outPath, text, "utf8");
const data = JSON.parse(text);
const words = data.words ?? [];
console.error(`OK in ${secs}s: ${words.length} words, ${(data.segments ?? []).length} segments, audio ${data.duration}s -> ${outPath}`);

Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.