update action, iterate on logging
This commit is contained in:
+7
-6
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pullfrog/action",
|
||||
"version": "0.0.62",
|
||||
"version": "0.0.63",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"index.js",
|
||||
|
||||
+30
-159
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user