#!/usr/bin/env runts -- --allow-all import {createInterface} from "node:readline/promises"; import {stdin, stdout} from "node:process"; import { execCommand, execCommandShell, fetchDataWithTimeout, getSecretValue, log, ProcessBar, term, } from "https://script.hatter.ink/@61/deno-commons-mod.ts"; import {getGitLocalRev, getGitRemoteRev, getGitStatus,} from "https://script.hatter.ink/@2/deno-git-mod.ts"; async function checkRev(): Promise { const localRev = await getGitLocalRev(); const remoteRev = await new ProcessBar("Checking rev").call( async (): Promise => { return await getGitRemoteRev(); }, ); if (localRev === remoteRev) { console.log(term.green(`Check rev successfully, rev: ${localRev}`)); } else { throw `Check rev failed, local rev: ${localRev} vs remote rev: ${remoteRev}`; } } async function summarize(): Promise { const gitStatus = (await execCommand("git", ["status"])) .assertSuccess().getStdoutAsStringThenTrim(); const gitDiff = (await execCommand("git", ["diff"])) .assertSuccess().getStdoutAsStringThenTrim(); const response = await fetchDataWithTimeout( "https://hatter.ink/ai/commit-summarize.json", { method: "POST", headers: { "Authorization": "Bearer " + await getSecretValue("ai-commit-summarize-token"), }, body: new URLSearchParams({ "gitStatus": gitStatus, "gitDiff": gitDiff, }), }, ); if (response.status != 200) { console.log( term.yellow( `Summarize commit message failed, status: ${response.status}`, ), ); return null; } return (await response.json())["data"]["summary"]; } async function main() { await checkRev(); // check local rev <--> remote rev equals const gitStatus = await getGitStatus(); if (gitStatus === null) { log.warn("No git status found"); return; } log.info("Git status:", gitStatus); const summary = await new ProcessBar("AI summarizing").call( async (): Promise => { return await summarize(); }, ); if (summary != null) { log.success(`AI summarized git commit message: ${summary}`); } let emptyCount = 0; let message = "empty message"; const readline = createInterface({ input: stdin, output: stdout }); do { if (emptyCount++ === 3) { readline.close(); console.log("Too many empty messages, then exit"); return; } message = await readline.question( "Input your commit message (Enter for AI summary) > ", ); if (message.trim().length == 0) { message = summary ?? ""; } } while (message.length == 0); readline.close(); const gitCommitArgs: string[] = []; gitCommitArgs.push("commit"); for (const file of gitStatus.modified) { gitCommitArgs.push(file); } for (const file of gitStatus.deleted) { gitCommitArgs.push(file); } for (const file of gitStatus.untracked) { gitCommitArgs.push(file); } gitCommitArgs.push("-m"); gitCommitArgs.push(message); if (gitStatus.untracked.length > 0) { const gitAddArgs = ["add"]; for (const file of gitStatus.untracked) { gitAddArgs.push(file); } log.info(">>>>>", ["git", gitAddArgs]); await execCommandShell("git", gitAddArgs); } log.info(">>>>>", ["git", gitCommitArgs]); await execCommandShell("git", gitCommitArgs); log.info(">>>>>", ["git", "push"]); await new ProcessBar("Git pushing").call(async (): Promise => { await execCommandShell("git", ["push"]); }); } main().catch((err) => { log.error(err); process.exit(0); }).then(() => process.exit(0)); // @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260404T230316+08:00.MEYCIQDPcT9WH0J7pH4eZ+3v // eShmOZRyUpCu268LAvn08Y5AoQIhAK+zj3K5SuBmtRrz+6TpIZPv/C8L8VSj8FsdhZdMn8rL