scai
Version:
> **A local-first AI CLI for understanding, querying, and iterating on large codebases.** > **100% local • No token costs • No cloud • No prompt injection • Private by design**
157 lines (156 loc) • 5.53 kB
JavaScript
import chalk from "chalk";
import { getDbForRepo } from "../db/client.js";
import { getRl } from "./ReadlineSingleton.js";
export async function runTasksCommand() {
const { rl, isTemporary } = getRl();
try {
const db = getDbForRepo();
const tasks = db.prepare(`
SELECT id, status, initial_query, created_at
FROM tasks
ORDER BY id DESC
`).all();
if (!tasks.length) {
console.log(chalk.dim("No tasks found."));
return;
}
console.log(chalk.bold("\nTasks:\n"));
for (const t of tasks) {
console.log(`${chalk.cyan(`[${t.id}]`)} ${chalk.yellow(t.status.padEnd(9))} ${oneLineTruncate(t.initial_query)}`);
}
await new Promise((resolve) => {
rl.question("\nSelect task id (q to quit): ", answer => {
const trimmed = answer.trim();
if (trimmed.toLowerCase() === "q") {
resolve();
return;
}
if (!trimmed) {
console.log(chalk.red("Please enter a task id or 'q' to quit."));
resolve();
return;
}
const id = Number(trimmed);
if (!Number.isInteger(id)) {
console.log(chalk.red("Invalid task id."));
resolve();
return;
}
showTaskDetails(id);
resolve();
});
});
}
finally {
if (isTemporary) {
rl.close();
}
}
}
export function showTaskDetails(taskId) {
const db = getDbForRepo();
const task = db.prepare(`SELECT * FROM tasks WHERE id = ?`).get(taskId);
if (!task) {
console.log(chalk.red(`Task ${taskId} not found.`));
return;
}
console.log(chalk.bold(`\nTask ${task.id} — ${chalk.yellow(task.status)}\n`));
const print = (label, value, indent = " ") => {
if (value == null)
return;
const isArray = Array.isArray(value);
const isObject = typeof value === "object" && !isArray;
if (typeof value === "string" && value.length < 80) {
console.log(chalk.yellow(`${label}:`), value);
}
else if (isArray) {
console.log(chalk.yellow(`${label}:`));
const items = value;
const displayCount = Math.min(5, items.length);
for (let i = 0; i < displayCount; i++) {
console.log(`${indent}- ${oneLineTruncate(String(items[i]), 100)}`);
}
if (items.length > displayCount) {
console.log(`${indent}… (+${items.length - displayCount} more)`);
}
}
else if (isObject) {
console.log(chalk.yellow(`${label}:`));
console.log(indent + JSON.stringify(value, null, 2).replace(/\n/g, "\n" + indent));
}
else {
console.log(chalk.yellow(`${label}:`));
console.log(indent + String(value));
}
};
// Lifecycle
print("Initial query", task.initial_query);
if (task.summary) {
console.log(chalk.green.bold("\nFinal answer:\n"));
console.log(chalk.white(task.summary));
console.log();
}
// Intent
print("Agreed intent", task.agreed_intent);
print("Intent category", task.intent_category);
print("Normalized query", task.normalized_query);
print("Intent confidence", task.intent_confidence);
// Focus / files
if (task.relevant_files_json) {
try {
print("Relevant files", JSON.parse(task.relevant_files_json));
}
catch {
print("Relevant files", "[invalid JSON]");
}
}
if (task.missing_files_json) {
try {
print("Missing files", JSON.parse(task.missing_files_json));
}
catch {
print("Missing files", "[invalid JSON]");
}
}
// Routing decision
if (task.routing_decision_json) {
try {
print("Routing decision", JSON.parse(task.routing_decision_json));
}
catch {
print("Routing decision", "[invalid JSON]");
}
}
// Bookkeeping
print("Created", task.created_at);
print("Updated", task.updated_at);
// ----------------- TASK STEPS -----------------
const steps = db.prepare(`
SELECT id, file_path, action, status, step_index, start_time, end_time, created_at, updated_at
FROM task_steps
WHERE task_id = ?
ORDER BY step_index ASC
`).all(taskId);
if (steps.length === 0) {
console.log(chalk.dim("\nNo task steps recorded."));
}
else {
console.log(chalk.bold("\nTask Steps:\n"));
steps.forEach(s => {
console.log(`${chalk.cyan(`[${s.id}]`)} ` +
`${chalk.yellow((s.status || "").padEnd(9))} ` +
`${s.file_path}` +
(s.action ? ` → ${s.action}` : "") +
` (index=${s.step_index ?? "-"})` +
(s.start_time ? ` started=${s.start_time}` : "") +
(s.end_time ? ` ended=${s.end_time}` : ""));
});
}
}
/** Helper: truncate long text to a single line */
function oneLineTruncate(input, max = 80) {
const singleLine = input.replace(/\s+/g, " ").trim();
if (singleLine.length <= max)
return singleLine;
return singleLine.slice(0, max - 1) + "…";
}