UNPKG

@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

199 lines (198 loc) 7.7 kB
#!/usr/bin/env node import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { ProjectManager } from "../../core/projects/project-manager.js"; import chalk from "chalk"; import Table from "cli-table3"; function registerProjectCommands(program) { const projects = program.command("projects").description("Manage multi-project organization"); projects.command("detect").description("Auto-detect current project").action(async () => { const manager = ProjectManager.getInstance(); const project = await manager.detectProject(); console.log(chalk.green("\u2713 Project detected:")); console.log(chalk.cyan(" Name:"), project.name); console.log( chalk.cyan(" Organization:"), project.organization || "none" ); console.log(chalk.cyan(" Account Type:"), project.accountType); console.log(chalk.cyan(" Private:"), project.isPrivate ? "Yes" : "No"); console.log( chalk.cyan(" Language:"), project.primaryLanguage || "unknown" ); console.log(chalk.cyan(" Framework:"), project.framework || "unknown"); console.log(chalk.cyan(" ID:"), project.id); }); projects.command("scan").description("Scan and auto-categorize all Git projects").option("-p, --paths <paths...>", "Custom paths to scan").action(async (options) => { const manager = ProjectManager.getInstance(); console.log(chalk.yellow("\u{1F50D} Scanning for Git repositories...")); await manager.scanAndCategorizeAllProjects(options.paths); const allProjects = manager.getAllProjects(); console.log(chalk.green(`\u2713 Found ${allProjects.length} projects`)); const byType = {}; const byOrg = {}; for (const project of allProjects) { byType[project.accountType] = (byType[project.accountType] || 0) + 1; if (project.organization) { byOrg[project.organization] = (byOrg[project.organization] || 0) + 1; } } console.log("\n\u{1F4CA} Summary by Account Type:"); for (const [type, count] of Object.entries(byType)) { console.log(` ${type}: ${count} projects`); } console.log("\n\u{1F3E2} Top Organizations:"); const topOrgs = Object.entries(byOrg).sort((a, b) => b[1] - a[1]).slice(0, 5); for (const [org, count] of topOrgs) { console.log(` ${org}: ${count} projects`); } }); projects.command("list").description("List all discovered projects").option( "-t, --type <type>", "Filter by account type (personal/work/opensource/client)" ).option("-o, --org <org>", "Filter by organization").option("-l, --language <lang>", "Filter by language").action((options) => { const manager = ProjectManager.getInstance(); let projectList = manager.getAllProjects(); if (options.type) { projectList = projectList.filter((p) => p.accountType === options.type); } if (options.org) { projectList = projectList.filter((p) => p.organization === options.org); } if (options.language) { projectList = projectList.filter( (p) => p.primaryLanguage?.toLowerCase().includes(options.language.toLowerCase()) ); } if (projectList.length === 0) { console.log(chalk.yellow("No projects found matching criteria")); return; } const table = new Table({ head: [ "Name", "Organization", "Type", "Language", "Framework", "Last Accessed" ], style: { head: ["cyan"] } }); for (const project of projectList.slice(0, 20)) { table.push([ project.name, project.organization || "-", project.accountType, project.primaryLanguage || "-", project.framework || "-", new Date(project.lastAccessed).toLocaleDateString() ]); } console.log(table.toString()); if (projectList.length > 20) { console.log( chalk.gray(` ... and ${projectList.length - 20} more projects`) ); } }); projects.command("orgs").description("List all detected organizations").action(() => { const manager = ProjectManager.getInstance(); const allProjects = manager.getAllProjects(); const orgMap = {}; for (const project of allProjects) { if (project.organization) { if (!orgMap[project.organization]) { orgMap[project.organization] = { count: 0, types: /* @__PURE__ */ new Set() }; } orgMap[project.organization].count++; orgMap[project.organization].types.add(project.accountType); } } const table = new Table({ head: ["Organization", "Projects", "Account Types"], style: { head: ["cyan"] } }); const sorted = Object.entries(orgMap).sort( (a, b) => b[1].count - a[1].count ); for (const [org, data] of sorted) { table.push([ org, data.count.toString(), Array.from(data.types).join(", ") ]); } console.log(table.toString()); }); projects.command("config-org <name>").description("Configure an organization").option( "-t, --type <type>", "Organization type (company/personal/opensource/client)" ).option( "-a, --account <account>", "Account type (personal/work/opensource/client)" ).option("-d, --domain <domain>", "Add domain pattern").action((name, options) => { const manager = ProjectManager.getInstance(); manager.saveOrganization({ name, type: options.type || "company", accountType: options.account || "work", domains: options.domain ? [options.domain] : [], githubOrgs: [name], autoPatterns: [] }); console.log(chalk.green(`\u2713 Organization '${name}' configured`)); }); projects.command("report").description("Generate project statistics report").action(() => { const manager = ProjectManager.getInstance(); const report = manager.generateReport(); console.log(chalk.cyan("\n\u{1F4CA} Project Statistics:\n")); console.log(report); }); projects.command("switch <path>").description("Switch to a different project context").action(async (projectPath) => { const manager = ProjectManager.getInstance(); const project = await manager.detectProject(projectPath); console.log(chalk.green(`\u2713 Switched to project: ${project.name}`)); console.log( chalk.cyan(` Organization: ${project.organization || "none"}`) ); console.log(chalk.cyan(` Account Type: ${project.accountType}`)); }); projects.command("organize").description("Auto-organize projects into accounts").action(async () => { const manager = ProjectManager.getInstance(); console.log(chalk.yellow("\u{1F504} Auto-organizing projects...")); await manager.scanAndCategorizeAllProjects(); const allProjects = manager.getAllProjects(); const organized = { personal: [], work: [], opensource: [], client: [] }; for (const project of allProjects) { organized[project.accountType]?.push(project.name); } console.log(chalk.green("\n\u2713 Projects organized by account type:\n")); for (const [type, projects2] of Object.entries(organized)) { if (projects2.length > 0) { console.log( chalk.cyan(`${type.toUpperCase()} (${projects2.length} projects):`) ); for (const proj of projects2.slice(0, 5)) { console.log(` - ${proj}`); } if (projects2.length > 5) { console.log(chalk.gray(` ... and ${projects2.length - 5} more`)); } console.log(); } } }); } export { registerProjectCommands };