claude-code-templates 1.11.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-claude-config.js +33 -26
- package/package.json +1 -2
- package/src/agents.js +262 -0
- package/src/analytics/core/ConversationAnalyzer.js +65 -23
- package/src/analytics-web/components/AgentsPage.js +2365 -156
- package/src/analytics-web/components/App.js +11 -0
- package/src/analytics-web/components/DashboardPage.js +4 -0
- package/src/analytics-web/components/ToolDisplay.js +17 -2
- package/src/analytics-web/index.html +3005 -1059
- package/src/analytics.js +342 -3
- package/src/command-scanner.js +5 -5
- package/src/index.js +13 -6
- package/src/prompts.js +40 -3
- package/src/templates.js +1 -4
- package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +530 -0
- package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +295 -0
- package/templates/python/examples/django-app/agents/django-api-security.md +642 -0
- package/templates/python/examples/django-app/agents/django-database-optimization.md +752 -0
|
@@ -5,32 +5,39 @@ const chalk = require('chalk');
|
|
|
5
5
|
const boxen = require('boxen');
|
|
6
6
|
const { createClaudeConfig } = require('../src/index');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log(
|
|
8
|
+
const pkg = require('../package.json');
|
|
9
|
+
|
|
10
|
+
const title = 'Claude Code Templates';
|
|
11
|
+
const subtitle = 'Your starting point for Claude Code projects';
|
|
12
|
+
|
|
13
|
+
const colorGradient = ['#EA580C', '#F97316', '#FB923C', '#FDBA74', '#FED7AA', '#FFEBD6'];
|
|
14
|
+
|
|
15
|
+
function colorizeTitle(text) {
|
|
16
|
+
const chars = text.split('');
|
|
17
|
+
const steps = colorGradient.length;
|
|
18
|
+
return chars
|
|
19
|
+
.map((char, i) => {
|
|
20
|
+
const color = colorGradient[i % steps];
|
|
21
|
+
return chalk.hex(color)(char);
|
|
22
|
+
})
|
|
23
|
+
.join('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.clear();
|
|
27
|
+
console.log(chalk.hex('#F97316')('════════════════════════════════════════════════════════════════'));
|
|
28
|
+
console.log('\n');
|
|
29
|
+
console.log(' 🔮 ' + colorizeTitle(title));
|
|
30
|
+
console.log('\n');
|
|
31
|
+
console.log(' ' + chalk.hex('#FDBA74')(subtitle));
|
|
32
|
+
console.log('\n');
|
|
33
|
+
console.log(chalk.hex('#F97316')('════════════════════════════════════════════════════════════════\n'));
|
|
34
|
+
|
|
35
|
+
console.log(
|
|
36
|
+
chalk.hex('#D97706')('🚀 Setup Claude Code for any project language 🚀') +
|
|
37
|
+
chalk.gray(`\n v${pkg.version}\n\n`) +
|
|
38
|
+
chalk.blue('🌐 Templates: ') + chalk.underline('https://davila7.github.io/claude-code-templates/') + '\n' +
|
|
39
|
+
chalk.blue('📖 Documentation: ') + chalk.underline('https://davila7.github.io/claude-code-templates/docu/')
|
|
40
|
+
);
|
|
34
41
|
|
|
35
42
|
program
|
|
36
43
|
.name('create-claude-config')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
4
4
|
"description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"dev:link": "npm link",
|
|
32
32
|
"dev:unlink": "npm unlink -g claude-code-templates",
|
|
33
33
|
"pretest:commands": "npm run dev:link",
|
|
34
|
-
"prepublishOnly": "echo 'Skipping tests for emergency publish'",
|
|
35
34
|
"analytics:start": "node src/analytics.js",
|
|
36
35
|
"analytics:test": "npm run test:analytics"
|
|
37
36
|
},
|
package/src/agents.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get all available agents from the templates directory structure
|
|
9
|
+
* @returns {Array} Array of agent objects with name, description, and color
|
|
10
|
+
*/
|
|
11
|
+
function getAvailableAgents() {
|
|
12
|
+
const agents = [];
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Scan all language directories
|
|
16
|
+
const languageDirs = fs.readdirSync(TEMPLATES_DIR)
|
|
17
|
+
.filter(dir => {
|
|
18
|
+
const dirPath = path.join(TEMPLATES_DIR, dir);
|
|
19
|
+
return fs.statSync(dirPath).isDirectory() && dir !== 'common';
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
for (const langDir of languageDirs) {
|
|
23
|
+
const frameworksPath = path.join(TEMPLATES_DIR, langDir, 'examples');
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(frameworksPath)) {
|
|
26
|
+
const frameworks = fs.readdirSync(frameworksPath)
|
|
27
|
+
.filter(dir => {
|
|
28
|
+
const dirPath = path.join(frameworksPath, dir);
|
|
29
|
+
return fs.statSync(dirPath).isDirectory();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
for (const framework of frameworks) {
|
|
33
|
+
const agentsPath = path.join(frameworksPath, framework, 'agents');
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(agentsPath)) {
|
|
36
|
+
const agentFiles = fs.readdirSync(agentsPath)
|
|
37
|
+
.filter(file => file.endsWith('.md'));
|
|
38
|
+
|
|
39
|
+
for (const file of agentFiles) {
|
|
40
|
+
const filePath = path.join(agentsPath, file);
|
|
41
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
const agent = parseAgentFile(content, file);
|
|
43
|
+
|
|
44
|
+
if (agent) {
|
|
45
|
+
agent.language = langDir;
|
|
46
|
+
agent.framework = framework;
|
|
47
|
+
agent.filePath = filePath;
|
|
48
|
+
agents.push(agent);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return agents;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.log(chalk.yellow('⚠️ No agents templates found'));
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse agent markdown file to extract frontmatter
|
|
65
|
+
* @param {string} content - File content
|
|
66
|
+
* @param {string} filename - File name
|
|
67
|
+
* @returns {Object|null} Agent object or null if parsing fails
|
|
68
|
+
*/
|
|
69
|
+
function parseAgentFile(content, filename) {
|
|
70
|
+
try {
|
|
71
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
72
|
+
if (!frontmatterMatch) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const frontmatter = frontmatterMatch[1];
|
|
77
|
+
const lines = frontmatter.split('\n');
|
|
78
|
+
const agent = { filename };
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const [key, ...valueParts] = line.split(':');
|
|
82
|
+
if (key && valueParts.length > 0) {
|
|
83
|
+
const value = valueParts.join(':').trim();
|
|
84
|
+
agent[key.trim()] = value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Extract description without examples for display
|
|
89
|
+
if (agent.description) {
|
|
90
|
+
const shortDesc = agent.description.split('Examples:')[0].trim();
|
|
91
|
+
agent.shortDescription = shortDesc;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return agent;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.log(chalk.yellow(`⚠️ Failed to parse agent file: ${filename}`));
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get agents that are relevant for a specific language/framework
|
|
103
|
+
* @param {string} language - Programming language
|
|
104
|
+
* @param {string} framework - Framework (optional)
|
|
105
|
+
* @returns {Array} Array of relevant agents
|
|
106
|
+
*/
|
|
107
|
+
function getAgentsForLanguageAndFramework(language, framework) {
|
|
108
|
+
const allAgents = getAvailableAgents();
|
|
109
|
+
|
|
110
|
+
// Filter agents based on language and framework
|
|
111
|
+
return allAgents.filter(agent => {
|
|
112
|
+
// First check exact language match
|
|
113
|
+
if (agent.language !== language) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If framework is specified and not 'none', check framework match
|
|
118
|
+
if (framework && framework !== 'none') {
|
|
119
|
+
// Extract framework name from framework path (e.g., 'react-app' -> 'react')
|
|
120
|
+
const frameworkName = framework.includes('-') ? framework.split('-')[0] : framework;
|
|
121
|
+
const agentFrameworkName = agent.framework.includes('-') ? agent.framework.split('-')[0] : agent.framework;
|
|
122
|
+
|
|
123
|
+
return agentFrameworkName.toLowerCase().includes(frameworkName.toLowerCase());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If no specific framework, return all agents for this language
|
|
127
|
+
return true;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Install selected agents to the project's .claude/agents directory
|
|
133
|
+
* @param {Array} selectedAgents - Array of agent names to install
|
|
134
|
+
* @param {string} projectPath - Project directory path
|
|
135
|
+
* @returns {Promise<boolean>} Success status
|
|
136
|
+
*/
|
|
137
|
+
async function installAgents(selectedAgents, projectPath = process.cwd()) {
|
|
138
|
+
try {
|
|
139
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
140
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
141
|
+
|
|
142
|
+
// Create .claude/agents directory if it doesn't exist
|
|
143
|
+
await fs.ensureDir(agentsDir);
|
|
144
|
+
|
|
145
|
+
let installedCount = 0;
|
|
146
|
+
const allAgents = getAvailableAgents();
|
|
147
|
+
|
|
148
|
+
for (const agentName of selectedAgents) {
|
|
149
|
+
// Find the agent by name in the available agents
|
|
150
|
+
const agent = allAgents.find(a => a.name === agentName);
|
|
151
|
+
|
|
152
|
+
if (agent && agent.filePath) {
|
|
153
|
+
const targetFile = path.join(agentsDir, `${agentName}.md`);
|
|
154
|
+
|
|
155
|
+
if (await fs.pathExists(agent.filePath)) {
|
|
156
|
+
await fs.copy(agent.filePath, targetFile);
|
|
157
|
+
installedCount++;
|
|
158
|
+
console.log(chalk.green(`✓ Installed agent: ${agentName} (${agent.language}/${agent.framework})`));
|
|
159
|
+
} else {
|
|
160
|
+
console.log(chalk.yellow(`⚠️ Agent source file not found: ${agent.filePath}`));
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.yellow(`⚠️ Agent not found: ${agentName}`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (installedCount > 0) {
|
|
168
|
+
console.log(chalk.green(`\n🎉 Successfully installed ${installedCount} agent(s) to .claude/agents/`));
|
|
169
|
+
console.log(chalk.blue(' You can now use these agents in your Claude Code conversations!'));
|
|
170
|
+
return true;
|
|
171
|
+
} else {
|
|
172
|
+
console.log(chalk.yellow('⚠️ No agents were installed'));
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(chalk.red('❌ Failed to install agents:'), error.message);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if project already has agents installed
|
|
184
|
+
* @param {string} projectPath - Project directory path
|
|
185
|
+
* @returns {Promise<Array>} Array of installed agent names
|
|
186
|
+
*/
|
|
187
|
+
async function getInstalledAgents(projectPath = process.cwd()) {
|
|
188
|
+
try {
|
|
189
|
+
const agentsDir = path.join(projectPath, '.claude', 'agents');
|
|
190
|
+
|
|
191
|
+
if (!(await fs.pathExists(agentsDir))) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const agentFiles = await fs.readdir(agentsDir);
|
|
196
|
+
return agentFiles
|
|
197
|
+
.filter(file => file.endsWith('.md'))
|
|
198
|
+
.map(file => path.basename(file, '.md'));
|
|
199
|
+
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Format agent choices for inquirer prompt
|
|
207
|
+
* @param {Array} agents - Array of agent objects
|
|
208
|
+
* @param {Array} installedAgents - Array of already installed agent names
|
|
209
|
+
* @returns {Array} Formatted choices for inquirer
|
|
210
|
+
*/
|
|
211
|
+
function formatAgentChoices(agents, installedAgents = []) {
|
|
212
|
+
return agents.map(agent => {
|
|
213
|
+
const isInstalled = installedAgents.includes(agent.name);
|
|
214
|
+
const colorFn = getColorFunction(agent.color);
|
|
215
|
+
|
|
216
|
+
const name = isInstalled
|
|
217
|
+
? `${colorFn(agent.name)} ${chalk.dim('(already installed)')}`
|
|
218
|
+
: colorFn(agent.name);
|
|
219
|
+
|
|
220
|
+
const description = agent.shortDescription || agent.description || 'No description available';
|
|
221
|
+
// Truncate description if too long
|
|
222
|
+
const truncatedDesc = description.length > 80
|
|
223
|
+
? description.substring(0, 80) + '...'
|
|
224
|
+
: description;
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
name: `${name}\n ${chalk.dim(truncatedDesc)}`,
|
|
228
|
+
value: agent.name,
|
|
229
|
+
short: agent.name,
|
|
230
|
+
disabled: isInstalled ? 'Already installed' : false
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get chalk color function based on color name
|
|
237
|
+
* @param {string} colorName - Color name
|
|
238
|
+
* @returns {Function} Chalk color function
|
|
239
|
+
*/
|
|
240
|
+
function getColorFunction(colorName) {
|
|
241
|
+
const colorMap = {
|
|
242
|
+
red: chalk.red,
|
|
243
|
+
green: chalk.green,
|
|
244
|
+
yellow: chalk.yellow,
|
|
245
|
+
blue: chalk.blue,
|
|
246
|
+
magenta: chalk.magenta,
|
|
247
|
+
cyan: chalk.cyan,
|
|
248
|
+
white: chalk.white,
|
|
249
|
+
gray: chalk.gray,
|
|
250
|
+
grey: chalk.gray
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return colorMap[colorName] || chalk.white;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
getAvailableAgents,
|
|
258
|
+
getAgentsForLanguageAndFramework,
|
|
259
|
+
installAgents,
|
|
260
|
+
getInstalledAgents,
|
|
261
|
+
formatAgentChoices
|
|
262
|
+
};
|
|
@@ -101,7 +101,7 @@ class ConversationAnalyzer {
|
|
|
101
101
|
|
|
102
102
|
try {
|
|
103
103
|
// Extract project name from path
|
|
104
|
-
const projectFromPath = this.extractProjectFromPath(filePath);
|
|
104
|
+
const projectFromPath = await this.extractProjectFromPath(filePath);
|
|
105
105
|
|
|
106
106
|
// Use cached parsed conversation if available
|
|
107
107
|
const parsedMessages = await this.getParsedConversation(filePath);
|
|
@@ -113,6 +113,9 @@ class ConversationAnalyzer {
|
|
|
113
113
|
// Calculate tool usage data with caching
|
|
114
114
|
const toolUsage = await this.getCachedToolUsage(filePath, parsedMessages);
|
|
115
115
|
|
|
116
|
+
const projectFromConversation = await this.extractProjectFromConversation(filePath);
|
|
117
|
+
const finalProject = projectFromConversation || projectFromPath;
|
|
118
|
+
|
|
116
119
|
const conversation = {
|
|
117
120
|
id: filename.replace('.jsonl', ''),
|
|
118
121
|
filename: filename,
|
|
@@ -125,7 +128,7 @@ class ConversationAnalyzer {
|
|
|
125
128
|
tokenUsage: tokenUsage,
|
|
126
129
|
modelInfo: modelInfo,
|
|
127
130
|
toolUsage: toolUsage,
|
|
128
|
-
project:
|
|
131
|
+
project: finalProject,
|
|
129
132
|
status: stateCalculator.determineConversationStatus(parsedMessages, stats.mtime),
|
|
130
133
|
conversationState: stateCalculator.determineConversationState(parsedMessages, stats.mtime),
|
|
131
134
|
statusSquares: await this.getCachedStatusSquares(filePath, parsedMessages),
|
|
@@ -433,11 +436,11 @@ class ConversationAnalyzer {
|
|
|
433
436
|
}
|
|
434
437
|
|
|
435
438
|
/**
|
|
436
|
-
* Extract project name from Claude directory file path
|
|
439
|
+
* Extract project name from Claude directory file path using settings.json
|
|
437
440
|
* @param {string} filePath - Full path to conversation file
|
|
438
|
-
* @returns {string|null} Project name or null
|
|
441
|
+
* @returns {Promise<string|null>} Project name or null
|
|
439
442
|
*/
|
|
440
|
-
extractProjectFromPath(filePath) {
|
|
443
|
+
async extractProjectFromPath(filePath) {
|
|
441
444
|
// Extract project name from file path like:
|
|
442
445
|
// /Users/user/.claude/projects/-Users-user-Projects-MyProject/conversation.jsonl
|
|
443
446
|
const pathParts = filePath.split('/');
|
|
@@ -445,34 +448,73 @@ class ConversationAnalyzer {
|
|
|
445
448
|
|
|
446
449
|
if (projectIndex !== -1 && projectIndex + 1 < pathParts.length) {
|
|
447
450
|
const projectDir = pathParts[projectIndex + 1];
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
.
|
|
452
|
-
.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
451
|
+
|
|
452
|
+
// Try to read the settings.json file for this project
|
|
453
|
+
try {
|
|
454
|
+
const projectPath = path.join(path.dirname(filePath)); // Directory containing the conversation file
|
|
455
|
+
const settingsPath = path.join(projectPath, 'settings.json');
|
|
456
|
+
|
|
457
|
+
if (await fs.pathExists(settingsPath)) {
|
|
458
|
+
const settingsContent = await fs.readFile(settingsPath, 'utf8');
|
|
459
|
+
const settings = JSON.parse(settingsContent);
|
|
460
|
+
|
|
461
|
+
if (settings.projectName) {
|
|
462
|
+
return settings.projectName;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// If no projectName in settings, try to extract from projectPath
|
|
466
|
+
if (settings.projectPath) {
|
|
467
|
+
return path.basename(settings.projectPath);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
// If we can't read settings.json, fall back to parsing the directory name
|
|
472
|
+
console.warn(chalk.yellow(`Warning: Could not read settings.json for project ${projectDir}:`, error.message));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Fallback: we'll extract project name from conversation content instead
|
|
476
|
+
// For now, return null to trigger reading from conversation file
|
|
477
|
+
return null;
|
|
456
478
|
}
|
|
457
479
|
|
|
458
480
|
return null;
|
|
459
481
|
}
|
|
460
482
|
|
|
483
|
+
|
|
461
484
|
/**
|
|
462
485
|
* Attempt to extract project information from conversation content
|
|
463
|
-
* @param {
|
|
464
|
-
* @returns {string} Project name or 'Unknown'
|
|
465
|
-
*/
|
|
466
|
-
extractProjectFromConversation(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
486
|
+
* @param {string} filePath - Path to the conversation file
|
|
487
|
+
* @returns {Promise<string>} Project name or 'Unknown'
|
|
488
|
+
*/
|
|
489
|
+
async extractProjectFromConversation(filePath) {
|
|
490
|
+
try {
|
|
491
|
+
// Read the conversation file and look for cwd field
|
|
492
|
+
const content = await this.getFileContent(filePath);
|
|
493
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
494
|
+
|
|
495
|
+
for (const line of lines.slice(0, 10)) { // Check first 10 lines
|
|
496
|
+
try {
|
|
497
|
+
const item = JSON.parse(line);
|
|
498
|
+
|
|
499
|
+
// Look for cwd field in the message
|
|
500
|
+
if (item.cwd) {
|
|
501
|
+
const projectName = path.basename(item.cwd);
|
|
502
|
+
return projectName;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Also check if it's in nested objects
|
|
506
|
+
if (item.message && item.message.cwd) {
|
|
507
|
+
return path.basename(item.message.cwd);
|
|
508
|
+
}
|
|
509
|
+
} catch (parseError) {
|
|
510
|
+
// Skip invalid JSON lines
|
|
511
|
+
continue;
|
|
473
512
|
}
|
|
474
513
|
}
|
|
514
|
+
} catch (error) {
|
|
515
|
+
console.warn(chalk.yellow(`Warning: Could not extract project from conversation ${filePath}:`, error.message));
|
|
475
516
|
}
|
|
517
|
+
|
|
476
518
|
return 'Unknown';
|
|
477
519
|
}
|
|
478
520
|
|