@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
169 lines (168 loc) • 5.25 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { Command } from "commander";
import Database from "better-sqlite3";
import { join } from "path";
import { existsSync, readFileSync } from "fs";
function createLogCommand() {
const log = new Command("log").alias("history").description("View recent activity log").option("-n, --lines <n>", "Number of entries to show", "20").option("-t, --type <type>", "Filter by type (task, frame, event, sync)").option("-f, --follow", "Follow log in real-time").action(async (options) => {
const projectRoot = process.cwd();
const dbPath = join(projectRoot, ".stackmemory", "context.db");
const _tasksPath = join(projectRoot, ".stackmemory", "tasks.jsonl");
if (!existsSync(dbPath)) {
console.log(
'\u274C StackMemory not initialized. Run "stackmemory init" first.'
);
return;
}
const limit = parseInt(options.lines);
const activities = [];
const db = new Database(dbPath);
if (!options.type || options.type === "frame") {
try {
const frames = db.prepare(
`
SELECT id, type, name, state, created_at, updated_at
FROM frames
ORDER BY updated_at DESC
LIMIT ?
`
).all(limit);
frames.forEach((f) => {
activities.push({
timestamp: f.updated_at || f.created_at,
type: "frame",
action: f.state === "closed" ? "closed" : "opened",
details: `[${f.type}] ${f.name || f.id.slice(0, 10)}`
});
});
} catch {
}
}
if (!options.type || options.type === "event") {
try {
const events = db.prepare(
`
SELECT id, type, data, timestamp
FROM events
ORDER BY timestamp DESC
LIMIT ?
`
).all(limit);
events.forEach((e) => {
let data = {};
try {
data = JSON.parse(e.data);
} catch {
}
activities.push({
timestamp: e.timestamp,
type: "event",
action: e.type,
details: data.message || data.content || data.decision || ""
});
});
} catch {
}
}
if (!options.type || options.type === "task") {
try {
const tasks = db.prepare(
`
SELECT id, title, status, type, timestamp
FROM task_cache
ORDER BY timestamp DESC
LIMIT ?
`
).all(limit);
tasks.forEach((t) => {
activities.push({
timestamp: t.timestamp,
type: "task",
action: t.type?.replace("task_", "") || t.status,
details: t.title
});
});
} catch {
}
}
db.close();
if (!options.type || options.type === "sync") {
const mappingsPath = join(
projectRoot,
".stackmemory",
"linear-mappings.json"
);
if (existsSync(mappingsPath)) {
try {
const mappings = JSON.parse(readFileSync(mappingsPath, "utf-8"));
mappings.slice(-limit).forEach((m) => {
activities.push({
timestamp: Math.floor(m.lastSyncTimestamp / 1e3),
type: "sync",
action: "synced",
details: `${m.linearIdentifier}`
});
});
} catch {
}
}
}
activities.sort((a, b) => b.timestamp - a.timestamp);
console.log(`
\u{1F4DC} Activity Log
`);
const typeIcon = {
frame: "\u{1F4C1}",
event: "\u26A1",
task: "\u{1F4CB}",
sync: "\u{1F504}"
};
activities.slice(0, limit).forEach((activity) => {
const date = new Date(activity.timestamp * 1e3);
const timeStr = date.toLocaleTimeString("en-US", {
hour: "2-digit",
minute: "2-digit"
});
const dateStr = date.toLocaleDateString("en-US", {
month: "short",
day: "numeric"
});
const icon = typeIcon[activity.type] || "\u{1F4DD}";
console.log(
`${icon} ${dateStr} ${timeStr} ${activity.action.padEnd(12)} ${activity.details.slice(0, 50)}`
);
});
console.log("");
if (options.follow) {
console.log("\u{1F440} Watching for changes... (Ctrl+C to stop)\n");
try {
const chokidar = await import("chokidar");
const watcher = chokidar.watch(join(projectRoot, ".stackmemory"), {
persistent: true,
ignoreInitial: true
});
watcher.on("change", (path) => {
const now = /* @__PURE__ */ new Date();
const timeStr = now.toLocaleTimeString("en-US", {
hour: "2-digit",
minute: "2-digit"
});
console.log(
`\u{1F504} ${timeStr} File changed: ${path.split("/").pop()}`
);
});
await new Promise(() => {
});
} catch {
console.log("Install chokidar for follow mode: npm i chokidar");
}
}
});
return log;
}
export {
createLogCommand
};