update action, iterate on logging

This commit is contained in:
David Blass
2025-10-31 00:46:40 -04:00
parent 876663cd1a
commit ab2d762658
4 changed files with 38 additions and 308 deletions
+7 -6
View File
@@ -1,8 +1,8 @@
import * as core from "@actions/core";
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
import { createMcpConfig } from "../mcp/config.ts";
import { debugLog, isDebug } from "../utils/logging.ts";
import { log } from "../utils/cli.ts";
import { debugLog, isDebug } from "../utils/logging.ts";
import { instructions } from "./shared.ts";
import type { Agent, AgentConfig, AgentResult } from "./types.ts";
@@ -39,8 +39,6 @@ export class ClaudeAgent implements Agent {
debugLog(`📋 MCP Config: ${JSON.stringify(mcpConfig, null, 2)}`);
}
core.startGroup("🔄 Run details");
// Initialize session
core.info(`🚀 Starting Claude Agent SDK session...`);
@@ -63,7 +61,6 @@ export class ClaudeAgent implements Agent {
}
log.success("Task complete.");
core.endGroup();
return {
success: true,
@@ -130,8 +127,12 @@ const messageHandlers: SDKMessageHandlers = {
},
result: async (data) => {
if (data.subtype === "success") {
await log.table([
[{ data: "Cost", header: true }, { data: "Input Tokens", header: true }, { data: "Output Tokens", header: true }],
await log.summaryTable([
[
{ data: "Cost", header: true },
{ data: "Input Tokens", header: true },
{ data: "Output Tokens", header: true },
],
[
`$${data.total_cost_usd?.toFixed(4) || "0.0000"}`,
String(data.usage?.input_tokens || 0),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@pullfrog/action",
"version": "0.0.62",
"version": "0.0.63",
"type": "module",
"files": [
"index.js",
+30 -159
View File
@@ -1,6 +1,5 @@
/**
* CLI output utilities for GitHub Actions and local development
* Uses GitHub Actions Job Summaries API when available for rich formatting
* CLI output utilities that work well in both local and GitHub Actions environments
*/
import * as core from "@actions/core";
@@ -79,7 +78,7 @@ function boxString(
/**
* Print a formatted box with text
* In GitHub Actions, uses markdown code blocks; locally uses box formatting
* Works well in both local and GitHub Actions environments
*/
function box(
text: string,
@@ -88,29 +87,14 @@ function box(
maxWidth?: number;
}
): void {
if (isGitHubActions) {
const { title } = options || {};
let markdown = "";
if (title) {
markdown += `### ${title}\n\n`;
}
markdown += "```\n";
markdown += text;
markdown += "\n```\n";
// Note: summary.write() should be called once at the end, but for immediate visibility we call it here
// In practice, you might want to batch multiple calls
core.summary.addRaw(markdown);
core.info(markdown);
} else {
log.info(boxString(text, options));
}
core.info(boxString(text, options));
}
/**
* Add a table using GitHub Actions Job Summaries API
* Falls back to formatted console table locally
* Add a table to GitHub Actions job summary (rich formatting)
* Also logs to console. Only use this once at the end of execution.
*/
async function table(
async function summaryTable(
rows: Array<Array<{ data: string; header?: boolean } | string>>,
options?: {
title?: string;
@@ -118,107 +102,38 @@ async function table(
): Promise<void> {
const { title } = options || {};
// Convert rows to format expected by Job Summaries API
const formattedRows = rows.map((row) =>
row.map((cell) => {
if (typeof cell === "string") {
return { data: cell };
}
return cell;
})
);
if (isGitHubActions) {
const summary = core.summary;
if (title) {
summary.addHeading(title);
}
// Convert rows to format expected by Job Summaries API
const formattedRows = rows.map((row) =>
row.map((cell) => {
if (typeof cell === "string") {
return { data: cell };
}
return cell;
})
);
summary.addTable(formattedRows);
await summary.write();
// Also log to console for visibility in logs
const tableText = formattedRows
.map((row) => row.map((cell) => cell.data).join(" | "))
.join("\n");
log.info(tableText);
} else {
// Local fallback: simple console table
if (title) {
log.info(`\n${title}`);
}
const tableText = rows
.map((row) => {
const rowData = row.map((cell) => {
const text = typeof cell === "string" ? cell : cell.data;
const isHeader = typeof cell !== "string" && cell.header;
return isHeader ? `**${text}**` : text;
});
return rowData.join(" | ");
})
.join("\n");
log.info(`\n${tableText}\n`);
}
}
/**
* Add raw markdown to job summary (GitHub Actions) or print as info (local)
*/
async function markdown(content: string): Promise<void> {
if (isGitHubActions) {
core.summary.addRaw(content);
await core.summary.write();
// Also log to console
core.info(content);
} else {
log.info(content);
}
}
/**
* Add a code block to output
*/
function codeBlock(code: string, language?: string): void {
if (isGitHubActions) {
core.summary.addCodeBlock(code, language);
core.info(`\`\`\`${language || ""}\n${code}\n\`\`\``);
} else {
log.info(`\`\`\`${language || ""}\n${code}\n\`\`\``);
}
}
/**
* Add a link to output
*/
function link(text: string, url: string): void {
if (isGitHubActions) {
core.summary.addLink(text, url);
log.info(`[${text}](${url})`);
} else {
log.info(`[${text}](${url})`);
}
}
/**
* Write all pending summary changes to the job summary
* Call this at the end of your action to ensure all summary content is written
*/
async function writeSummary(): Promise<void> {
if (isGitHubActions) {
await core.summary.write();
// Also log to console for visibility
if (title) {
core.info(`\n${title}`);
}
const tableText = formattedRows.map((row) => row.map((cell) => cell.data).join(" | ")).join("\n");
core.info(`\n${tableText}\n`);
}
/**
* Print a separator line
*/
function separator(length: number = 50): void {
if (isGitHubActions) {
core.info("─".repeat(length));
} else {
console.log("─".repeat(length));
}
core.info("─".repeat(length));
}
/**
@@ -226,87 +141,43 @@ function separator(length: number = 50): void {
*/
export const log = {
/**
* Check if running in GitHub Actions environment
*/
isGitHubActions: (): boolean => isGitHubActions,
/**
* Print info message (GitHub Actions or console)
* Print info message
*/
info: (message: string): void => {
if (isGitHubActions) {
core.info(message);
} else {
console.log(message);
}
core.info(message);
},
/**
* Print warning message
*/
warning: (message: string): void => {
if (isGitHubActions) {
core.warning(message);
} else {
console.warn(`⚠️ ${message}`);
}
core.warning(message);
},
/**
* Print error message
*/
error: (message: string): void => {
if (isGitHubActions) {
core.error(message);
} else {
console.error(`${message}`);
}
core.error(message);
},
/**
* Print success message
*/
success: (message: string): void => {
const msg = `${message}`;
if (isGitHubActions) {
core.info(msg);
} else {
console.log(msg);
}
core.info(`${message}`);
},
/**
* Print a formatted box with text
* In GitHub Actions, uses markdown code blocks; locally uses box formatting
*/
box,
/**
* Add a table using GitHub Actions Job Summaries API
* Falls back to formatted console table locally
* Add a table to GitHub Actions job summary (rich formatting)
* Only use this once at the end of execution
*/
table,
/**
* Add raw markdown to job summary (GitHub Actions) or print as info (local)
*/
markdown,
/**
* Add a code block to output
*/
codeBlock,
/**
* Add a link to output
*/
link,
/**
* Write all pending summary changes to the job summary
* Call this at the end of your action to ensure all summary content is written
*/
writeSummary,
summaryTable,
/**
* Print a separator line
-142
View File
@@ -1,142 +0,0 @@
import { table } from "table";
/**
* Print a formatted table with consistent styling
* @param rows - Array of string arrays representing table rows
* @param options - Optional table configuration
*/
export function tableString(
rows: string[][],
options?: {
title?: string;
indent?: string;
drawHorizontalLine?: (lineIndex: number, rowCount: number) => boolean;
}
): string {
const {
title,
indent = "",
drawHorizontalLine = (lineIndex: number, rowCount: number) => {
return lineIndex === 0 || (options?.title && lineIndex === 1) || lineIndex === rowCount;
},
} = options || {};
if (title) {
rows.unshift([title]);
}
const tableOutput = table(rows, {
drawHorizontalLine,
border: {
topBody: ``,
topJoin: ``,
topLeft: ``,
topRight: ``,
bottomBody: ``,
bottomJoin: ``,
bottomLeft: ``,
bottomRight: ``,
bodyLeft: ``,
bodyRight: ``,
bodyJoin: ``,
joinBody: ``,
joinLeft: ``,
joinRight: ``,
joinJoin: ``,
},
});
const indentedOutput = tableOutput.split("\n").join(`\n${indent}`).trim();
return `${indent}${indentedOutput}`;
}
/**
* Print a multi-line string in a formatted box with text wrapping
* @param text - The text to display
* @param options - Optional configuration for the box
*/
export function boxString(
text: string,
options?: {
title?: string;
maxWidth?: number;
indent?: string;
padding?: number;
}
): string {
const { title, maxWidth = 80, indent = "", padding = 1 } = options || {};
// Clean up the text and split into lines
const lines = text.trim().split("\n");
// Word wrap each line to fit within maxWidth
const wrappedLines: string[] = [];
for (const line of lines) {
if (line.length <= maxWidth - padding * 2) {
wrappedLines.push(line);
} else {
// Word wrap the line
const words = line.split(" ");
let currentLine = "";
for (const word of words) {
const testLine = currentLine ? `${currentLine} ${word}` : word;
if (testLine.length <= maxWidth - padding * 2) {
currentLine = testLine;
} else {
if (currentLine) {
wrappedLines.push(currentLine);
currentLine = word;
} else {
// Word is too long, break it
wrappedLines.push(word.substring(0, maxWidth - padding * 2));
currentLine = word.substring(maxWidth - padding * 2);
}
}
}
if (currentLine) {
wrappedLines.push(currentLine);
}
}
}
// Find the maximum line length for box sizing
const maxLineLength = Math.max(...wrappedLines.map((line) => line.length));
const boxWidth = maxLineLength + padding * 2;
// Create the box
const topBorder = "┌" + "─".repeat(boxWidth) + "┐";
const bottomBorder = "└" + "─".repeat(boxWidth) + "┘";
const sideBorder = "│";
let result = "";
// Add title if provided
if (title) {
const titleLine = ` ${title} `;
const titlePadding = Math.max(0, boxWidth - titleLine.length);
result += `${indent}${titleLine}${"─".repeat(titlePadding)}\n`;
}
// Add top border (or title border)
if (!title) {
result += `${indent}${topBorder}\n`;
}
// Add content lines
for (const line of wrappedLines) {
const paddedLine = line.padEnd(maxLineLength);
result += `${indent}${sideBorder}${" ".repeat(padding)}${paddedLine}${" ".repeat(padding)}${sideBorder}\n`;
}
// Add bottom border
result += `${indent}${bottomBorder}`;
return result;
}