update action, iterate on logging
This commit is contained in:
+7
-6
@@ -1,8 +1,8 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||||
import { createMcpConfig } from "../mcp/config.ts";
|
import { createMcpConfig } from "../mcp/config.ts";
|
||||||
import { debugLog, isDebug } from "../utils/logging.ts";
|
|
||||||
import { log } from "../utils/cli.ts";
|
import { log } from "../utils/cli.ts";
|
||||||
|
import { debugLog, isDebug } from "../utils/logging.ts";
|
||||||
import { instructions } from "./shared.ts";
|
import { instructions } from "./shared.ts";
|
||||||
import type { Agent, AgentConfig, AgentResult } from "./types.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)}`);
|
debugLog(`📋 MCP Config: ${JSON.stringify(mcpConfig, null, 2)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
core.startGroup("🔄 Run details");
|
|
||||||
|
|
||||||
// Initialize session
|
// Initialize session
|
||||||
core.info(`🚀 Starting Claude Agent SDK session...`);
|
core.info(`🚀 Starting Claude Agent SDK session...`);
|
||||||
|
|
||||||
@@ -63,7 +61,6 @@ export class ClaudeAgent implements Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.success("Task complete.");
|
log.success("Task complete.");
|
||||||
core.endGroup();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -130,8 +127,12 @@ const messageHandlers: SDKMessageHandlers = {
|
|||||||
},
|
},
|
||||||
result: async (data) => {
|
result: async (data) => {
|
||||||
if (data.subtype === "success") {
|
if (data.subtype === "success") {
|
||||||
await log.table([
|
await log.summaryTable([
|
||||||
[{ data: "Cost", header: true }, { data: "Input Tokens", header: true }, { data: "Output Tokens", header: true }],
|
[
|
||||||
|
{ data: "Cost", header: true },
|
||||||
|
{ data: "Input Tokens", header: true },
|
||||||
|
{ data: "Output Tokens", header: true },
|
||||||
|
],
|
||||||
[
|
[
|
||||||
`$${data.total_cost_usd?.toFixed(4) || "0.0000"}`,
|
`$${data.total_cost_usd?.toFixed(4) || "0.0000"}`,
|
||||||
String(data.usage?.input_tokens || 0),
|
String(data.usage?.input_tokens || 0),
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/action",
|
"name": "@pullfrog/action",
|
||||||
"version": "0.0.62",
|
"version": "0.0.63",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
|||||||
+30
-159
@@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* CLI output utilities for GitHub Actions and local development
|
* CLI output utilities that work well in both local and GitHub Actions environments
|
||||||
* Uses GitHub Actions Job Summaries API when available for rich formatting
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
@@ -79,7 +78,7 @@ function boxString(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a formatted box with text
|
* 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(
|
function box(
|
||||||
text: string,
|
text: string,
|
||||||
@@ -88,29 +87,14 @@ function box(
|
|||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
if (isGitHubActions) {
|
core.info(boxString(text, options));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a table using GitHub Actions Job Summaries API
|
* Add a table to GitHub Actions job summary (rich formatting)
|
||||||
* Falls back to formatted console table locally
|
* 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>>,
|
rows: Array<Array<{ data: string; header?: boolean } | string>>,
|
||||||
options?: {
|
options?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -118,107 +102,38 @@ async function table(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { title } = options || {};
|
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) {
|
if (isGitHubActions) {
|
||||||
const summary = core.summary;
|
const summary = core.summary;
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
summary.addHeading(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);
|
summary.addTable(formattedRows);
|
||||||
await summary.write();
|
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`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Also log to console for visibility
|
||||||
* Add raw markdown to job summary (GitHub Actions) or print as info (local)
|
if (title) {
|
||||||
*/
|
core.info(`\n${title}`);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
const tableText = formattedRows.map((row) => row.map((cell) => cell.data).join(" | ")).join("\n");
|
||||||
|
core.info(`\n${tableText}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a separator line
|
* Print a separator line
|
||||||
*/
|
*/
|
||||||
function separator(length: number = 50): void {
|
function separator(length: number = 50): void {
|
||||||
if (isGitHubActions) {
|
core.info("─".repeat(length));
|
||||||
core.info("─".repeat(length));
|
|
||||||
} else {
|
|
||||||
console.log("─".repeat(length));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,87 +141,43 @@ function separator(length: number = 50): void {
|
|||||||
*/
|
*/
|
||||||
export const log = {
|
export const log = {
|
||||||
/**
|
/**
|
||||||
* Check if running in GitHub Actions environment
|
* Print info message
|
||||||
*/
|
|
||||||
isGitHubActions: (): boolean => isGitHubActions,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Print info message (GitHub Actions or console)
|
|
||||||
*/
|
*/
|
||||||
info: (message: string): void => {
|
info: (message: string): void => {
|
||||||
if (isGitHubActions) {
|
core.info(message);
|
||||||
core.info(message);
|
|
||||||
} else {
|
|
||||||
console.log(message);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print warning message
|
* Print warning message
|
||||||
*/
|
*/
|
||||||
warning: (message: string): void => {
|
warning: (message: string): void => {
|
||||||
if (isGitHubActions) {
|
core.warning(message);
|
||||||
core.warning(message);
|
|
||||||
} else {
|
|
||||||
console.warn(`⚠️ ${message}`);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print error message
|
* Print error message
|
||||||
*/
|
*/
|
||||||
error: (message: string): void => {
|
error: (message: string): void => {
|
||||||
if (isGitHubActions) {
|
core.error(message);
|
||||||
core.error(message);
|
|
||||||
} else {
|
|
||||||
console.error(`❌ ${message}`);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print success message
|
* Print success message
|
||||||
*/
|
*/
|
||||||
success: (message: string): void => {
|
success: (message: string): void => {
|
||||||
const msg = `✅ ${message}`;
|
core.info(`✅ ${message}`);
|
||||||
if (isGitHubActions) {
|
|
||||||
core.info(msg);
|
|
||||||
} else {
|
|
||||||
console.log(msg);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a formatted box with text
|
* Print a formatted box with text
|
||||||
* In GitHub Actions, uses markdown code blocks; locally uses box formatting
|
|
||||||
*/
|
*/
|
||||||
box,
|
box,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a table using GitHub Actions Job Summaries API
|
* Add a table to GitHub Actions job summary (rich formatting)
|
||||||
* Falls back to formatted console table locally
|
* Only use this once at the end of execution
|
||||||
*/
|
*/
|
||||||
table,
|
summaryTable,
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a separator line
|
* 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