@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
412 lines ⢠16 kB
JavaScript
/**
* Session management commands for Claude-Flow CLI
*/
import { Command } from "../cliffy-compat.js";
import { logger } from "../../core/logger.js";
import { promises as fs } from "node:fs";
import { platform } from "node:os";
import { join } from "node:path";
import { existsSync } from "node:fs";
const SESSION_DIR = ".claude-flow/sessions";
const SESSION_VERSION = "1.0.0";
export const sessionCommand = new Command()
.name("session")
.description("Manage Claude-Flow sessions")
.action(() => {
console.log("Available session commands:");
console.log(" create - Create a new session");
console.log(" list - List all sessions");
console.log(" save - Save current session state");
console.log(" restore - Restore a saved session");
console.log(" delete - Delete a session");
console.log(" export - Export session to file");
console.log(" import - Import session from file");
console.log(" info - Show detailed session information");
console.log(" clean - Clean up old sessions");
})
.command("create")
.description("Create a new session")
.arguments("<name>")
.option("-d, --description <desc>", "Session description")
.option("-t, --tags <tags>", "Comma-separated tags")
.action(async (options, name) => {
try {
await ensureSessionDirectory();
const session = {
id: generateSessionId(),
name,
description: options.description,
createdAt: new Date(),
updatedAt: new Date(),
metadata: {
version: SESSION_VERSION,
platform: platform(),
nodeVersion: process.version,
workingDirectory: process.cwd(),
},
state: {
agents: [],
tasks: [],
memory: [],
config: {},
},
tags: options.tags ? options.tags.split(",").map((tag) => tag.trim()) : [],
archived: false,
};
const filePath = join(SESSION_DIR, `${session.id}.json`);
await fs.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
console.log(`ā
Session created: ${session.id}`);
console.log(` Name: ${name}`);
console.log(` File: ${filePath}`);
}
catch (error) {
logger.error("Failed to create session:", error);
console.error("ā Failed to create session");
}
})
.command("list")
.description("List all sessions")
.option("-a, --archived", "Include archived sessions")
.option("--json", "Output in JSON format")
.action(async (options) => {
try {
const sessions = await getAllSessions();
let filteredSessions = sessions;
if (!options.archived) {
filteredSessions = sessions.filter(s => !s.archived);
}
if (options.json) {
console.log(JSON.stringify(filteredSessions, null, 2));
return;
}
if (filteredSessions.length === 0) {
console.log("No sessions found");
return;
}
console.log(`\nš Sessions (${filteredSessions.length}):\n`);
for (const session of filteredSessions) {
const ageMs = Date.now() - session.createdAt.getTime();
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
const ageHours = Math.floor(ageMs / (1000 * 60 * 60));
const ageStr = ageDays > 0 ? `${ageDays}d ago` : `${ageHours}h ago`;
console.log(`š¹ ${session.name} (${session.id})`);
console.log(` Created: ${ageStr}`);
if (session.description) {
console.log(` Description: ${session.description}`);
}
if (session.tags.length > 0) {
console.log(` Tags: ${session.tags.join(", ")}`);
}
console.log(` Agents: ${session.state.agents.length}, Tasks: ${session.state.tasks.length}`);
if (session.archived) {
console.log(" š¦ Archived");
}
console.log();
}
}
catch (error) {
logger.error("Failed to list sessions:", error);
console.error("ā Failed to list sessions");
}
})
.command("save")
.description("Save current session state")
.arguments("<session-id>")
.option("-m, --message <msg>", "Save message")
.action(async (sessionId, options) => {
try {
const session = await loadSession(sessionId);
if (!session) {
console.error(`ā Session not found: ${sessionId}`);
return;
}
// In a real implementation, this would capture current state
session.updatedAt = new Date();
session.state = {
agents: [], // Would capture current agents
tasks: [], // Would capture current tasks
memory: [], // Would capture current memory
config: {}, // Would capture current config
};
const filePath = join(SESSION_DIR, `${session.id}.json`);
await fs.writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
console.log(`ā
Session saved: ${sessionId}`);
if (options.message) {
console.log(` Message: ${options.message}`);
}
}
catch (error) {
logger.error("Failed to save session:", error);
console.error("ā Failed to save session");
}
})
.command("restore")
.description("Restore a saved session")
.arguments("<session-id>")
.option("-f, --force", "Force restore without confirmation")
.action(async (sessionId, options) => {
try {
const session = await loadSession(sessionId);
if (!session) {
console.error(`ā Session not found: ${sessionId}`);
return;
}
if (!options.force) {
console.log(`ā ļø This will restore session: ${session.name}`);
console.log(" Current state will be lost!");
// In a real implementation, would prompt for confirmation
}
// In a real implementation, this would restore the session state
console.log(`ā
Session restored: ${session.name}`);
console.log(` Agents: ${session.state.agents.length}`);
console.log(` Tasks: ${session.state.tasks.length}`);
}
catch (error) {
logger.error("Failed to restore session:", error);
console.error("ā Failed to restore session");
}
})
.command("delete")
.description("Delete a session")
.arguments("<session-id>")
.option("-f, --force", "Force delete without confirmation")
.action(async (sessionId, options) => {
try {
const session = await loadSession(sessionId);
if (!session) {
console.error(`ā Session not found: ${sessionId}`);
return;
}
if (!options.force) {
console.log(`ā ļø This will permanently delete session: ${session.name}`);
// In a real implementation, would prompt for confirmation
}
const filePath = join(SESSION_DIR, `${sessionId}.json`);
await fs.unlink(filePath);
console.log(`ā
Session deleted: ${session.name}`);
}
catch (error) {
logger.error("Failed to delete session:", error);
console.error("ā Failed to delete session");
}
})
.command("export")
.description("Export session to file")
.arguments("<session-id> <output-file>")
.option("--format <fmt>", "Export format (json, yaml)", "json")
.action(async (sessionId, outputFile, options) => {
try {
const session = await loadSession(sessionId);
if (!session) {
console.error(`ā Session not found: ${sessionId}`);
return;
}
const exportData = {
session,
exportedAt: new Date(),
exportVersion: SESSION_VERSION,
};
let content;
if (options.format === "yaml") {
// Would use yaml library in real implementation
content = JSON.stringify(exportData, null, 2);
}
else {
content = JSON.stringify(exportData, null, 2);
}
await fs.writeFile(outputFile, content, "utf-8");
console.log(`ā
Session exported: ${outputFile}`);
console.log(` Format: ${options.format}`);
console.log(` Size: ${content.length} bytes`);
}
catch (error) {
logger.error("Failed to export session:", error);
console.error("ā Failed to export session");
}
})
.command("import")
.description("Import session from file")
.arguments("<input-file>")
.option("-n, --name <name>", "Override session name")
.option("-f, --force", "Force import even if session exists")
.action(async (inputFile, options) => {
try {
const content = await fs.readFile(inputFile, "utf-8");
const importData = JSON.parse(content);
const { session } = importData;
if (options.name) {
session.name = options.name;
}
// Generate new ID to avoid conflicts
session.id = generateSessionId();
session.createdAt = new Date();
session.updatedAt = new Date();
await ensureSessionDirectory();
const sessionData = {
...session,
metadata: {
...session.metadata,
importedAt: new Date(),
importedFrom: inputFile,
},
};
const filePath = join(SESSION_DIR, `${session.id}.json`);
await fs.writeFile(filePath, JSON.stringify(sessionData, null, 2), "utf-8");
console.log(`ā
Session imported: ${session.name}`);
console.log(` ID: ${session.id}`);
console.log(` File: ${filePath}`);
}
catch (error) {
logger.error("Failed to import session:", error);
console.error("ā Failed to import session");
}
})
.command("info")
.description("Show detailed session information")
.arguments("<session-id>")
.option("--json", "Output in JSON format")
.action(async (sessionId, options) => {
try {
const session = await loadSession(sessionId);
if (!session) {
console.error(`ā Session not found: ${sessionId}`);
return;
}
if (options.json) {
console.log(JSON.stringify(session, null, 2));
return;
}
console.log("\nš Session Information\n");
console.log(`ID: ${session.id}`);
console.log(`Name: ${session.name}`);
if (session.description) {
console.log(`Description: ${session.description}`);
}
console.log(`Created: ${session.createdAt.toISOString()}`);
console.log(`Updated: ${session.updatedAt.toISOString()}`);
console.log("\nš§ Metadata:");
console.log(` Version: ${session.metadata.version}`);
console.log(` Platform: ${session.metadata.platform}`);
console.log(` Node Version: ${session.metadata.nodeVersion}`);
console.log(` Working Directory: ${session.metadata.workingDirectory}`);
console.log("\nš State:");
console.log(` Agents: ${session.state.agents.length}`);
console.log(` Tasks: ${session.state.tasks.length}`);
console.log(` Memory Entries: ${session.state.memory.length}`);
if (session.tags.length > 0) {
console.log(`\nš·ļø Tags: ${session.tags.join(", ")}`);
}
if (session.archived) {
console.log("\nš¦ Status: Archived");
}
// Show file size
const filePath = join(SESSION_DIR, `${sessionId}.json`);
try {
const fileInfo = await fs.stat(filePath);
console.log(`\nš File: ${filePath}`);
console.log(` Size: ${fileInfo.size} bytes`);
console.log(` Modified: ${fileInfo.mtime.toISOString()}`);
}
catch {
console.log("\nš File: Not found");
}
}
catch (error) {
logger.error("Failed to show session info:", error);
console.error("ā Failed to show session info");
}
})
.command("clean")
.description("Clean up old sessions")
.option("--days <days>", "Delete sessions older than N days", "30")
.option("--archived-only", "Only clean archived sessions")
.option("--dry-run", "Show what would be deleted without deleting")
.action(async (options) => {
try {
const sessions = await getAllSessions();
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - parseInt(options.days || "30", 10));
const sessionsToDelete = sessions.filter(session => {
const isOld = session.createdAt < cutoffDate;
const shouldDelete = options.archivedOnly ? session.archived && isOld : isOld;
return shouldDelete;
});
if (sessionsToDelete.length === 0) {
console.log("No sessions to clean up");
return;
}
console.log(`Found ${sessionsToDelete.length} sessions to clean up:`);
for (const session of sessionsToDelete) {
const ageMs = Date.now() - session.createdAt.getTime();
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
console.log(` - ${session.name} (${session.id}) - ${ageDays} days old`);
if (!options.dryRun) {
const filePath = join(SESSION_DIR, `${session.id}.json`);
await fs.unlink(filePath);
}
}
if (options.dryRun) {
console.log("\nš Dry run - no sessions were deleted");
console.log("Remove --dry-run to actually delete these sessions");
}
else {
console.log(`\nā
Cleaned up ${sessionsToDelete.length} sessions`);
}
}
catch (error) {
logger.error("Failed to clean sessions:", error);
console.error("ā Failed to clean sessions");
}
});
// Helper functions
async function ensureSessionDirectory() {
await fs.mkdir(SESSION_DIR, { recursive: true });
}
function generateSessionId() {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 8);
return `session-${timestamp}-${random}`;
}
async function loadSession(sessionId) {
try {
const filePath = join(SESSION_DIR, `${sessionId}.json`);
const content = await fs.readFile(filePath, "utf-8");
const session = JSON.parse(content);
// Convert date strings back to Date objects
session.createdAt = new Date(session.createdAt);
session.updatedAt = new Date(session.updatedAt);
return session;
}
catch {
return null;
}
}
async function getAllSessions() {
try {
if (!existsSync(SESSION_DIR)) {
return [];
}
const entries = await fs.readdir(SESSION_DIR, { withFileTypes: true });
const sessions = [];
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".json")) {
try {
const content = await fs.readFile(join(SESSION_DIR, entry.name), "utf-8");
const session = JSON.parse(content);
// Convert date strings back to Date objects
session.createdAt = new Date(session.createdAt);
session.updatedAt = new Date(session.updatedAt);
sessions.push(session);
}
catch (error) {
logger.warn(`Failed to load session ${entry.name}:`, error);
}
}
}
return sessions.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
}
catch {
return [];
}
}
//# sourceMappingURL=session.js.map