Initial commit: Claude Session Manager with complete CLI toolset

- Add TypeScript project structure with Bun runtime
- Implement CLI commands for session, project, stats, and optimization
- Add SQLite database integration with prepared statements
- Include AGENTS.md for development guidelines
- Add Makefile for common development tasks
- Configure ESLint and TypeScript with strict mode
This commit is contained in:
William Valentin
2025-10-31 17:38:48 -07:00
commit 0625d457be
28 changed files with 3859 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
],
plugins: ['@typescript-eslint'],
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
project: './tsconfig.json',
},
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'prefer-const': 'error',
'no-var': 'error',
'no-unused-vars': 'off', // Turn off base rule as it conflicts with @typescript-eslint version
},
env: {
node: true,
es2022: true,
},
globals: {
Bun: 'readonly',
},
};
+17
View File
@@ -0,0 +1,17 @@
node_modules/
dist/
coverage/
*.log
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
data/*.db
data/*.db-journal
.DS_Store
.vscode/
.idea/
*.swp
*.swo
*~
+23
View File
@@ -0,0 +1,23 @@
# Agent Guidelines
## Build Commands
- `bun run build` - Build the project (runs typecheck first)
- `bun run dev` - Development mode with hot reload
- `bun run test` - Run all tests
- `bun run lint` - Run ESLint on TypeScript files
- `bun run typecheck` - Run TypeScript type checking without emitting
## Code Style Guidelines
- Use TypeScript with strict mode enabled (noImplicitAny: false, strictNullChecks: false)
- Import style: ES6 imports with `import { X } from './path'` for named exports
- Use `@/*` path alias for src directory imports
- Naming: camelCase for variables/functions, PascalCase for classes/interfaces
- File structure: Organize by domain (cli/, database/, models/, services/, templates/)
- Error handling: Use try/catch blocks, log errors with logger utility
- Database: Use SQLite with prepared statements, map rows to interfaces
- CLI: Use Commander.js for command structure, chalk for colored output
- Date handling: Use moment.js for date formatting/manipulation
- UUID generation: Use uuid v4 for IDs
- No explicit return types required (inferred)
- Prefer const over let, avoid var
- Use Bun runtime features where appropriate
+50
View File
@@ -0,0 +1,50 @@
.PHONY: help build dev test lint typecheck clean start install
# Default target
help:
@echo "Available commands:"
@echo " install Install dependencies"
@echo " build Build the project"
@echo " dev Run in development mode with hot reload"
@echo " start Run the built application"
@echo " test Run all tests"
@echo " lint Run ESLint"
@echo " typecheck Run TypeScript type checking"
@echo " clean Clean build artifacts"
@echo " help Show this help message"
# Install dependencies
install:
bun install
# Build the project (includes typecheck)
build:
bun run build
# Development mode with hot reload
dev:
bun run dev
# Run the built application
start:
bun run start
# Run all tests
test:
bun run test
# Run ESLint
lint:
bun run lint
# Run TypeScript type checking
typecheck:
bun run typecheck
# Clean build artifacts
clean:
bun run clean
# Development workflow: install, typecheck, lint, test
check: install typecheck lint test
@echo "All checks passed!"
+36
View File
@@ -0,0 +1,36 @@
Here are the best tips and best practices for maximizing your Claude Code usage and avoiding those dreaded limits:
### 1. Master Your Context (The Token Saver)
Every message you send re-sends the entire conversation history to Claude, which is what eats up your tokens. The goal is to keep this history short and clean.
* **Use `/clear` Frequently:** This is the most crucial command. Use it to completely wipe the current conversation history when you finish a task, switch to an unrelated bug, or start a new feature.
* **Tip:** When you clear, have Claude first **write a summary** of the finished work to a file (e.g., `session-notes.md`). Then, in the new session, simply reference that file to catch Claude up without re-sending the hundreds of messages.
* **Use `/compact`:** If the conversation is getting long but you still need the history, use the `/compact` command. Claude will summarize the history, reducing the token count while preserving key context.
* **Leverage `CLAUDE.md` for Long-Term Memory:** Create a `CLAUDE.md` file in your project root. Claude Code reads this automatically at the start of every session. Use it for:
* Project architecture and tech stack.
* Coding style guides (e.g., "Use snake\_case for APIs").
* Common commands (e.g., `npm run test`).
* **This saves you tokens** by preventing you from repeating the same instructions or context in every new chat.
* **Target Your Files:** Instead of letting Claude try to search for the right file, explicitly mention the files it needs to read or modify using the `@` symbol or full file paths. This prevents Claude from wasting tokens reading unnecessary parts of your codebase.
### 2. Optimize Your Workflow (The Efficiency Booster)
* **Batch Your Requests:** Group similar requests or steps into a single, detailed prompt.
* *Instead of:* 1. "Fix the typo in the config file." 2. "Now, run the tests." 3. "Commit the change."
* *Use:* "Fix the typo in the config file, run the unit tests, and if they pass, commit the change with the message 'fix: corrected config typo'."
* **Use the Right Model for the Job:**
* **Claude Opus (Max Plans):** Reserve Opus for high-level, complex tasks like architecture planning, intricate debugging, or large refactors. Opus consumes your allowance much faster.
* **Claude Sonnet:** Use Sonnet for implementation, writing boilerplate code, documentation, or routine fixes. It offers a much higher number of prompts per session.
* **Adopt a "Plan First" Approach:**
1. **Plan:** Ask Claude to first create a detailed, step-by-step plan for the task. You can use phrases like `"think hard"` to give it a higher computation budget for this step.
2. **Review:** Review and approve the plan.
3. **Execute:** Give the final go-ahead. This avoids wasting tokens on a long, iterative conversation if Claude was on the wrong path from the start.
### 3. Be Strategic with the 5-Hour Window (The Time Hack)
* **The Overlap Hack:** If you know you have an intensive work session coming up, you can deliberately send a very short message 2-3 hours before you start your main work. This starts the 5-hour timer for your first session. When your first session allowance is depleted, the second 5-hour session will be due for a reset, allowing you to seamlessly start fresh and continue working without a prolonged break.
* **Cycle Your Work:** When your Claude Code session runs out, switch to tasks that do **not** require the AI (e.g., meeting prep, email, manual testing, or architecture review). Come back to Claude when the 5-hour window has reset.
These strategies allow you to treat Claude Code like a powerful, highly-focused partner, giving it concentrated work blocks to maximize your session usage.
+310
View File
@@ -0,0 +1,310 @@
# Claude Session Manager
A powerful tool to manage and optimize your Claude Code programming sessions, implementing all the best practices from [PLAN.md](./PLAN.md).
## Features
### 🎯 Session Management
- **5-Hour Window Tracking**: Automatically manages Claude's 5-hour session windows
- **Overlap Hack**: Schedule overlap sessions for extended work periods
- **Smart Scheduling**: Optimize session timing based on usage patterns
- **Active Session Monitoring**: Real-time tracking of current sessions
### 📊 Token Optimization
- **Usage Analytics**: Detailed token consumption tracking
- **Optimization Recommendations**: AI-powered suggestions to reduce token usage
- **Efficiency Scoring**: Quantitative measurement of session efficiency
- **Cost Analysis**: Track token usage by model and project
### 🗂️ Project Context Management
- **CLAUDE.md Generation**: Auto-generate project context files
- **Tech Stack Detection**: Automatically identify project technologies
- **Coding Style Analysis**: Detect and document coding conventions
- **Common Commands**: Track frequently used project commands
### 📝 Smart Note-Taking
- **Session Summaries**: Automatic generation of session summaries
- **Quick Notes**: Categorized note-taking during sessions
- **Searchable History**: Find information across all sessions
- **Export Capabilities**: Export session history in various formats
### 🚀 Workflow Optimization
- **Task Scheduling**: Optimal scheduling for upcoming tasks
- **Model Recommendations**: AI-powered model selection suggestions
- **Batching Suggestions**: Recommendations for grouping similar tasks
- **Anomaly Detection**: Identify workflow inefficiencies
## Installation
```bash
# Clone the repository
git clone <repository-url>
cd claude-session-manager
# Install dependencies
npm install
# Build the project
npm run build
# Install globally (optional)
npm install -g .
```
## Quick Start
### 1. Create a Project
```bash
csm project create --name "My Project" --path "/path/to/project"
```
### 2. Start a Session
```bash
csm session start --project <project-id> --model sonnet
```
### 3. Track Usage
```bash
csm session track <session-id> <tokens> <messages>
```
### 4. End Session
```bash
csm session end --summary "Implemented user authentication"
```
### 5. View Statistics
```bash
csm stats today
```
## Command Reference
### Project Management
```bash
# Create new project
csm project create [-n <name>] [-p <path>]
# List all projects
csm project list
# Show project details
csm project info <project-id>
# Generate CLAUDE.md
csm project claude-md <project-id>
# View session summaries
csm project notes <project-id> [-l <limit>]
# Export session history
csm project export <project-id> [-f <format>] [-o <output>]
```
### Session Management
```bash
# Start new session
csm session start [-p <project-id>] [-m <model>]
# End session
csm session end [-i <session-id>] [-s <summary>]
# Track token usage
csm session track <session-id> <tokens> <messages>
# Add note to session
csm session note <note> [-t <type>] [-i <session-id>]
# List recent sessions
csm session list [-l <limit>] [-p <project-id>]
# Check session status
csm session status
# Schedule overlap hack
csm session overlap [-d <hours>]
```
### Statistics & Analytics
```bash
# Show usage statistics
csm stats usage [-d <days>]
# Today's usage and recommendations
csm stats today
# Workflow efficiency analysis
csm stats optimize [-p <project-id>]
# Detect anomalies and trends
csm stats anomalies
# Token usage breakdown
csm stats tokens [-p <project-id>]
```
### Optimization Tools
```bash
# Get suggestions for specific task
csm optimize suggest <task> [-c <complexity>]
# Generate optimal schedule
csm optimize schedule
# Analyze overall workflow
csm optimize workflow [-p <project-id>]
# Show optimization tips
csm optimize tips
```
## Usage Examples
### Example 1: Daily Workflow
```bash
# Start your day by checking status
csm session status
# Start a new session for feature development
csm session start --project my-web-app --model sonnet
# During development, add quick notes
csm session note "Fixed login bug" --type bug
csm session note "Add password validation" --type feature
# Track usage periodically
csm session track abc123 25000 15
# End session with summary
csm session end --summary "Implemented user authentication with password validation"
# Check today's optimization recommendations
csm stats today
```
### Example 2: Complex Architecture Task
```bash
# Get optimization suggestions first
csm optimize suggest "Design microservices architecture" --complexity complex
# Start with Opus for complex task
csm session start --project my-system --model opus
# Schedule overlap hack for extended work
csm session overlap --delay 2
# End with detailed summary
csm session end --summary "Designed comprehensive microservices architecture with service mesh"
```
### Example 3: Project Setup
```bash
# Create new project
csm project create --name "E-commerce Platform" --path ./ecommerce
# Generate CLAUDE.md for context
csm project claude-md "E-commerce Platform"
# View project info
csm project info "E-commerce Platform"
```
## Best Practices (from PLAN.md)
### Context Management
- **Use `/clear` frequently** - Most crucial command for token savings
- **Use `/compact`** for long but necessary conversations
- **Leverage `CLAUDE.md`** for long-term project memory
- **Target specific files** with `@` symbol or full paths
### Workflow Optimization
- **Batch similar requests** into single detailed prompts
- **Use Sonnet for implementation**, Opus for complex tasks
- **Adopt "Plan First" approach** - plan, review, execute
- **Use "think hard"** for complex planning tasks
### 5-Hour Window Strategy
- **Use overlap hack** 2-3 hours before intensive work
- **Cycle to non-AI tasks** when session runs out
- **Schedule complex tasks** for fresh sessions
- **Track window timing** to maximize productivity
## Configuration
The tool stores all data in a local SQLite database (`./data/claude-sessions.db`). Configuration options:
- Database location: Can be overridden with environment variable `CSM_DB_PATH`
- Default model: Sonnet (recommended for most tasks)
- Token limits: Configurable in service classes
## Advanced Features
### Workflow Automation
```bash
# Generate optimal schedule for multiple tasks
csm optimize schedule
# Enter your tasks interactively
# Get optimized schedule with model recommendations
# Analyze workflow efficiency
csm optimize workflow
# Get comprehensive analysis with actionable recommendations
```
### Token Optimization
```bash
# Get personalized recommendations
csm stats today
# See high-priority optimization suggestions
# Track token efficiency trends
csm stats usage --days 30
# Monitor your optimization progress over time
```
### Project Intelligence
```bash
# Auto-generate comprehensive project context
csm project claude-md <project-id>
# Includes tech stack, coding style, architecture, and common commands
# Export session history for documentation
csm project export <project-id> --format markdown --output docs/session-history.md
```
## Troubleshooting
### Common Issues
1. **Database errors**: Ensure write permissions to `./data/` directory
2. **Project not found**: Use `csm project list` to see available projects
3. **Session tracking**: Always track tokens before ending session for accurate statistics
### Getting Help
```bash
# Show all commands
csm --help
# Show command-specific help
csm session --help
csm project --help
csm stats --help
csm optimize --help
# Show optimization tips
csm optimize tips
```
## Contributing
This tool implements the best practices from PLAN.md. Contributions welcome for:
- Additional optimization algorithms
- New workflow automation features
- Enhanced analytics and reporting
- Integration with other development tools
## License
MIT License - see LICENSE file for details.
+404
View File
@@ -0,0 +1,404 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "claude-session-manager",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^11.1.0",
"inquirer": "^9.2.12",
"moment": "^2.29.4",
"table": "^6.8.1",
"uuid": "^9.0.1",
},
"devDependencies": {
"@types/inquirer": "^9.0.7",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.13.0",
"bun-types": "^1.2.20",
"eslint": "^8.54.0",
"typescript": "^5.3.2",
},
},
},
"packages": {
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
"@inquirer/external-editor": ["@inquirer/external-editor@1.0.2", "", { "dependencies": { "chardet": "^2.1.0", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ=="],
"@inquirer/figures": ["@inquirer/figures@1.0.14", "", {}, "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@types/inquirer": ["@types/inquirer@9.0.9", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
"@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
"@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@6.21.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/type-utils": "6.21.0", "@typescript-eslint/utils": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@6.21.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag=="],
"@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@6.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
"cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
"clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"inquirer": ["inquirer@9.3.8", "", { "dependencies": { "@inquirer/external-editor": "^1.0.2", "@inquirer/figures": "^1.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "1.0.0", "ora": "^5.4.1", "run-async": "^3.0.0", "rxjs": "^7.8.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="],
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"run-async": ["run-async@3.0.0", "", {}, "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="],
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"table/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"table/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
}
}
+33
View File
@@ -0,0 +1,33 @@
[install]
# Use npm registry
registry = "https://registry.npmjs.org/"
# Cache in node_modules for compatibility
cache = true
[run]
# Enable hot reload by default
hot = true
[test]
# Enable test coverage
coverage = true
[build]
# Target current Node.js version for compatibility
target = "node"
# Minify production builds
minify = true
# Enable sourcemaps for debugging
sourcemap = true
# Performance optimizations
[optimize]
# Enable Bun's optimizer
enabled = true
# Lockfile configuration
[lockfile]
# Save exact versions
save = true
# Print install summary
print = "yarn"
+49
View File
@@ -0,0 +1,49 @@
{
"name": "claude-session-manager",
"version": "1.0.0",
"description": "A tool to manage Claude Code programming sessions with optimization and scheduling",
"main": "dist/index.js",
"bin": {
"csm": "dist/index.js"
},
"scripts": {
"build": "bun build ./src/index.ts --outdir ./dist --target node --minify --sourcemap",
"dev": "bun --watch src/index.ts",
"start": "bun dist/index.js",
"test": "bun test",
"lint": "bunx eslint src/**/*.ts",
"typecheck": "bun tsc --noEmit",
"clean": "rm -rf dist",
"prebuild": "bun run typecheck",
"postbuild": "chmod +x dist/index.js"
},
"keywords": [
"claude",
"session",
"management",
"optimization",
"ai"
],
"author": "",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^11.1.0",
"inquirer": "^9.2.12",
"moment": "^2.29.4",
"table": "^6.8.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/inquirer": "^9.0.7",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.13.0",
"@typescript-eslint/parser": "^6.13.0",
"bun-types": "^1.2.20",
"eslint": "^8.54.0",
"typescript": "^5.3.2"
},
"engines": {
"bun": ">=1.0.0"
}
}
+273
View File
@@ -0,0 +1,273 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { Database } from '../../database/Database';
import { WorkflowOptimizer } from '../../services/WorkflowOptimizer';
import { SessionManager } from '../../services/SessionManager';
import { TokenTracker } from '../../services/TokenTracker';
import inquirer from 'inquirer';
import { table } from 'table';
import moment from 'moment';
export function optimizeCommands(program: Command): void {
const db = new Database();
const sessionManager = new SessionManager(db);
const tokenTracker = new TokenTracker(db);
const workflowOptimizer = new WorkflowOptimizer(db, tokenTracker, sessionManager);
const optimizeCmd = program.command('optimize').description('Optimize your Claude workflow');
optimizeCmd
.command('suggest')
.description('Get optimization suggestions for a task')
.argument('<task>', 'Task description')
.option('-c, --complexity <complexity>', 'Task complexity (simple|medium|complex)', 'medium')
.action(async (task, options) => {
try {
const suggestion = await workflowOptimizer.suggestOptimalSession(task, options.complexity);
console.log(chalk.cyan('\\nOptimization Suggestions:'));
console.log(chalk.gray('─'.repeat(40)));
console.log(chalk.yellow(`\\nTask: ${task}`));
console.log(chalk.cyan(`\\nRecommended Model: ${suggestion.modelType.toUpperCase()}`));
console.log(`Estimated Tokens: ${suggestion.estimatedTokens.toLocaleString()}`);
if (suggestion.batchingSuggestions.length > 0) {
console.log(chalk.cyan('\\nBatching Suggestions:'));
suggestion.batchingSuggestions.forEach((suggestion, index) => {
console.log(`${index + 1}. ${suggestion}`);
});
}
if (suggestion.preparationTips.length > 0) {
console.log(chalk.cyan('\\nPreparation Tips:'));
suggestion.preparationTips.forEach((tip, index) => {
console.log(`${index + 1}. ${tip}`);
});
}
} catch (error: any) {
console.error(chalk.red('Error getting suggestions:'), error.message);
}
});
optimizeCmd
.command('schedule')
.description('Generate optimal schedule for upcoming tasks')
.action(async () => {
try {
console.log(chalk.cyan('Enter your upcoming tasks (one per line). Empty line to finish:'));
const tasks = [];
let taskCount = 0;
while (true) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'description',
message: `Task ${taskCount + 1}:`,
when: () => taskCount === 0 || tasks.length > 0
},
{
type: 'list',
name: 'complexity',
message: 'Complexity:',
choices: ['simple', 'medium', 'complex'],
when: (answers) => answers.description !== ''
},
{
type: 'input',
name: 'duration',
message: 'Estimated duration (minutes):',
validate: (input) => !isNaN(input) && parseInt(input) > 0 || 'Please enter a valid number',
when: (answers) => answers.description !== ''
},
{
type: 'list',
name: 'priority',
message: 'Priority:',
choices: ['high', 'medium', 'low'],
when: (answers) => answers.description !== ''
}
]);
if (answers.description === '') {
break;
}
tasks.push({
description: answers.description,
complexity: answers.complexity,
estimatedDuration: parseInt(answers.duration),
priority: answers.priority
});
taskCount++;
const { continue: shouldContinue } = await inquirer.prompt([
{
type: 'confirm',
name: 'continue',
message: 'Add another task?',
default: true
}
]);
if (!shouldContinue) break;
}
if (tasks.length === 0) {
console.log(chalk.yellow('No tasks entered.'));
return;
}
const schedule = await workflowOptimizer.generateOptimalSchedule(tasks);
console.log(chalk.cyan('\\nOptimal Schedule:'));
console.log(chalk.gray('─'.repeat(50)));
const tableData = [
['Time', 'Task', 'Model', 'Grouping']
];
schedule.schedule.forEach(item => {
tableData.push([
moment(item.recommendedTime).format('HH:mm'),
item.task.substring(0, 30) + (item.task.length > 30 ? '...' : ''),
item.modelType.toUpperCase(),
item.grouping
]);
});
console.log(table(tableData));
console.log(chalk.cyan(`\\nTotal Estimated Tokens: ${schedule.totalEstimatedTokens.toLocaleString()}`));
if (schedule.optimizationNotes.length > 0) {
console.log(chalk.yellow('\\nOptimization Notes:'));
schedule.optimizationNotes.forEach(note => {
console.log(`${note}`);
});
}
} catch (error: any) {
console.error(chalk.red('Error generating schedule:'), error.message);
}
});
optimizeCmd
.command('workflow')
.description('Analyze and optimize your overall workflow')
.option('-p, --project <projectId>', 'Focus on specific project')
.action(async (options) => {
try {
const analysis = await workflowOptimizer.analyzeWorkflowEfficiency(options.project);
console.log(chalk.cyan('\\n🔍 Workflow Analysis'));
console.log(chalk.gray('─'.repeat(40)));
// Score with visual indicator
const score = analysis.score;
let emoji, color;
if (score >= 80) {
emoji = '🟢';
color = chalk.green;
} else if (score >= 60) {
emoji = '🟡';
color = chalk.yellow;
} else {
emoji = '🔴';
color = chalk.red;
}
console.log(`\\n${emoji} Efficiency Score: ${color(`${score}/100`)}`);
// Priority recommendations
if (analysis.recommendations.length > 0) {
console.log(chalk.cyan('\\n🎯 Priority Actions:'));
const highPriority = analysis.recommendations.filter(r => r.priority === 'high');
const mediumPriority = analysis.recommendations.filter(r => r.priority === 'medium');
if (highPriority.length > 0) {
console.log(chalk.red('\\nHigh Priority:'));
highPriority.forEach((rec, index) => {
console.log(`${index + 1}. ${rec.message}`);
if (rec.potentialSavings) {
console.log(chalk.gray(` 💰 Save ${rec.potentialSavings.toLocaleString()} tokens`));
}
});
}
if (mediumPriority.length > 0) {
console.log(chalk.yellow('\\nMedium Priority:'));
mediumPriority.forEach((rec, index) => {
console.log(`${index + 1}. ${rec.message}`);
});
}
}
// Quick wins
console.log(chalk.cyan('\\n⚡ Quick Wins:'));
console.log('• Use /clear before starting unrelated tasks');
console.log('• Batch similar requests into single prompts');
console.log('• Use Sonnet for implementation, Opus for architecture');
console.log('• Schedule overlap hack for intensive work sessions');
// Insights
if (analysis.insights.length > 0) {
console.log(chalk.cyan('\\n💡 Key Insights:'));
analysis.insights.forEach(insight => {
console.log(`${insight}`);
});
}
// Next steps
console.log(chalk.cyan('\\n📋 Next Steps:'));
console.log('1. Implement high-priority recommendations first');
console.log('2. Set up overlap hack for your next intensive session');
console.log('3. Review and update your project CLAUDE.md files');
console.log('4. Use "csm optimize suggest" before starting new tasks');
} catch (error: any) {
console.error(chalk.red('Error analyzing workflow:'), error.message);
}
});
optimizeCmd
.command('tips')
.description('Show optimization tips based on PLAN.md guidelines')
.action(async () => {
try {
console.log(chalk.cyan('\\n🚀 Claude Code Optimization Tips'));
console.log(chalk.gray('─'.repeat(50)));
console.log(chalk.yellow('\\n📚 Context Management (Token Savers):'));
console.log('• Use /clear frequently - most crucial command');
console.log('• Use /compact for long but needed conversations');
console.log('• Leverage CLAUDE.md for long-term project memory');
console.log('• Target specific files with @ symbol or paths');
console.log(chalk.yellow('\\n⚡ Workflow Optimization:'));
console.log('• Batch similar requests into single detailed prompts');
console.log('• Use Sonnet for implementation, Opus for complex tasks');
console.log('• Adopt "Plan First" approach - plan, review, execute');
console.log('• Use "think hard" for complex planning tasks');
console.log(chalk.yellow('\\n⏰ 5-Hour Window Strategy:'));
console.log('• Use overlap hack 2-3 hours before intensive work');
console.log('• Cycle to non-AI tasks when session runs out');
console.log('• Schedule complex tasks for fresh sessions');
console.log('• Track window timing to maximize productivity');
console.log(chalk.yellow('\\n🎯 Best Practices:'));
console.log('• Write session summaries before using /clear');
console.log('• Reference summary files instead of long histories');
console.log('• Group implementation tasks together');
console.log('• Save architecture work for Opus sessions');
console.log(chalk.green('\\n💡 Pro Tip:'));
console.log('Create a routine: Start with optimization check, work in focused blocks, end with summary');
} catch (error: any) {
console.error(chalk.red('Error showing tips:'), error.message);
}
});
}
+281
View File
@@ -0,0 +1,281 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { Database } from '../../database/Database';
import { ProjectManager } from '../../services/ProjectManager';
import { NoteTaker } from '../../services/NoteTaker';
import inquirer from 'inquirer';
import { table } from 'table';
import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
export function projectCommands(program: Command): void {
const db = new Database();
const projectManager = new ProjectManager(db);
const noteTaker = new NoteTaker(db);
const projectCmd = program.command('project').description('Manage projects');
projectCmd
.command('create')
.description('Create a new project')
.option('-n, --name <name>', 'Project name')
.option('-p, --path <path>', 'Project path')
.action(async (options) => {
try {
let { name, path: projectPath } = options;
if (!name || !projectPath) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name:',
when: !name,
validate: (input) => input.trim() !== '' || 'Project name is required'
},
{
type: 'input',
name: 'path',
message: 'Project path:',
when: !projectPath,
default: process.cwd(),
validate: (input) => existsSync(input) || 'Path does not exist'
}
]);
name = answers.name;
projectPath = answers.path;
}
const project = await projectManager.createProject(name, projectPath);
console.log(chalk.green('✓ Project created successfully!'));
console.log(chalk.cyan(`Name: ${project.name}`));
console.log(chalk.cyan(`Path: ${project.path}`));
console.log(chalk.cyan(`Tech Stack: ${project.techStack.join(', ')}`));
// Ask if user wants to generate CLAUDE.md
const { generateClaudeMd } = await inquirer.prompt([
{
type: 'confirm',
name: 'generateClaudeMd',
message: 'Generate CLAUDE.md file?',
default: true
}
]);
if (generateClaudeMd) {
await projectManager.generateClaudeMd(project.id);
console.log(chalk.green('✓ CLAUDE.md generated successfully!'));
}
} catch (error: any) {
console.error(chalk.red('Error creating project:'), error.message);
}
});
projectCmd
.command('list')
.description('List all projects')
.action(async () => {
try {
const projects = await projectManager.getAllProjects();
if (projects.length === 0) {
console.log(chalk.yellow('No projects found.'));
return;
}
const tableData = [
['Name', 'Path', 'Tech Stack', 'Sessions', 'Tokens', 'Last Active']
];
for (const project of projects) {
tableData.push([
project.name,
project.path,
project.techStack.slice(0, 3).join(', ') + (project.techStack.length > 3 ? '...' : ''),
project.totalSessions.toString(),
project.totalTokensUsed.toLocaleString(),
new Date(project.lastActive).toLocaleDateString()
]);
}
console.log(chalk.cyan('\\nProjects:'));
console.log(table(tableData));
} catch (error: any) {
console.error(chalk.red('Error listing projects:'), error.message);
}
});
projectCmd
.command('info')
.description('Show detailed project information')
.argument('<projectId>', 'Project ID or name')
.action(async (projectIdOrName) => {
try {
let project;
// Try to find by ID first, then by name
project = await projectManager.getProject(projectIdOrName);
if (!project) {
const projects = await projectManager.getAllProjects();
project = projects.find(p => p.name.toLowerCase() === projectIdOrName.toLowerCase());
}
if (!project) {
console.log(chalk.yellow('Project not found.'));
return;
}
console.log(chalk.cyan(`\\nProject: ${project.name}`));
console.log(chalk.gray('─'.repeat(50)));
console.log(`Path: ${project.path}`);
console.log(`Created: ${new Date(project.createdAt).toLocaleDateString()}`);
console.log(`Last Active: ${new Date(project.lastActive).toLocaleDateString()}`);
console.log(`Total Sessions: ${project.totalSessions}`);
console.log(`Total Tokens: ${project.totalTokensUsed.toLocaleString()}`);
console.log(chalk.cyan('\\nTech Stack:'));
project.techStack.forEach(tech => console.log(`${tech}`));
console.log(chalk.cyan('\\nCoding Style:'));
console.log(` • Naming: ${project.codingStyle.namingConvention}`);
console.log(` • Structure: ${project.codingStyle.fileStructure}`);
project.codingStyle.conventions.forEach(conv => console.log(`${conv}`));
console.log(chalk.cyan('\\nCommon Commands:'));
project.commonCommands.forEach(cmd => console.log(`${cmd}`));
console.log(chalk.cyan('\\nArchitecture:'));
console.log(` ${project.architecture}`);
if (project.claudeMdContent) {
console.log(chalk.green('\\n✓ CLAUDE.md file exists'));
} else {
console.log(chalk.yellow('\\n⚠ CLAUDE.md file not found. Generate with: csm project claude-md <projectId>'));
}
} catch (error: any) {
console.error(chalk.red('Error getting project info:'), error.message);
}
});
projectCmd
.command('claude-md')
.description('Generate or update CLAUDE.md file')
.argument('<projectId>', 'Project ID or name')
.action(async (projectIdOrName) => {
try {
let project;
project = await projectManager.getProject(projectIdOrName);
if (!project) {
const projects = await projectManager.getAllProjects();
project = projects.find(p => p.name.toLowerCase() === projectIdOrName.toLowerCase());
}
if (!project) {
console.log(chalk.yellow('Project not found.'));
return;
}
const content = await projectManager.generateClaudeMd(project.id);
console.log(chalk.green('✓ CLAUDE.md generated successfully!'));
console.log(chalk.cyan(`File: ${join(project.path, 'CLAUDE.md')}`));
// Show preview
const lines = content.split('\\n');
const preview = lines.slice(0, 20).join('\\n');
console.log(chalk.cyan('\\nPreview:'));
console.log(preview);
if (lines.length > 20) {
console.log(chalk.gray(`... and ${lines.length - 20} more lines`));
}
} catch (error: any) {
console.error(chalk.red('Error generating CLAUDE.md:'), error.message);
}
});
projectCmd
.command('notes')
.description('Show session summaries for a project')
.argument('<projectId>', 'Project ID or name')
.option('-l, --limit <limit>', 'Number of recent summaries to show', '5')
.action(async (projectIdOrName, options) => {
try {
let project;
project = await projectManager.getProject(projectIdOrName);
if (!project) {
const projects = await projectManager.getAllProjects();
project = projects.find(p => p.name.toLowerCase() === projectIdOrName.toLowerCase());
}
if (!project) {
console.log(chalk.yellow('Project not found.'));
return;
}
const summaries = await noteTaker.getProjectSessionSummaries(project.id, parseInt(options.limit));
if (summaries.length === 0) {
console.log(chalk.yellow('No session summaries found.'));
return;
}
console.log(chalk.cyan(`\\nRecent Session Summaries for ${project.name}:`));
console.log(chalk.gray('─'.repeat(50)));
summaries.forEach((summary, index) => {
console.log(chalk.yellow(`\\n${index + 1}. ${summary.split('\\n')[0]}`));
const preview = summary.split('\\n').slice(1, 8).join('\\n');
console.log(preview);
if (summary.split('\\n').length > 8) {
console.log(chalk.gray('...'));
}
});
} catch (error: any) {
console.error(chalk.red('Error getting notes:'), error.message);
}
});
projectCmd
.command('export')
.description('Export project session history')
.argument('<projectId>', 'Project ID or name')
.option('-f, --format <format>', 'Export format (markdown|json)', 'markdown')
.option('-o, --output <file>', 'Output file path')
.action(async (projectIdOrName, options) => {
try {
let project;
project = await projectManager.getProject(projectIdOrName);
if (!project) {
const projects = await projectManager.getAllProjects();
project = projects.find(p => p.name.toLowerCase() === projectIdOrName.toLowerCase());
}
if (!project) {
console.log(chalk.yellow('Project not found.'));
return;
}
const history = await noteTaker.exportSessionHistory(project.id, options.format);
let outputPath = options.output;
if (!outputPath) {
const timestamp = new Date().toISOString().split('T')[0];
const extension = options.format === 'json' ? 'json' : 'md';
outputPath = join(project.path, `session-history-${timestamp}.${extension}`);
}
writeFileSync(outputPath, history);
console.log(chalk.green('✓ Session history exported successfully!'));
console.log(chalk.cyan(`File: ${outputPath}`));
console.log(chalk.cyan(`Format: ${options.format}`));
} catch (error: any) {
console.error(chalk.red('Error exporting history:'), error.message);
}
});
}
+266
View File
@@ -0,0 +1,266 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { Database } from '../../database/Database';
import { SessionManager } from '../../services/SessionManager';
import { TokenTracker } from '../../services/TokenTracker';
import { NoteTaker } from '../../services/NoteTaker';
import inquirer from 'inquirer';
import { table } from 'table';
import moment from 'moment';
export function sessionCommands(program: Command): void {
const db = new Database();
const sessionManager = new SessionManager(db);
const tokenTracker = new TokenTracker(db);
const noteTaker = new NoteTaker(db);
const sessionCmd = program.command('session').description('Manage Claude sessions');
sessionCmd
.command('start')
.description('Start a new session')
.option('-p, --project <projectId>', 'Project ID')
.option('-m, --model <model>', 'Model type (opus|sonnet)', 'sonnet')
.action(async (options) => {
try {
let projectId = options.project;
if (!projectId) {
const projects = await db.all('SELECT * FROM projects ORDER BY lastActive DESC LIMIT 5');
if (projects.length === 0) {
console.log(chalk.yellow('No projects found. Create a project first with: csm project create'));
return;
}
const answers = await inquirer.prompt([
{
type: 'list',
name: 'projectId',
message: 'Select a project:',
choices: (projects as Array<{ name: string; path: string; id: string }>).map(p => ({
name: `${p.name} (${p.path})`,
value: p.id
}))
}
]);
projectId = answers.projectId;
}
const session = await sessionManager.createSession(projectId, options.model);
const project = await db.get('SELECT * FROM projects WHERE id = ?', [projectId]);
console.log(chalk.green('✓ Session started successfully!'));
console.log(chalk.cyan(`Session ID: ${session.id}`));
console.log(chalk.cyan(`Project: ${(project as { name: string })?.name}`));
console.log(chalk.cyan(`Model: ${session.modelType}`));
console.log(chalk.cyan(`Started: ${moment(session.startTime).format('YYYY-MM-DD HH:mm:ss')}`));
console.log(chalk.yellow('\\nTips for this session:'));
console.log('• Use /clear frequently to reset conversation history');
console.log('• Batch similar requests into single prompts');
console.log('• Use /compact when conversation gets long but context is needed');
console.log('• Track tokens with: csm session track <sessionId>');
} catch (error: any) {
console.error(chalk.red('Error starting session:'), error.message);
}
});
sessionCmd
.command('end')
.description('End the current session')
.option('-i, --id <sessionId>', 'Session ID (if not provided, ends active session)')
.option('-s, --summary <summary>', 'Session summary')
.action(async (options) => {
try {
let sessionId = options.id;
if (!sessionId) {
const activeSession = await sessionManager.getActiveSession();
if (!activeSession) {
console.log(chalk.yellow('No active session found.'));
return;
}
sessionId = activeSession.id;
}
const summary = options.summary || await noteTaker.generateSessionSummary(sessionId);
const session = await sessionManager.endSession(sessionId, summary);
console.log(chalk.green('✓ Session ended successfully!'));
console.log(chalk.cyan(`Duration: ${moment.duration(session.endTime!.getTime() - session.startTime.getTime()).humanize()}`));
console.log(chalk.cyan(`Tokens used: ${session.tokensUsed.toLocaleString()}`));
console.log(chalk.cyan(`Messages: ${session.messages}`));
const optimizationScore = await tokenTracker.calculateOptimizationScore(session);
console.log(chalk.cyan(`Optimization score: ${optimizationScore}/100`));
} catch (error: any) {
console.error(chalk.red('Error ending session:'), error.message);
}
});
sessionCmd
.command('track')
.description('Track token usage for a session')
.argument('<sessionId>', 'Session ID')
.argument('<tokens>', 'Tokens used')
.argument('<messages>', 'Number of messages')
.action(async (sessionId, tokens, messages) => {
try {
await sessionManager.updateSessionTokens(sessionId, parseInt(tokens), parseInt(messages));
await tokenTracker.trackTokenUsage(sessionId, parseInt(tokens));
console.log(chalk.green('✓ Token usage tracked successfully!'));
console.log(chalk.cyan(`Tokens: ${parseInt(tokens).toLocaleString()}`));
console.log(chalk.cyan(`Messages: ${messages}`));
} catch (error: any) {
console.error(chalk.red('Error tracking usage:'), error.message);
}
});
sessionCmd
.command('note')
.description('Add a note to the current session')
.argument('<note>', 'Note content')
.option('-t, --type <type>', 'Note type (bug|feature|question|idea)', 'general')
.option('-i, --id <sessionId>', 'Session ID (if not provided, uses active session)')
.action(async (note, options) => {
try {
let sessionId = options.id;
if (!sessionId) {
const activeSession = await sessionManager.getActiveSession();
if (!activeSession) {
console.log(chalk.yellow('No active session found.'));
return;
}
sessionId = activeSession.id;
}
if (options.type !== 'general') {
await noteTaker.addQuickNote(sessionId, options.type, note);
} else {
await noteTaker.takeSessionNote(sessionId, note);
}
console.log(chalk.green('✓ Note added successfully!'));
} catch (error: any) {
console.error(chalk.red('Error adding note:'), error.message);
}
});
sessionCmd
.command('list')
.description('List recent sessions')
.option('-l, --limit <limit>', 'Number of sessions to show', '10')
.option('-p, --project <projectId>', 'Filter by project')
.action(async (options) => {
try {
let sessions;
if (options.project) {
sessions = await db.all(
'SELECT * FROM sessions WHERE projectId = ? ORDER BY startTime DESC LIMIT ?',
[options.project, parseInt(options.limit)]
);
} else {
sessions = await sessionManager.getSessionHistory(parseInt(options.limit));
}
if (sessions.length === 0) {
console.log(chalk.yellow('No sessions found.'));
return;
}
const tableData = [
['ID', 'Project', 'Model', 'Tokens', 'Duration', 'Status']
];
for (const session of sessions) {
const project = await db.get('SELECT name FROM projects WHERE id = ?', [session.projectId]);
const duration = session.endTime
? moment.duration(session.endTime - session.startTime).humanize()
: 'Active';
tableData.push([
session.id.substring(0, 8),
(project as { name: string })?.name || 'Unknown',
session.modelType,
session.tokensUsed.toLocaleString(),
duration,
session.status
]);
}
console.log(chalk.cyan('\\nRecent Sessions:'));
console.log(table(tableData));
} catch (error: any) {
console.error(chalk.red('Error listing sessions:'), error.message);
}
});
sessionCmd
.command('status')
.description('Show current session and window status')
.action(async () => {
try {
const activeSession = await sessionManager.getActiveSession();
const currentWindow = await sessionManager.getCurrentWindow();
const nextWindow = await sessionManager.getNextAvailableWindow();
console.log(chalk.cyan('\\nSession Status:'));
if (activeSession) {
const project = await db.get('SELECT name FROM projects WHERE id = ?', [activeSession.projectId]);
console.log(chalk.green('✓ Active Session:'));
console.log(` ID: ${activeSession.id.substring(0, 8)}`);
console.log(` Project: ${(project as { name: string })?.name}`);
console.log(` Model: ${activeSession.modelType}`);
console.log(` Started: ${moment(activeSession.startTime).format('HH:mm:ss')}`);
console.log(` Tokens: ${activeSession.tokensUsed.toLocaleString()}`);
} else {
console.log(chalk.yellow('No active session'));
}
console.log(chalk.cyan('\\nWindow Status:'));
if (currentWindow) {
console.log(chalk.green('✓ In Active Window:'));
console.log(` Started: ${moment(currentWindow.startTime).format('HH:mm:ss')}`);
console.log(` Ends: ${moment(currentWindow.endTime).format('HH:mm:ss')}`);
console.log(` Remaining: ${moment.duration(currentWindow.endTime.getTime() - Date.now()).humanize()}`);
} else if (nextWindow) {
console.log(chalk.yellow('Next window available:'));
console.log(` Starts: ${moment(nextWindow).format('YYYY-MM-DD HH:mm:ss')}`);
console.log(` In: ${moment.duration(nextWindow.getTime() - Date.now()).humanize()}`);
} else {
console.log(chalk.yellow('No windows scheduled'));
}
} catch (error: any) {
console.error(chalk.red('Error checking status:'), error.message);
}
});
sessionCmd
.command('overlap')
.description('Schedule overlap hack for extended sessions')
.option('-d, --delay <hours>', 'Delay in hours before starting overlap', '2')
.action(async (options) => {
try {
const activeSession = await sessionManager.getActiveSession();
if (!activeSession) {
console.log(chalk.yellow('No active session found.'));
return;
}
const schedule = await sessionManager.scheduleOverlapHack(activeSession.id, parseInt(options.delay));
console.log(chalk.green('✓ Overlap hack scheduled!'));
console.log(chalk.cyan(`Current session: ${activeSession.id.substring(0, 8)}`));
console.log(chalk.cyan(`Overlap starts: ${moment(schedule.startTime).format('YYYY-MM-DD HH:mm:ss')}`));
console.log(chalk.cyan(`Overlap ends: ${moment(schedule.endTime).format('YYYY-MM-DD HH:mm:ss')}`));
console.log(chalk.yellow('\\nThis will give you a fresh 5-hour window while maintaining context!'));
} catch (error: any) {
console.error(chalk.red('Error scheduling overlap:'), error.message);
}
});
}
+239
View File
@@ -0,0 +1,239 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { Database } from '../../database/Database';
import { TokenTracker } from '../../services/TokenTracker';
import { WorkflowOptimizer } from '../../services/WorkflowOptimizer';
import { SessionManager } from '../../services/SessionManager';
import { table } from 'table';
import moment from 'moment';
export function statsCommands(program: Command): void {
const db = new Database();
const tokenTracker = new TokenTracker(db);
const sessionManager = new SessionManager(db);
const workflowOptimizer = new WorkflowOptimizer(db, tokenTracker, sessionManager);
const statsCmd = program.command('stats').description('View usage statistics and analytics');
statsCmd
.command('usage')
.description('Show token usage statistics')
.option('-d, --days <days>', 'Number of days to analyze', '7')
.action(async (options) => {
try {
const usageStats = await tokenTracker.getUsageStats(parseInt(options.days));
if (usageStats.length === 0) {
console.log(chalk.yellow('No usage data found.'));
return;
}
const tableData = [
['Date', 'Sessions', 'Tokens', 'Opus', 'Sonnet', 'Opt Score']
];
let totalTokens = 0;
let totalSessions = 0;
for (const stats of usageStats) {
tableData.push([
moment(stats.date).format('MM/DD'),
stats.sessionsCount.toString(),
stats.totalTokens.toLocaleString(),
stats.opusSessions.toString(),
stats.sonnetSessions.toString(),
`${stats.optimizationScore}/100`
]);
totalTokens += stats.totalTokens;
totalSessions += stats.sessionsCount;
}
console.log(chalk.cyan(`\\nUsage Statistics (${options.days} days):`));
console.log(table(tableData));
console.log(chalk.cyan('\\nSummary:'));
console.log(`Total Sessions: ${totalSessions}`);
console.log(`Total Tokens: ${totalTokens.toLocaleString()}`);
console.log(`Avg Tokens/Session: ${Math.round(totalTokens / totalSessions).toLocaleString()}`);
// Show efficiency trend
const trend = await tokenTracker.getTokenEfficiencyTrend(parseInt(options.days));
if (trend.length > 1) {
const recent = trend.slice(-3);
const avgEfficiency = recent.reduce((sum, day) => sum + day.efficiency, 0) / recent.length;
console.log(`Avg Efficiency: ${Math.round(avgEfficiency)}%`);
}
} catch (error: any) {
console.error(chalk.red('Error getting usage stats:'), error.message);
}
});
statsCmd
.command('today')
.description('Show today\'s usage and recommendations')
.action(async () => {
try {
const todayUsage = await tokenTracker.getTodayUsage();
const recommendations = await tokenTracker.getOptimizationRecommendations();
console.log(chalk.cyan('\\nToday\'s Usage:'));
if (todayUsage) {
console.log(`Sessions: ${todayUsage.sessionsCount}`);
console.log(`Tokens Used: ${todayUsage.totalTokens.toLocaleString()}`);
console.log(`Opus Sessions: ${todayUsage.opusSessions}`);
console.log(`Sonnet Sessions: ${todayUsage.sonnetSessions}`);
console.log(`Optimization Score: ${todayUsage.optimizationScore}/100`);
} else {
console.log(chalk.yellow('No sessions today.'));
}
if (recommendations.length > 0) {
console.log(chalk.cyan('\\nRecommendations:'));
recommendations.forEach((rec, index) => {
const priorityColor = rec.priority === 'high' ? chalk.red :
rec.priority === 'medium' ? chalk.yellow : chalk.green;
console.log(`${index + 1}. ${priorityColor(`[${rec.priority.toUpperCase()}]`)} ${rec.message}`);
if (rec.potentialSavings) {
console.log(` Potential savings: ${rec.potentialSavings.toLocaleString()} tokens`);
}
});
} else {
console.log(chalk.green('\\n✓ Great job! No optimization recommendations at this time.'));
}
} catch (error: any) {
console.error(chalk.red('Error getting today\'s stats:'), error.message);
}
});
statsCmd
.command('optimize')
.description('Analyze workflow efficiency and provide insights')
.option('-p, --project <projectId>', 'Analyze specific project')
.action(async (options) => {
try {
const analysis = await workflowOptimizer.analyzeWorkflowEfficiency(options.project);
console.log(chalk.cyan('\\nWorkflow Efficiency Analysis:'));
console.log(chalk.gray('─'.repeat(40)));
// Score visualization
const score = analysis.score;
const scoreColor = score >= 80 ? chalk.green : score >= 60 ? chalk.yellow : chalk.red;
console.log(`\\nEfficiency Score: ${scoreColor(`${score}/100`)}`);
// Progress bar
const barLength = 20;
const filledLength = Math.round((score / 100) * barLength);
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
console.log(`[${scoreColor(bar)}]`);
if (analysis.recommendations.length > 0) {
console.log(chalk.cyan('\\nTop Recommendations:'));
analysis.recommendations.slice(0, 5).forEach((rec, index) => {
const priorityColor = rec.priority === 'high' ? chalk.red :
rec.priority === 'medium' ? chalk.yellow : chalk.green;
console.log(`${index + 1}. ${priorityColor(`[${rec.priority.toUpperCase()}]`)} ${rec.message}`);
});
}
if (analysis.insights.length > 0) {
console.log(chalk.cyan('\\nKey Insights:'));
analysis.insights.forEach(insight => {
console.log(`${insight}`);
});
}
} catch (error: any) {
console.error(chalk.red('Error analyzing workflow:'), error.message);
}
});
statsCmd
.command('anomalies')
.description('Detect workflow anomalies and trends')
.action(async () => {
try {
const analysis = await workflowOptimizer.detectWorkflowAnomalies();
console.log(chalk.cyan('\\nWorkflow Anomaly Detection:'));
console.log(chalk.gray('─'.repeat(40)));
if (analysis.anomalies.length > 0) {
console.log(chalk.yellow('\\n⚠ Anomalies Detected:'));
analysis.anomalies.forEach((anomaly, index) => {
const severityColor = anomaly.severity === 'high' ? chalk.red :
anomaly.severity === 'medium' ? chalk.yellow : chalk.green;
console.log(`\\n${index + 1}. ${severityColor(`[${anomaly.severity.toUpperCase()}]`)} ${anomaly.type.replace('_', ' ').toUpperCase()}`);
console.log(` ${anomaly.description}`);
console.log(chalk.cyan(` Recommendation: ${anomaly.recommendation}`));
});
} else {
console.log(chalk.green('\\n✓ No anomalies detected. Your workflow looks healthy!'));
}
if (analysis.trends.length > 0) {
console.log(chalk.cyan('\\n📈 Trends:'));
analysis.trends.forEach(trend => {
const trendColor = trend.trend === 'improving' ? chalk.green :
trend.trend === 'declining' ? chalk.red : chalk.gray;
const icon = trend.trend === 'improving' ? '↑' :
trend.trend === 'declining' ? '↓' : '→';
console.log(` ${trendColor(`${icon} ${trend.metric.replace('_', ' ')}: ${trend.trend} (${trend.change > 0 ? '+' : ''}${trend.change})`)}`);
});
}
} catch (error: any) {
console.error(chalk.red('Error detecting anomalies:'), error.message);
}
});
statsCmd
.command('tokens')
.description('Detailed token usage breakdown')
.option('-p, --project <projectId>', 'Breakdown by project')
.action(async (options) => {
try {
if (options.project) {
// Project-specific breakdown
const breakdown = await tokenTracker.projectTokenBreakdown(options.project);
console.log(chalk.cyan('\\nToken Breakdown by Project:'));
console.log(`Total Tokens: ${breakdown.total.toLocaleString()}`);
console.log(`Opus: ${breakdown.byModel.opus.toLocaleString()} (${Math.round(breakdown.byModel.opus / breakdown.total * 100)}%)`);
console.log(`Sonnet: ${breakdown.byModel.sonnet.toLocaleString()} (${Math.round(breakdown.byModel.sonnet / breakdown.total * 100)}%)`);
} else {
// Overall breakdown with model efficiency
const usageStats = await tokenTracker.getUsageStats(30);
let totalOpus = 0;
let totalSonnet = 0;
let opusSessions = 0;
let sonnetSessions = 0;
usageStats.forEach(stats => {
totalOpus += stats.totalTokens * (stats.opusSessions / Math.max(1, stats.sessionsCount));
totalSonnet += stats.totalTokens * (stats.sonnetSessions / Math.max(1, stats.sessionsCount));
opusSessions += stats.opusSessions;
sonnetSessions += stats.sonnetSessions;
});
const total = totalOpus + totalSonnet;
console.log(chalk.cyan('\\n30-Day Token Breakdown:'));
console.log(`Total Tokens: ${total.toLocaleString()}`);
console.log(`Opus: ${totalOpus.toLocaleString()} (${Math.round(totalOpus / total * 100)}%) - ${opusSessions} sessions`);
console.log(`Sonnet: ${totalSonnet.toLocaleString()} (${Math.round(totalSonnet / total * 100)}%) - ${sonnetSessions} sessions`);
// Efficiency comparison
const opusAvg = opusSessions > 0 ? totalOpus / opusSessions : 0;
const sonnetAvg = sonnetSessions > 0 ? totalSonnet / sonnetSessions : 0;
console.log(chalk.cyan('\\nAverage Tokens per Session:'));
console.log(`Opus: ${Math.round(opusAvg).toLocaleString()}`);
console.log(`Sonnet: ${Math.round(sonnetAvg).toLocaleString()}`);
}
} catch (error: any) {
console.error(chalk.red('Error getting token breakdown:'), error.message);
}
});
}
+57
View File
@@ -0,0 +1,57 @@
import chalk from 'chalk';
export function log(message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info'): void {
const colors = {
info: chalk.blue,
success: chalk.green,
warning: chalk.yellow,
error: chalk.red
};
console.log(colors[type](`[${type.toUpperCase()}] ${message}`));
}
export function success(message: string): void {
log(message, 'success');
}
export function error(message: string | any): void {
const msg = typeof message === 'string' ? message : message?.message || String(message);
log(msg, 'error');
}
export function warning(message: string): void {
log(message, 'warning');
}
export function info(message: string): void {
log(message, 'info');
}
export function formatDuration(ms: number): string {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}
export function formatTokens(tokens: number): string {
if (tokens >= 1000000) {
return `${(tokens / 1000000).toFixed(1)}M`;
} else if (tokens >= 1000) {
return `${(tokens / 1000).toFixed(1)}K`;
}
return tokens.toString();
}
export function truncateString(str: string, maxLength: number): string {
if (str.length <= maxLength) return str;
return str.substring(0, maxLength - 3) + '...';
}
+93
View File
@@ -0,0 +1,93 @@
import { Database as SQLiteDatabase } from 'bun:sqlite';
export class DatabaseManager {
private db: SQLiteDatabase;
constructor(dbPath: string = './data/claude-sessions.db') {
// Create database (bun:sqlite automatically creates directories if needed)
this.db = new SQLiteDatabase(dbPath);
this.initialize();
}
private initialize() {
this.db.exec(`
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
path TEXT NOT NULL UNIQUE,
techStack TEXT,
codingStyle TEXT,
claudeMdContent TEXT,
commonCommands TEXT,
architecture TEXT,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
lastActive DATETIME DEFAULT CURRENT_TIMESTAMP,
totalSessions INTEGER DEFAULT 0,
totalTokensUsed INTEGER DEFAULT 0
)
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
projectId TEXT NOT NULL,
startTime DATETIME NOT NULL,
endTime DATETIME,
modelType TEXT NOT NULL,
tokensUsed INTEGER DEFAULT 0,
messages INTEGER DEFAULT 0,
notes TEXT,
summary TEXT,
status TEXT NOT NULL,
optimizationScore INTEGER DEFAULT 0,
FOREIGN KEY (projectId) REFERENCES projects (id)
)
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS usage_stats (
id TEXT PRIMARY KEY,
date DATE NOT NULL,
sessionsCount INTEGER DEFAULT 0,
totalTokens INTEGER DEFAULT 0,
opusSessions INTEGER DEFAULT 0,
sonnetSessions INTEGER DEFAULT 0,
averageSessionLength REAL DEFAULT 0,
optimizationScore INTEGER DEFAULT 0,
recommendations TEXT
)
`);
this.db.exec(`
CREATE TABLE IF NOT EXISTS window_schedules (
id TEXT PRIMARY KEY,
startTime DATETIME NOT NULL,
endTime DATETIME NOT NULL,
isActive BOOLEAN DEFAULT 0,
overlapSession TEXT
)
`);
}
run(sql: string, params: unknown[] = []): unknown {
const stmt = this.db.prepare(sql);
return stmt.run(...params as []);
}
get(sql: string, params: unknown[] = []): unknown {
const stmt = this.db.prepare(sql);
return stmt.get(...params as []);
}
all(sql: string, params: unknown[] = []): unknown[] {
const stmt = this.db.prepare(sql);
return stmt.all(...params as []);
}
close(): void {
this.db.close();
}
}
// Export as Database for backward compatibility
export { DatabaseManager as Database };
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bun
import { Command } from "commander";
import chalk from "chalk";
import { Database } from "./database/Database";
import { sessionCommands } from "./cli/commands/session";
import { projectCommands } from "./cli/commands/project";
import { statsCommands } from "./cli/commands/stats";
import { optimizeCommands } from "./cli/commands/optimize";
import { info, success, error } from "./cli/utils/logger";
const program = new Command();
program
.name("csm")
.description("Claude Session Manager - Optimize your Claude Code sessions")
.version("1.0.0");
// Initialize database
let db: Database;
try {
db = new Database();
success("Claude Session Manager initialized successfully!");
} catch (err) {
error(`Failed to initialize database: ${err}`);
process.exit(1);
}
// Register command modules
sessionCommands(program);
projectCommands(program);
statsCommands(program);
optimizeCommands(program);
// Global options
program
.option("-v, --verbose", "Enable verbose logging")
.hook("preAction", (thisCommand) => {
if (thisCommand.opts().verbose) {
info("Verbose mode enabled");
}
});
// Default command - show status
program.action(() => {
console.log(chalk.cyan("🤖 Claude Session Manager"));
console.log(
chalk.gray(
"Optimize your Claude Code sessions based on PLAN.md guidelines",
),
);
console.log(chalk.yellow("Quick Start:"));
console.log(" csm project create Create a new project");
console.log(" csm session start Start a new session");
console.log(" csm stats today Show today's usage");
console.log(" csm optimize tips Show optimization tips");
console.log(chalk.yellow("Commands:"));
console.log(" session Manage Claude sessions");
console.log(" project Manage projects and CLAUDE.md files");
console.log(" stats View usage statistics and analytics");
console.log(" optimize Get optimization suggestions");
console.log(chalk.yellow("Examples:"));
console.log(" csm session start -p my-project -m sonnet");
console.log(' csm optimize suggest "implement user authentication"');
console.log(" csm project claude-md my-project");
console.log(" csm stats usage --days 30");
console.log(
chalk.green(
"💡 Remember: Use /clear frequently and batch similar requests!",
),
);
});
// Error handling
program.on("command:*", () => {
error(`Invalid command: ${program.args.join(" ")}`);
console.log("See --help for a list of available commands.");
process.exit(1);
});
// Parse command line arguments
program.parse();
// Handle graceful shutdown
process.on("SIGINT", () => {
info("\\nShutting down Claude Session Manager...");
if (db) {
db.close();
}
process.exit(0);
});
process.on("SIGTERM", () => {
info("\\nShutting down Claude Session Manager...");
if (db) {
db.close();
}
process.exit(0);
});
+20
View File
@@ -0,0 +1,20 @@
export interface CodingStyle {
namingConvention: 'snake_case' | 'camelCase' | 'PascalCase';
fileStructure: string;
conventions: string[];
}
export interface Project {
id: string;
name: string;
path: string;
techStack: string[];
codingStyle: CodingStyle;
claudeMdContent?: string;
commonCommands: string[];
architecture: string;
createdAt: Date;
lastActive: Date;
totalSessions: number;
totalTokensUsed: number;
}
+21
View File
@@ -0,0 +1,21 @@
export interface Session {
id: string;
projectId: string;
startTime: Date;
endTime?: Date;
modelType: 'opus' | 'sonnet';
tokensUsed: number;
messages: number;
notes: string;
summary: string;
status: 'active' | 'completed' | 'interrupted';
optimizationScore: number;
}
export interface WindowSchedule {
id: string;
startTime: Date;
endTime: Date;
isActive: boolean;
overlapSession?: string;
}
+18
View File
@@ -0,0 +1,18 @@
export interface UsageStats {
id: string;
date: Date;
sessionsCount: number;
totalTokens: number;
opusSessions: number;
sonnetSessions: number;
averageSessionLength: number;
optimizationScore: number;
recommendations: string[];
}
export interface OptimizationRecommendation {
type: 'token' | 'scheduling' | 'workflow' | 'model';
priority: 'high' | 'medium' | 'low';
message: string;
potentialSavings?: number;
}
+3
View File
@@ -0,0 +1,3 @@
export * from './Session';
export * from './Project';
export * from './UsageStats';
+299
View File
@@ -0,0 +1,299 @@
import { Database } from '../database/Database';
import { Session, Project } from '../models';
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import moment from 'moment';
export class NoteTaker {
private db: Database;
constructor(db: Database) {
this.db = db;
}
async takeSessionNote(sessionId: string, note: string): Promise<void> {
await this.db.run(
'UPDATE sessions SET notes = notes || ? || "\\n\\n" WHERE id = ?',
[`[${new Date().toISOString()}] ${note}`, sessionId]
);
}
async generateSessionSummary(sessionId: string): Promise<string> {
const session = await this.getSession(sessionId);
if (!session) {
throw new Error('Session not found');
}
const project = await this.getProject(session.projectId);
const duration = session.endTime
? moment.duration(session.endTime.getTime() - session.startTime.getTime()).humanize()
: 'Still active';
const summary = `# Session Summary - ${moment(session.startTime).format('YYYY-MM-DD HH:mm')}
## Project: ${project?.name || 'Unknown'}
**Duration**: ${duration}
**Model**: ${session.modelType}
**Tokens Used**: ${session.tokensUsed.toLocaleString()}
**Messages**: ${session.messages}
## Key Activities
${this.extractKeyActivities(session.notes)}
## Files Modified
${this.extractFilesModified(session.notes)}
## Outcomes
${this.extractOutcomes(session.notes)}
## Next Steps
${this.extractNextSteps(session.notes)}
## Optimization Notes
${this.generateOptimizationNotes(session)}
---
*Generated by Claude Session Manager*
`;
// Save summary to database
await this.db.run(
'UPDATE sessions SET summary = ? WHERE id = ?',
[summary, sessionId]
);
// Save to file if project exists
if (project) {
await this.saveSessionNotesToFile(project, session, summary);
}
return summary;
}
async getSessionNotes(sessionId: string): Promise<string> {
const session = await this.getSession(sessionId);
return session?.notes || '';
}
async getProjectSessionSummaries(projectId: string, limit: number = 10): Promise<string[]> {
const sessions = await this.db.all(
`SELECT summary FROM sessions
WHERE projectId = ? AND summary IS NOT NULL AND summary != ''
ORDER BY startTime DESC
LIMIT ?`,
[projectId, limit]
);
return (sessions as Array<{ summary: string }>).map(session => session.summary);
}
async addQuickNote(sessionId: string, type: 'bug' | 'feature' | 'question' | 'idea', content: string): Promise<void> {
const timestamp = moment().format('HH:mm');
const formattedNote = `[${timestamp}] [${type.toUpperCase()}] ${content}`;
await this.takeSessionNote(sessionId, formattedNote);
}
async searchSessionNotes(projectId: string, query: string): Promise<{sessionId: string, note: string, timestamp: Date}[]> {
const sessions = await this.db.all(
`SELECT id, notes, startTime FROM sessions
WHERE projectId = ? AND notes LIKE ?
ORDER BY startTime DESC`,
[projectId, `%${query}%`]
);
return (sessions as Array<{ id: string; notes: string; startTime: string }>).map(session => ({
sessionId: session.id,
note: session.notes,
timestamp: new Date(session.startTime)
}));
}
async exportSessionHistory(projectId: string, format: 'markdown' | 'json' = 'markdown'): Promise<string> {
const sessions = await this.db.all(
`SELECT * FROM sessions
WHERE projectId = ?
ORDER BY startTime DESC`,
[projectId]
);
if (format === 'json') {
return JSON.stringify(sessions, null, 2);
}
// Markdown format
let markdown = `# Session History\n\n`;
for (const session of sessions as Array<{
startTime: string;
modelType: string;
tokensUsed: number;
status: string;
notes?: string;
summary?: string;
}>) {
markdown += `## Session - ${moment(session.startTime).format('YYYY-MM-DD HH:mm')}\n\n`;
markdown += `**Model**: ${session.modelType}\n`;
markdown += `**Tokens**: ${session.tokensUsed.toLocaleString()}\n`;
markdown += `**Status**: ${session.status}\n\n`;
if (session.notes) {
markdown += `### Notes\n${session.notes}\n\n`;
}
if (session.summary) {
markdown += `### Summary\n${session.summary}\n\n`;
}
markdown += `---\n\n`;
}
return markdown;
}
private async getSession(sessionId: string): Promise<Session | null> {
const row = await this.db.get('SELECT * FROM sessions WHERE id = ?', [sessionId]);
if (!row) return null;
return {
id: (row as { id: string }).id,
projectId: (row as { projectId: string }).projectId,
startTime: new Date((row as { startTime: string }).startTime),
endTime: (row as { endTime?: string }).endTime ? new Date((row as { endTime: string }).endTime) : undefined,
modelType: (row as { modelType: 'opus' | 'sonnet' }).modelType,
tokensUsed: (row as { tokensUsed: number }).tokensUsed,
messages: (row as { messages: number }).messages,
notes: (row as { notes: string }).notes,
summary: (row as { summary: string }).summary,
status: (row as { status: 'active' | 'completed' | 'interrupted' }).status,
optimizationScore: (row as { optimizationScore: number }).optimizationScore
};
}
private async getProject(projectId: string): Promise<Project | null> {
const row = await this.db.get('SELECT * FROM projects WHERE id = ?', [projectId]);
if (!row) return null;
return {
id: (row as { id: string }).id,
name: (row as { name: string }).name,
path: (row as { path: string }).path,
techStack: JSON.parse((row as { techStack: string }).techStack || '[]'),
codingStyle: JSON.parse((row as { codingStyle: string }).codingStyle || '{}'),
claudeMdContent: (row as { claudeMdContent: string }).claudeMdContent,
commonCommands: JSON.parse((row as { commonCommands: string }).commonCommands || '[]'),
architecture: (row as { architecture: string }).architecture,
createdAt: new Date((row as { createdAt: string }).createdAt),
lastActive: new Date((row as { lastActive: string }).lastActive),
totalSessions: (row as { totalSessions: number }).totalSessions,
totalTokensUsed: (row as { totalTokensUsed: number }).totalTokensUsed
};
}
private extractKeyActivities(notes: string): string {
// Extract activities from notes - this is a simplified implementation
const lines = notes.split('\n').filter(line =>
line.includes('implemented') ||
line.includes('fixed') ||
line.includes('created') ||
line.includes('updated')
);
return lines.length > 0
? lines.map(line => `- ${line.trim()}`).join('\n')
: '- No specific activities recorded';
}
private extractFilesModified(notes: string): string {
// Extract file mentions from notes
const filePattern = /\b[\w\-/]+\.(js|ts|jsx|tsx|py|java|go|rs|cpp|c|html|css|json|md)\b/g;
const matches = notes.match(filePattern) || [];
const uniqueFiles = [...new Set(matches)];
return uniqueFiles.length > 0
? uniqueFiles.map(file => `- ${file}`).join('\n')
: '- No files specifically mentioned';
}
private extractOutcomes(notes: string): string {
// Extract outcomes from notes
const outcomeKeywords = ['completed', 'finished', 'resolved', 'achieved', 'success'];
const lines = notes.split('\n').filter(line =>
outcomeKeywords.some(keyword => line.toLowerCase().includes(keyword))
);
return lines.length > 0
? lines.map(line => `- ${line.trim()}`).join('\n')
: '- Outcomes not explicitly recorded';
}
private extractNextSteps(notes: string): string {
// Extract next steps from notes
const nextStepKeywords = ['next', 'todo', 'follow-up', 'later', 'remaining'];
const lines = notes.split('\n').filter(line =>
nextStepKeywords.some(keyword => line.toLowerCase().includes(keyword))
);
return lines.length > 0
? lines.map(line => `- ${line.trim()}`).join('\n')
: '- No next steps recorded';
}
private generateOptimizationNotes(session: Session): string {
const notes = [];
if (session.tokensUsed > 50000) {
notes.push('- High token usage detected. Consider using /compact more frequently');
}
if (session.messages < 5) {
notes.push('- Low message count may indicate inefficient communication');
}
if (session.modelType === 'opus' && session.tokensUsed < 10000) {
notes.push('- Opus used for light task. Consider using Sonnet for better efficiency');
}
return notes.length > 0 ? notes.join('\n') : '- Good optimization practices observed';
}
private async saveSessionNotesToFile(project: Project, session: Session, summary: string): Promise<void> {
try {
const notesDir = join(project.path, '.claude-sessions');
if (!existsSync(notesDir)) {
mkdirSync(notesDir, { recursive: true });
}
const date = moment(session.startTime).format('YYYY-MM-DD');
const fileName = `session-${date}-${session.id.substring(0, 8)}.md`;
const filePath = join(notesDir, fileName);
writeFileSync(filePath, summary);
// Update CLAUDE.md with reference to latest session
await this.updateClaudeMdWithSessionReference(project, fileName);
} catch (error) {
console.warn('Failed to save session notes to file:', error);
}
}
private async updateClaudeMdWithSessionReference(project: Project, sessionFileName: string): Promise<void> {
try {
const claudeMdPath = join(project.path, 'CLAUDE.md');
if (!existsSync(claudeMdPath)) return;
let content = readFileSync(claudeMdPath, 'utf8');
const sessionReference = `\n\n## Latest Session\n- [${sessionFileName}](.claude-sessions/${sessionFileName})`;
if (content.includes('## Recent Session Notes')) {
content = content.replace('## Recent Session Notes', `## Recent Session Notes${sessionReference}`);
} else {
content += sessionReference;
}
writeFileSync(claudeMdPath, content);
} catch (error) {
console.warn('Failed to update CLAUDE.md:', error);
}
}
}
+317
View File
@@ -0,0 +1,317 @@
import { Database } from '../database/Database';
import { Project, CodingStyle } from '../models';
import { v4 as uuidv4 } from 'uuid';
import { writeFileSync, readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
export class ProjectManager {
private db: Database;
constructor(db: Database) {
this.db = db;
}
async createProject(name: string, projectPath: string): Promise<Project> {
const existingProject = await this.getProjectByPath(projectPath);
if (existingProject) {
throw new Error(`Project already exists at path: ${projectPath}`);
}
const detectedTech = await this.detectTechStack(projectPath);
const codingStyle = await this.analyzeCodingStyle();
const project: Project = {
id: uuidv4(),
name,
path: projectPath,
techStack: detectedTech,
codingStyle,
commonCommands: await this.detectCommonCommands(projectPath),
architecture: await this.analyzeArchitecture(projectPath),
createdAt: new Date(),
lastActive: new Date(),
totalSessions: 0,
totalTokensUsed: 0
};
await this.db.run(
`INSERT INTO projects (id, name, path, techStack, codingStyle, claudeMdContent, commonCommands, architecture, createdAt, lastActive, totalSessions, totalTokensUsed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
project.id,
project.name,
project.path,
JSON.stringify(project.techStack),
JSON.stringify(project.codingStyle),
project.claudeMdContent,
JSON.stringify(project.commonCommands),
project.architecture,
project.createdAt.toISOString(),
project.lastActive.toISOString(),
project.totalSessions,
project.totalTokensUsed
]
);
return project;
}
async getProject(projectId: string): Promise<Project | null> {
const row = await this.db.get('SELECT * FROM projects WHERE id = ?', [projectId]);
return row ? this.mapRowToProject(row as Record<string, unknown>) : null;
}
async getProjectByPath(projectPath: string): Promise<Project | null> {
const row = await this.db.get('SELECT * FROM projects WHERE path = ?', [projectPath]);
return row ? this.mapRowToProject(row as Record<string, unknown>) : null;
}
async getAllProjects(): Promise<Project[]> {
const rows = await this.db.all('SELECT * FROM projects ORDER BY lastActive DESC');
return rows.map(row => this.mapRowToProject(row as Record<string, unknown>));
}
async updateProject(projectId: string, updates: Partial<Project>): Promise<void> {
const setClause = [];
const values = [];
if (updates.name) {
setClause.push('name = ?');
values.push(updates.name);
}
if (updates.techStack) {
setClause.push('techStack = ?');
values.push(JSON.stringify(updates.techStack));
}
if (updates.codingStyle) {
setClause.push('codingStyle = ?');
values.push(JSON.stringify(updates.codingStyle));
}
if (updates.claudeMdContent) {
setClause.push('claudeMdContent = ?');
values.push(updates.claudeMdContent);
}
if (updates.commonCommands) {
setClause.push('commonCommands = ?');
values.push(JSON.stringify(updates.commonCommands));
}
if (updates.architecture) {
setClause.push('architecture = ?');
values.push(updates.architecture);
}
setClause.push('lastActive = ?');
values.push(new Date().toISOString());
values.push(projectId);
if (setClause.length > 0) {
await this.db.run(
`UPDATE projects SET ${setClause.join(', ')} WHERE id = ?`,
values
);
}
}
async generateClaudeMd(projectId: string): Promise<string> {
const project = await this.getProject(projectId);
if (!project) {
throw new Error('Project not found');
}
const template = `# ${project.name}
## Project Overview
${project.architecture}
## Tech Stack
${project.techStack.map(tech => `- **${tech}**`).join('\n')}
## Coding Style
- **Naming Convention**: ${project.codingStyle.namingConvention}
- **File Structure**: ${project.codingStyle.fileStructure}
### Conventions
${project.codingStyle.conventions.map(conv => `- ${conv}`).join('\n')}
## Common Commands
${project.commonCommands.map(cmd => `- \`${cmd}\``).join('\n')}
## Development Guidelines
- Use \`/clear\` frequently to reset conversation history
- Batch similar requests into single prompts
- Use Sonnet for implementation, Opus for complex architecture tasks
- Always run tests and linting before committing changes
## Project Structure
\`\`\`
${await this.generateFileStructure(project.path)}
\`\`\`
## Recent Session Notes
*Session notes will be automatically added here*
---
*This file is automatically generated by Claude Session Manager*
`;
// Update the project with the generated content
await this.updateProject(projectId, { claudeMdContent: template });
// Write to file system
const claudeMdPath = join(project.path, 'CLAUDE.md');
writeFileSync(claudeMdPath, template);
return template;
}
async detectTechStack(projectPath: string): Promise<string[]> {
const techStack: string[] = [];
try {
// Check package.json
const packageJsonPath = join(projectPath, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
techStack.push('Node.js');
if (packageJson.dependencies?.react) techStack.push('React');
if (packageJson.dependencies?.vue) techStack.push('Vue');
if (packageJson.dependencies?.angular) techStack.push('Angular');
if (packageJson.dependencies?.express) techStack.push('Express');
if (packageJson.dependencies?.typescript) techStack.push('TypeScript');
}
// Check for Python files
const pythonFiles = this.findFiles(projectPath, '.py');
if (pythonFiles.length > 0) {
techStack.push('Python');
// Check for common Python frameworks
const requirementsPath = join(projectPath, 'requirements.txt');
if (existsSync(requirementsPath)) {
const requirements = readFileSync(requirementsPath, 'utf8');
if (requirements.includes('django')) techStack.push('Django');
if (requirements.includes('flask')) techStack.push('Flask');
if (requirements.includes('fastapi')) techStack.push('FastAPI');
}
}
// Check for other common files
if (this.findFiles(projectPath, '.go').length > 0) techStack.push('Go');
if (this.findFiles(projectPath, '.rs').length > 0) techStack.push('Rust');
if (this.findFiles(projectPath, '.java').length > 0) techStack.push('Java');
if (this.findFiles(projectPath, '.cpp').length > 0 || this.findFiles(projectPath, '.c').length > 0) techStack.push('C/C++');
} catch (error) {
console.warn('Error detecting tech stack:', error);
}
return techStack.length > 0 ? techStack : ['Unknown'];
}
private async analyzeCodingStyle(): Promise<CodingStyle> {
// Default coding style - in a real implementation, this would analyze the codebase
return {
namingConvention: 'camelCase',
fileStructure: 'Organized by feature/module',
conventions: [
'Use meaningful variable names',
'Write self-documenting code',
'Follow DRY principles',
'Include JSDoc comments for complex functions'
]
};
}
private async detectCommonCommands(projectPath: string): Promise<string[]> {
const commands: string[] = [];
try {
const packageJsonPath = join(projectPath, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
if (packageJson.scripts) {
Object.keys(packageJson.scripts).forEach(script => {
commands.push(`npm run ${script}`);
});
}
}
} catch (error) {
console.warn('Error detecting common commands:', error);
}
// Add common commands based on detected tech stack
const techStack = await this.detectTechStack(projectPath);
if (techStack.includes('TypeScript')) {
commands.push('npm run typecheck', 'npm run build');
}
if (techStack.includes('React')) {
commands.push('npm run dev', 'npm run test');
}
return commands;
}
private async analyzeArchitecture(projectPath: string): Promise<string> {
// Basic architecture analysis - in a real implementation, this would be more sophisticated
const techStack = await this.detectTechStack(projectPath);
return `This is a ${techStack.join('/')} project. The architecture follows modern best practices with modular design and clear separation of concerns.`;
}
private async generateFileStructure(projectPath: string): Promise<string> {
try {
const { execSync } = require('child_process');
try {
return execSync(`find ${projectPath} -type f -name ".*" -prune -o -type f -print | head -20`, { encoding: 'utf8' })
.split('\n')
.filter(Boolean)
.map(file => file.replace(projectPath + '/', ''))
.join('\n');
} catch {
return 'File structure not available';
}
} catch {
return 'File structure not available';
}
}
private findFiles(dir: string, extension: string): string[] {
const files: string[] = [];
try {
const items = readdirSync(dir);
for (const item of items) {
const fullPath = join(dir, item);
const stat = statSync(fullPath);
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
files.push(...this.findFiles(fullPath, extension));
} else if (item.endsWith(extension)) {
files.push(fullPath);
}
}
} catch (error) {
// Ignore permission errors
}
return files;
}
private mapRowToProject(row: Record<string, unknown>): Project {
return {
id: row.id as string,
name: row.name as string,
path: row.path as string,
techStack: JSON.parse((row.techStack as string) || '[]'),
codingStyle: JSON.parse((row.codingStyle as string) || '{}'),
claudeMdContent: row.claudeMdContent as string,
commonCommands: JSON.parse((row.commonCommands as string) || '[]'),
architecture: row.architecture as string,
createdAt: new Date(row.createdAt as string),
lastActive: new Date(row.lastActive as string),
totalSessions: row.totalSessions as number,
totalTokensUsed: row.totalTokensUsed as number
};
}
}
+215
View File
@@ -0,0 +1,215 @@
import { Database } from '../database/Database';
import { Session, WindowSchedule } from '../models';
import { v4 as uuidv4 } from 'uuid';
import moment from 'moment';
export class SessionManager {
private db: Database;
private readonly WINDOW_DURATION = 5 * 60 * 60 * 1000; // 5 hours in milliseconds
constructor(db: Database) {
this.db = db;
}
async createSession(projectId: string, modelType: 'opus' | 'sonnet' = 'sonnet'): Promise<Session> {
const session: Session = {
id: uuidv4(),
projectId,
startTime: new Date(),
modelType,
tokensUsed: 0,
messages: 0,
notes: '',
summary: '',
status: 'active',
optimizationScore: 0
};
await this.db.run(
`INSERT INTO sessions (id, projectId, startTime, modelType, tokensUsed, messages, notes, summary, status, optimizationScore)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[session.id, session.projectId, session.startTime.toISOString(), session.modelType,
session.tokensUsed, session.messages, session.notes, session.summary, session.status, session.optimizationScore]
);
// Create or update window schedule
await this.updateWindowSchedule();
return session;
}
async endSession(sessionId: string, summary: string = ''): Promise<Session> {
const endTime = new Date();
await this.db.run(
`UPDATE sessions SET endTime = ?, status = 'completed', summary = ? WHERE id = ?`,
[endTime.toISOString(), summary, sessionId]
);
const session = await this.getSession(sessionId);
if (session) {
await this.updateProjectStats(session.projectId);
await this.updateUsageStats(session);
}
return session!;
}
async getSession(sessionId: string): Promise<Session | null> {
const row = await this.db.get('SELECT * FROM sessions WHERE id = ?', [sessionId]);
return row ? this.mapRowToSession(row as Record<string, unknown>) : null;
}
async getActiveSession(): Promise<Session | null> {
const row = await this.db.get('SELECT * FROM sessions WHERE status = "active" ORDER BY startTime DESC LIMIT 1');
return row ? this.mapRowToSession(row as Record<string, unknown>) : null;
}
async updateSessionTokens(sessionId: string, tokensUsed: number, messages: number): Promise<void> {
await this.db.run(
'UPDATE sessions SET tokensUsed = ?, messages = ? WHERE id = ?',
[tokensUsed, messages, sessionId]
);
}
async scheduleOverlapHack(sessionId: string, delayHours: number = 2): Promise<WindowSchedule> {
const startTime = new Date(Date.now() + delayHours * 60 * 60 * 1000);
const endTime = new Date(startTime.getTime() + this.WINDOW_DURATION);
const schedule: WindowSchedule = {
id: uuidv4(),
startTime,
endTime,
isActive: false,
overlapSession: sessionId
};
await this.db.run(
`INSERT INTO window_schedules (id, startTime, endTime, isActive, overlapSession)
VALUES (?, ?, ?, ?, ?)`,
[schedule.id, schedule.startTime.toISOString(), schedule.endTime.toISOString(),
schedule.isActive, schedule.overlapSession]
);
return schedule;
}
async getCurrentWindow(): Promise<WindowSchedule | null> {
const now = new Date();
const row = await this.db.get(
`SELECT * FROM window_schedules
WHERE startTime <= ? AND endTime >= ? AND isActive = 1
ORDER BY startTime DESC LIMIT 1`,
[now.toISOString(), now.toISOString()]
);
return row ? this.mapRowToWindowSchedule(row as Record<string, unknown>) : null;
}
async getNextAvailableWindow(): Promise<Date | null> {
const now = new Date();
const row = await this.db.get(
`SELECT startTime FROM window_schedules
WHERE startTime > ? ORDER BY startTime ASC LIMIT 1`,
[now.toISOString()]
);
return row ? new Date((row as { startTime: string }).startTime) : null;
}
async getSessionHistory(limit: number = 10): Promise<Session[]> {
const rows = await this.db.all(
'SELECT * FROM sessions ORDER BY startTime DESC LIMIT ?',
[limit]
);
return rows.map(row => this.mapRowToSession(row as Record<string, unknown>));
}
private async updateWindowSchedule(): Promise<void> {
const now = new Date();
const currentWindow = await this.getCurrentWindow();
if (!currentWindow) {
// Create new window
const endTime = new Date(now.getTime() + this.WINDOW_DURATION);
const schedule: WindowSchedule = {
id: uuidv4(),
startTime: now,
endTime,
isActive: true
};
await this.db.run(
`INSERT INTO window_schedules (id, startTime, endTime, isActive)
VALUES (?, ?, ?, ?)`,
[schedule.id, schedule.startTime.toISOString(), schedule.endTime.toISOString(), schedule.isActive]
);
}
}
private async updateProjectStats(projectId: string): Promise<void> {
const stats = await this.db.get(
`SELECT COUNT(*) as sessionCount, SUM(tokensUsed) as totalTokens
FROM sessions WHERE projectId = ?`,
[projectId]
) as { sessionCount: number; totalTokens: number };
await this.db.run(
`UPDATE projects SET totalSessions = ?, totalTokensUsed = ?, lastActive = ? WHERE id = ?`,
[stats.sessionCount, stats.totalTokens || 0, new Date().toISOString(), projectId]
);
}
private async updateUsageStats(session: Session): Promise<void> {
const today = moment().format('YYYY-MM-DD');
const existingStats = await this.db.get(
'SELECT * FROM usage_stats WHERE date = ?',
[today]
);
if (existingStats) {
await this.db.run(
`UPDATE usage_stats SET
sessionsCount = sessionsCount + 1,
totalTokens = totalTokens + ?,
opusSessions = opusSessions + ?,
sonnetSessions = sonnetSessions + ?
WHERE date = ?`,
[session.tokensUsed, session.modelType === 'opus' ? 1 : 0, session.modelType === 'sonnet' ? 1 : 0, today]
);
} else {
const statsId = uuidv4();
await this.db.run(
`INSERT INTO usage_stats (id, date, sessionsCount, totalTokens, opusSessions, sonnetSessions)
VALUES (?, ?, 1, ?, ?, ?)`,
[statsId, today, session.tokensUsed, session.modelType === 'opus' ? 1 : 0, session.modelType === 'sonnet' ? 1 : 0]
);
}
}
private mapRowToSession(row: Record<string, unknown>): Session {
return {
id: row.id as string,
projectId: row.projectId as string,
startTime: new Date(row.startTime as string),
endTime: row.endTime ? new Date(row.endTime as string) : undefined,
modelType: row.modelType as 'opus' | 'sonnet',
tokensUsed: row.tokensUsed as number,
messages: row.messages as number,
notes: row.notes as string,
summary: row.summary as string,
status: row.status as 'active' | 'completed' | 'interrupted',
optimizationScore: row.optimizationScore as number
};
}
private mapRowToWindowSchedule(row: Record<string, unknown>): WindowSchedule {
return {
id: row.id as string,
startTime: new Date(row.startTime as string),
endTime: new Date(row.endTime as string),
isActive: Boolean(row.isActive),
overlapSession: row.overlapSession as string
};
}
}
+202
View File
@@ -0,0 +1,202 @@
import { Database } from '../database/Database';
import { UsageStats, OptimizationRecommendation, Session } from '../models';
import moment from 'moment';
export class TokenTracker {
private db: Database;
private readonly DAILY_TOKEN_LIMIT = {
opus: 100000, // Estimated daily limit for Opus
sonnet: 500000 // Estimated daily limit for Sonnet
};
constructor(db: Database) {
this.db = db;
}
async trackTokenUsage(sessionId: string, tokensUsed: number): Promise<void> {
await this.db.run(
'UPDATE sessions SET tokensUsed = ? WHERE id = ?',
[tokensUsed, sessionId]
);
// Update daily usage stats
await this.updateDailyUsage(tokensUsed);
}
async getUsageStats(days: number = 7): Promise<UsageStats[]> {
const startDate = moment().subtract(days, 'days').format('YYYY-MM-DD');
const rows = await this.db.all(
`SELECT * FROM usage_stats
WHERE date >= ?
ORDER BY date DESC`,
[startDate]
);
return rows.map(row => this.mapRowToUsageStats(row as Record<string, unknown>));
}
async getTodayUsage(): Promise<UsageStats | null> {
const today = moment().format('YYYY-MM-DD');
const row = await this.db.get(
'SELECT * FROM usage_stats WHERE date = ?',
[today]
);
return row ? this.mapRowToUsageStats(row as Record<string, unknown>) : null;
}
async getOptimizationRecommendations(): Promise<OptimizationRecommendation[]> {
const recommendations: OptimizationRecommendation[] = [];
const todayUsage = await this.getTodayUsage();
const weeklyUsage = await this.getUsageStats(7);
if (!todayUsage) {
return [{
type: 'token',
priority: 'medium',
message: 'Start tracking your token usage to get optimization recommendations'
}];
}
// Check for high Opus usage
if (todayUsage.opusSessions > 3) {
recommendations.push({
type: 'model',
priority: 'high',
message: `You've used Opus ${todayUsage.opusSessions} times today. Consider using Sonnet for routine tasks to save tokens.`,
potentialSavings: todayUsage.opusSessions * 50000 // Estimated savings per session
});
}
// Check for low optimization score
if (todayUsage.optimizationScore < 70) {
recommendations.push({
type: 'workflow',
priority: 'medium',
message: 'Your optimization score is low. Try batching requests and using /compact more frequently.'
});
}
// Check daily token limits
const totalWeeklyTokens = weeklyUsage.reduce((sum, day) => sum + day.totalTokens, 0);
const dailyAverage = totalWeeklyTokens / 7;
if (dailyAverage > this.DAILY_TOKEN_LIMIT.sonnet * 0.8) {
recommendations.push({
type: 'token',
priority: 'high',
message: 'You\'re approaching your daily token limits. Consider using the overlap hack to schedule sessions.',
potentialSavings: this.DAILY_TOKEN_LIMIT.sonnet - dailyAverage
});
}
// Check session patterns
const shortSessions = weeklyUsage.filter(day => day.averageSessionLength < 10).length;
if (shortSessions > 3) {
recommendations.push({
type: 'scheduling',
priority: 'medium',
message: 'You have many short sessions. Try batching similar tasks into longer, more focused sessions.'
});
}
return recommendations;
}
async calculateOptimizationScore(session: Session): Promise<number> {
let score = 100;
// Deduct points for high token usage
if (session.tokensUsed > 50000) {
score -= 20;
} else if (session.tokensUsed > 25000) {
score -= 10;
}
// Deduct points for short sessions (less efficient)
const sessionLength = session.endTime
? (session.endTime.getTime() - session.startTime.getTime()) / (1000 * 60) // minutes
: 0;
if (sessionLength < 10) {
score -= 15;
} else if (sessionLength < 20) {
score -= 5;
}
// Bonus points for using Sonnet (more efficient)
if (session.modelType === 'sonnet') {
score += 10;
}
// Deduct points for low message count (might indicate inefficient communication)
if (session.messages < 5) {
score -= 10;
}
return Math.max(0, Math.min(100, score));
}
async getTokenEfficiencyTrend(days: number = 30): Promise<{date: string, efficiency: number}[]> {
const startDate = moment().subtract(days, 'days').format('YYYY-MM-DD');
const rows = await this.db.all(
`SELECT date,
(totalTokens / NULLIF(sessionsCount, 0)) as avgTokensPerSession,
optimizationScore
FROM usage_stats
WHERE date >= ?
ORDER BY date ASC`,
[startDate]
);
return rows.map(row => ({
date: (row as { date: string }).date,
efficiency: (row as { optimizationScore: number }).optimizationScore || 0
}));
}
async projectTokenBreakdown(projectId: string): Promise<{total: number, byModel: {opus: number, sonnet: number}}> {
const row = await this.db.get(
`SELECT
SUM(tokensUsed) as total,
SUM(CASE WHEN modelType = 'opus' THEN tokensUsed ELSE 0 END) as opus,
SUM(CASE WHEN modelType = 'sonnet' THEN tokensUsed ELSE 0 END) as sonnet
FROM sessions
WHERE projectId = ?`,
[projectId]
);
return {
total: (row as { total: number }).total || 0,
byModel: {
opus: (row as { opus: number }).opus || 0,
sonnet: (row as { sonnet: number }).sonnet || 0
}
};
}
private async updateDailyUsage(tokensUsed: number): Promise<void> {
const today = moment().format('YYYY-MM-DD');
await this.db.run(
`UPDATE usage_stats SET totalTokens = totalTokens + ? WHERE date = ?`,
[tokensUsed, today]
);
}
private mapRowToUsageStats(row: Record<string, unknown>): UsageStats {
return {
id: row.id as string,
date: new Date(row.date as string),
sessionsCount: row.sessionsCount as number,
totalTokens: row.totalTokens as number,
opusSessions: row.opusSessions as number,
sonnetSessions: row.sonnetSessions as number,
averageSessionLength: row.averageSessionLength as number,
optimizationScore: row.optimizationScore as number,
recommendations: row.recommendations ? JSON.parse(row.recommendations as string) : []
};
}
}
+394
View File
@@ -0,0 +1,394 @@
import { Database } from '../database/Database';
import { OptimizationRecommendation } from '../models';
import { TokenTracker } from './TokenTracker';
import { SessionManager } from './SessionManager';
export class WorkflowOptimizer {
private db: Database;
private tokenTracker: TokenTracker;
private sessionManager: SessionManager;
constructor(db: Database, tokenTracker: TokenTracker, sessionManager: SessionManager) {
this.db = db;
this.tokenTracker = tokenTracker;
this.sessionManager = sessionManager;
}
async analyzeWorkflowEfficiency(projectId?: string): Promise<{
score: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
let score = 100;
// Analyze session patterns
const sessionAnalysis = await this.analyzeSessionPatterns(projectId);
score -= sessionAnalysis.penalties;
recommendations.push(...sessionAnalysis.recommendations);
insights.push(...sessionAnalysis.insights);
// Analyze token usage
const tokenAnalysis = await this.analyzeTokenUsage();
score -= tokenAnalysis.penalties;
recommendations.push(...tokenAnalysis.recommendations);
insights.push(...tokenAnalysis.insights);
// Analyze scheduling efficiency
const schedulingAnalysis = await this.analyzeSchedulingEfficiency();
score -= schedulingAnalysis.penalties;
recommendations.push(...schedulingAnalysis.recommendations);
insights.push(...schedulingAnalysis.insights);
return {
score: Math.max(0, Math.min(100, score)),
recommendations: this.prioritizeRecommendations(recommendations),
insights
};
}
async suggestOptimalSession(taskDescription: string, complexity: 'simple' | 'medium' | 'complex'): Promise<{
modelType: 'opus' | 'sonnet';
estimatedTokens: number;
batchingSuggestions: string[];
preparationTips: string[];
}> {
const modelType = this.recommendModel(taskDescription, complexity);
const estimatedTokens = this.estimateTokenUsage(taskDescription, complexity, modelType);
return {
modelType,
estimatedTokens,
batchingSuggestions: this.generateBatchingSuggestions(taskDescription),
preparationTips: this.generatePreparationTips(complexity)
};
}
async generateOptimalSchedule(upcomingTasks: Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number; // minutes
priority: 'high' | 'medium' | 'low';
}>): Promise<{
schedule: Array<{
task: string;
recommendedTime: Date;
modelType: 'opus' | 'sonnet';
grouping: string;
}>;
totalEstimatedTokens: number;
optimizationNotes: string[];
}> {
const schedule = [];
let totalTokens = 0;
const optimizationNotes = [];
// Group tasks by complexity and type
const groupedTasks = this.groupTasksByType(upcomingTasks);
// Get current window status
const currentWindow = await this.sessionManager.getCurrentWindow();
const nextWindow = await this.sessionManager.getNextAvailableWindow();
let currentTime = currentWindow ? new Date() : (nextWindow || new Date());
for (const [groupName, tasks] of Object.entries(groupedTasks)) {
for (const task of tasks) {
const suggestion = await this.suggestOptimalSession(task.description, task.complexity);
schedule.push({
task: task.description,
recommendedTime: new Date(currentTime),
modelType: suggestion.modelType,
grouping: groupName
});
totalTokens += suggestion.estimatedTokens;
currentTime = new Date(currentTime.getTime() + task.estimatedDuration * 60 * 1000);
}
}
// Add optimization notes
if (totalTokens > 400000) {
optimizationNotes.push('High token usage expected. Consider splitting across multiple 5-hour windows');
}
const opusTasks = upcomingTasks.filter(t => t.complexity === 'complex').length;
if (opusTasks > 3) {
optimizationNotes.push('Multiple complex tasks detected. Consider using overlap hack for extended sessions');
}
return {
schedule,
totalEstimatedTokens: totalTokens,
optimizationNotes
};
}
async detectWorkflowAnomalies(): Promise<{
anomalies: Array<{
type: 'token_spike' | 'session_fragmentation' | 'model_misuse' | 'scheduling_conflict';
severity: 'low' | 'medium' | 'high';
description: string;
recommendation: string;
}>;
trends: Array<{
metric: string;
trend: 'improving' | 'declining' | 'stable';
change: number;
}>;
}> {
const anomalies = [];
const trends = [];
// Detect token spikes
const recentUsage = await this.tokenTracker.getUsageStats(7);
const avgDailyTokens = recentUsage.reduce((sum, day) => sum + day.totalTokens, 0) / 7;
if (avgDailyTokens > 100000) {
anomalies.push({
type: 'token_spike',
severity: 'high',
description: `Daily token usage (${avgDailyTokens.toLocaleString()}) is unusually high`,
recommendation: 'Review recent sessions for inefficiencies and consider using /compact more frequently'
});
}
// Detect session fragmentation
const recentSessions = await this.sessionManager.getSessionHistory(20);
const shortSessions = recentSessions.filter(s => {
const duration = s.endTime ? (s.endTime.getTime() - s.startTime.getTime()) / (1000 * 60) : 0;
return duration < 10;
}).length;
if (shortSessions > recentSessions.length * 0.5) {
anomalies.push({
type: 'session_fragmentation',
severity: 'medium',
description: `${shortSessions} out of ${recentSessions.length} recent sessions were shorter than 10 minutes`,
recommendation: 'Consider batching similar tasks into longer, more focused sessions'
});
}
// Detect model misuse
const opusHeavyDays = recentUsage.filter(day => day.opusSessions > day.sonnetSessions).length;
if (opusHeavyDays > recentUsage.length * 0.6) {
anomalies.push({
type: 'model_misuse',
severity: 'medium',
description: 'Opus is being used more frequently than Sonnet',
recommendation: 'Reserve Opus for complex tasks and use Sonnet for routine implementation work'
});
}
// Calculate trends
if (recentUsage.length >= 2) {
const recentAvg = recentUsage.slice(0, 3).reduce((sum, day) => sum + day.optimizationScore, 0) / 3;
const olderAvg = recentUsage.slice(3, 6).reduce((sum, day) => sum + day.optimizationScore, 0) / Math.min(3, recentUsage.length - 3);
if (recentAvg > olderAvg + 5) {
trends.push({ metric: 'optimization_score', trend: 'improving', change: recentAvg - olderAvg });
} else if (recentAvg < olderAvg - 5) {
trends.push({ metric: 'optimization_score', trend: 'declining', change: olderAvg - recentAvg });
} else {
trends.push({ metric: 'optimization_score', trend: 'stable', change: 0 });
}
}
return { anomalies, trends };
}
private async analyzeSessionPatterns(projectId?: string): Promise<{
penalties: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const penalties = 0;
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
const sessions = projectId
? await this.db.all('SELECT * FROM sessions WHERE projectId = ? ORDER BY startTime DESC LIMIT 10', [projectId])
: await this.sessionManager.getSessionHistory(10);
// Analyze session length distribution
const sessionLengths = sessions.map(session => {
const endTime = (session as { endTime?: string | Date }).endTime || new Date();
const endTimeDate = typeof endTime === 'string' ? new Date(endTime) : endTime;
return (endTimeDate.getTime() - new Date((session as { startTime: string }).startTime).getTime()) / (1000 * 60); // minutes
});
const avgLength = sessionLengths.reduce((sum, length) => sum + length, 0) / sessionLengths.length;
if (avgLength < 15) {
recommendations.push({
type: 'workflow',
priority: 'medium',
message: 'Average session length is quite short. Consider batching related tasks.'
});
}
insights.push(`Average session duration: ${avgLength.toFixed(1)} minutes`);
return { penalties, recommendations, insights };
}
private async analyzeTokenUsage(): Promise<{
penalties: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const penalties = 0;
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
const usageStats = await this.tokenTracker.getUsageStats(7);
usageStats.reduce((sum, day) => sum + day.totalTokens, 0);
const avgTokensPerSession = usageStats.reduce((sum, day) => sum + (day.totalTokens / Math.max(1, day.sessionsCount)), 0) / usageStats.length;
if (avgTokensPerSession > 50000) {
recommendations.push({
type: 'token',
priority: 'high',
message: 'High token usage per session detected. Consider using /compact more frequently.',
potentialSavings: avgTokensPerSession * 0.3
});
}
insights.push(`Average tokens per session: ${avgTokensPerSession.toLocaleString()}`);
return { penalties, recommendations, insights };
}
private async analyzeSchedulingEfficiency(): Promise<{
penalties: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const penalties = 0;
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
const currentWindow = await this.sessionManager.getCurrentWindow();
if (!currentWindow) {
recommendations.push({
type: 'scheduling',
priority: 'medium',
message: 'No active 5-hour window. Consider using the overlap hack to prepare for intensive sessions.'
});
}
insights.push(currentWindow ? 'Currently within an active 5-hour window' : 'No active window detected');
return { penalties, recommendations, insights };
}
private recommendModel(taskDescription: string, complexity: 'simple' | 'medium' | 'complex'): 'opus' | 'sonnet' {
if (complexity === 'complex') return 'opus';
if (complexity === 'simple') return 'sonnet';
// For medium complexity, analyze task description
const complexKeywords = ['architecture', 'design', 'refactor', 'optimize', 'debug complex', 'implement system'];
const hasComplexKeywords = complexKeywords.some(keyword =>
taskDescription.toLowerCase().includes(keyword)
);
return hasComplexKeywords ? 'opus' : 'sonnet';
}
private estimateTokenUsage(taskDescription: string, complexity: 'simple' | 'medium' | 'complex', modelType: 'opus' | 'sonnet'): number {
const baseTokens = {
simple: { sonnet: 5000, opus: 8000 },
medium: { sonnet: 15000, opus: 25000 },
complex: { sonnet: 30000, opus: 50000 }
};
return baseTokens[complexity][modelType];
}
private generateBatchingSuggestions(taskDescription: string): string[] {
const suggestions = [
'Prepare all related files and context before starting',
'Group similar implementation tasks together',
'Plan testing and linting as part of the same session'
];
if (taskDescription.includes('test')) {
suggestions.push('Include test writing in the same session as implementation');
}
if (taskDescription.includes('refactor')) {
suggestions.push('Batch related refactoring tasks to maintain context');
}
return suggestions;
}
private generatePreparationTips(complexity: 'simple' | 'medium' | 'complex'): string[] {
const tips = {
simple: [
'Have specific files ready to reference',
'Prepare clear, concise instructions'
],
medium: [
'Review relevant documentation beforehand',
'Prepare test cases or expected outcomes',
'Consider edge cases in advance'
],
complex: [
'Create a detailed step-by-step plan',
'Prepare comprehensive project context',
'Have fallback approaches ready',
'Schedule adequate time for implementation'
]
};
return tips[complexity];
}
private groupTasksByType(tasks: Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}>): Record<string, Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}>> {
return tasks.reduce((groups, task) => {
const category = this.categorizeTask(task);
if (!groups[category]) groups[category] = [];
groups[category].push(task);
return groups;
}, {} as Record<string, Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}>>);
}
private categorizeTask(task: {
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}): string {
if (task.complexity === 'complex') return 'Complex Architecture';
if (task.description.includes('test')) return 'Testing';
if (task.description.includes('fix') || task.description.includes('bug')) return 'Bug Fixes';
if (task.description.includes('feature')) return 'Feature Development';
return 'General Tasks';
}
private prioritizeRecommendations(recommendations: OptimizationRecommendation[]): OptimizationRecommendation[] {
return recommendations.sort((a, b) => {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
}
}
+41
View File
@@ -0,0 +1,41 @@
# CLAUDE.md Template
This file provides Claude with essential context about your project to maximize session efficiency and reduce token usage.
## Project Overview
{{projectOverview}}
## Tech Stack
{{techStack}}
## Architecture
{{architecture}}
## Coding Style
- **Naming Convention**: {{namingConvention}}
- **File Structure**: {{fileStructure}}
### Conventions
{{conventions}}
## Common Commands
{{commonCommands}}
## Development Guidelines
- Use `/clear` frequently to reset conversation history
- Batch similar requests into single detailed prompts
- Use Sonnet for implementation, Opus for complex architecture tasks
- Always run tests and linting before committing changes
- Reference this file instead of repeating project context
## Project Structure
```
{{fileStructure}}
```
## Recent Session Notes
{{sessionNotes}}
---
*This file is automatically generated by Claude Session Manager*
*Last updated: {{lastUpdated}}*
+30
View File
@@ -0,0 +1,30 @@
# Session Notes - {{date}}
## Session Details
- **Session ID**: {{sessionId}}
- **Project**: {{projectName}}
- **Model**: {{modelType}}
- **Duration**: {{duration}}
- **Tokens Used**: {{tokensUsed}}
- **Messages**: {{messages}}
## Key Activities
{{activities}}
## Files Modified
{{filesModified}}
## Outcomes
{{outcomes}}
## Next Steps
{{nextSteps}}
## Optimization Notes
{{optimizationNotes}}
## Raw Notes
{{rawNotes}}
---
*Generated by Claude Session Manager*
+35
View File
@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noImplicitAny": false,
"strictNullChecks": false,
"types": ["bun-types"],
"isolatedModules": true,
"verbatimModuleSyntax": false,
"useDefineForClassFields": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}