123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
import chalk from "chalk";
|
|
import type { CIStatus, ReviewDecision, ActivityState } from "@aoagents/ao-core";
|
|
|
|
export function header(title: string): string {
|
|
const line = "─".repeat(76);
|
|
return [
|
|
chalk.dim(`┌${line}┐`),
|
|
chalk.dim("│") + chalk.bold(` ${title}`.padEnd(76)) + chalk.dim("│"),
|
|
chalk.dim(`└${line}┘`),
|
|
].join("\n");
|
|
}
|
|
|
|
export function banner(title: string): string {
|
|
const line = "═".repeat(76);
|
|
return [
|
|
chalk.dim(`╔${line}╗`),
|
|
chalk.dim("║") + chalk.bold.cyan(` ${title}`.padEnd(76)) + chalk.dim("║"),
|
|
chalk.dim(`╚${line}╝`),
|
|
].join("\n");
|
|
}
|
|
|
|
export function formatAge(epochMs: number): string {
|
|
const diff = Math.floor((Date.now() - epochMs) / 1000);
|
|
if (diff < 60) return `${diff}s ago`;
|
|
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
return `${Math.floor(diff / 86400)}d ago`;
|
|
}
|
|
|
|
export function statusColor(status: string): string {
|
|
switch (status) {
|
|
case "working":
|
|
return chalk.green(status);
|
|
case "idle":
|
|
return chalk.yellow(status);
|
|
case "pr_open":
|
|
case "review_pending":
|
|
return chalk.blue(status);
|
|
case "approved":
|
|
case "mergeable":
|
|
case "merged":
|
|
return chalk.green(status);
|
|
case "ci_failed":
|
|
case "errored":
|
|
case "stuck":
|
|
return chalk.red(status);
|
|
case "changes_requested":
|
|
case "needs_input":
|
|
return chalk.magenta(status);
|
|
case "spawning":
|
|
return chalk.cyan(status);
|
|
case "killed":
|
|
case "cleanup":
|
|
return chalk.gray(status);
|
|
default:
|
|
return status;
|
|
}
|
|
}
|
|
|
|
export function ciStatusIcon(status: CIStatus | null): string {
|
|
switch (status) {
|
|
case "passing":
|
|
return chalk.green("pass");
|
|
case "failing":
|
|
return chalk.red("fail");
|
|
case "pending":
|
|
return chalk.yellow("pend");
|
|
case "none":
|
|
case null:
|
|
return chalk.dim("-");
|
|
}
|
|
}
|
|
|
|
export function reviewDecisionIcon(decision: ReviewDecision | null): string {
|
|
switch (decision) {
|
|
case "approved":
|
|
return chalk.green("ok");
|
|
case "changes_requested":
|
|
return chalk.red("chg!");
|
|
case "pending":
|
|
return chalk.yellow("rev?");
|
|
case "none":
|
|
case null:
|
|
return chalk.dim("-");
|
|
}
|
|
}
|
|
|
|
export function activityIcon(activity: ActivityState | null): string {
|
|
switch (activity) {
|
|
case "active":
|
|
return chalk.green("working");
|
|
case "ready":
|
|
return chalk.cyan("ready");
|
|
case "idle":
|
|
return chalk.yellow("idle");
|
|
case "waiting_input":
|
|
return chalk.magenta("waiting");
|
|
case "blocked":
|
|
return chalk.red("blocked");
|
|
case "exited":
|
|
return chalk.dim("exited");
|
|
case null:
|
|
return chalk.dim("unknown");
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line no-control-regex
|
|
const ANSI_RE = /\u001b\[[0-9;]*m/g;
|
|
|
|
/** Pad/truncate a string to exactly `width` visible characters */
|
|
export function padCol(str: string, width: number): string {
|
|
// Strip ANSI codes to measure visible length
|
|
const visible = str.replace(ANSI_RE, "");
|
|
if (visible.length > width) {
|
|
// Truncate visible content, re-apply truncation
|
|
const plain = visible.slice(0, width - 1) + "\u2026";
|
|
return plain.padEnd(width);
|
|
}
|
|
// Pad with spaces based on visible length
|
|
const padding = width - visible.length;
|
|
return str + " ".repeat(Math.max(0, padding));
|
|
}
|