feat(HAI-023): complete Step 4 — CLI attach command

This commit is contained in:
Dustin Byrne
2026-03-25 22:05:19 -04:00
parent 2750d4b262
commit 554137b73f
2 changed files with 54 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { runDashboard } from "./commands/dashboard.js";
import { runTaskCreate, runTaskList, runTaskMove, runTaskMerge, runTaskUpdate, runTaskLog, runTaskShow } from "./commands/task.js";
import { runTaskCreate, runTaskList, runTaskMove, runTaskMerge, runTaskUpdate, runTaskLog, runTaskShow, runTaskAttach } from "./commands/task.js";
const HELP = `
hai — AI-orchestrated task board
@@ -15,6 +15,7 @@ Usage:
hai task update <id> <step> <status> Update step status (pending|in-progress|done|skipped)
hai task log <id> <message> Add a log entry
hai task merge <id> Merge an in-review task and close it
hai task attach <id> <file> Attach a screenshot image to a task
Options:
--port, -p <port> Dashboard port (default: 4040)
@@ -100,6 +101,15 @@ async function main() {
await runTaskMerge(id);
break;
}
case "attach": {
const id = args[2], file = args[3];
if (!id || !file) {
console.error("Usage: hai task attach <id> <file>");
process.exit(1);
}
await runTaskAttach(id, file);
break;
}
default:
console.error(`Unknown subcommand: task ${subcommand || ""}`);
console.log("Try: hai task create | list | move");

View File

@@ -169,6 +169,49 @@ export async function runTaskMerge(id: string) {
}
}
const MIME_TYPES: Record<string, string> = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
};
export async function runTaskAttach(id: string, filePath: string) {
const { readFile } = await import("node:fs/promises");
const { basename, extname } = await import("node:path");
const { resolve } = await import("node:path");
const resolvedPath = resolve(filePath);
const filename = basename(resolvedPath);
const ext = extname(filename).toLowerCase();
const mimeType = MIME_TYPES[ext];
if (!mimeType) {
console.error(`Unsupported file type: ${ext}`);
console.error(`Supported: ${Object.keys(MIME_TYPES).join(", ")}`);
process.exit(1);
}
let content: Buffer;
try {
content = await readFile(resolvedPath);
} catch {
console.error(`Cannot read file: ${filePath}`);
process.exit(1);
}
const store = await getStore();
const attachment = await store.addAttachment(id, filename, content, mimeType);
const sizeKB = (attachment.size / 1024).toFixed(1);
console.log();
console.log(` ✓ Attached to ${id}: ${attachment.originalName}`);
console.log(` File: ${attachment.filename} (${sizeKB} KB)`);
console.log(` Path: .hai/tasks/${id}/attachments/${attachment.filename}`);
console.log();
}
export async function runTaskMove(id: string, column: string) {
if (!COLUMNS.includes(column as Column)) {
console.error(`Invalid column: ${column}`);