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/src/analytics.js CHANGED
@@ -238,8 +238,35 @@ class ClaudeAnalytics {
238
238
  };
239
239
  }
240
240
 
241
- extractProjectFromPath(filePath) {
242
- // Extract project name from file path like:
241
+ async extractProjectFromPath(filePath) {
242
+ // First try to read cwd from the conversation file itself
243
+ try {
244
+ const content = await fs.readFile(filePath, 'utf8');
245
+ const lines = content.trim().split('\n').filter(line => line.trim());
246
+
247
+ for (const line of lines.slice(0, 10)) { // Check first 10 lines
248
+ try {
249
+ const item = JSON.parse(line);
250
+
251
+ // Look for cwd field in the message
252
+ if (item.cwd) {
253
+ return path.basename(item.cwd);
254
+ }
255
+
256
+ // Also check if it's in nested objects
257
+ if (item.message && item.message.cwd) {
258
+ return path.basename(item.message.cwd);
259
+ }
260
+ } catch (parseError) {
261
+ // Skip invalid JSON lines
262
+ continue;
263
+ }
264
+ }
265
+ } catch (error) {
266
+ console.warn(chalk.yellow(`Warning: Could not extract project from conversation ${filePath}:`, error.message));
267
+ }
268
+
269
+ // Fallback: Extract project name from file path like:
243
270
  // /Users/user/.claude/projects/-Users-user-Projects-MyProject/conversation.jsonl
244
271
  const pathParts = filePath.split('/');
245
272
  const projectIndex = pathParts.findIndex(part => part === 'projects');
@@ -256,7 +283,7 @@ class ClaudeAnalytics {
256
283
  return cleanName;
257
284
  }
258
285
 
259
- return null;
286
+ return 'Unknown';
260
287
  }
261
288
 
262
289
 
@@ -1097,6 +1124,17 @@ class ClaudeAnalytics {
1097
1124
  }
1098
1125
  });
1099
1126
 
1127
+ // Agents API endpoint
1128
+ this.app.get('/api/agents', async (req, res) => {
1129
+ try {
1130
+ const agents = await this.loadAgents();
1131
+ res.json({ agents });
1132
+ } catch (error) {
1133
+ console.error('Error loading agents:', error);
1134
+ res.status(500).json({ error: 'Failed to load agents data' });
1135
+ }
1136
+ });
1137
+
1100
1138
  // Main dashboard route
1101
1139
  this.app.get('/', (req, res) => {
1102
1140
  res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
@@ -1282,6 +1320,307 @@ class ClaudeAnalytics {
1282
1320
  });
1283
1321
  }
1284
1322
 
1323
+ /**
1324
+ * Load available agents from .claude/agents directories (project and user level)
1325
+ * @returns {Promise<Array>} Array of agent objects
1326
+ */
1327
+ async loadAgents() {
1328
+ const agents = [];
1329
+ const homeDir = os.homedir();
1330
+
1331
+ // Define agent paths (user level and project level)
1332
+ const userAgentsDir = path.join(homeDir, '.claude', 'agents');
1333
+ const projectAgentsDirs = [];
1334
+
1335
+ try {
1336
+ // 1. Check current working directory for .claude/agents
1337
+ const currentProjectAgentsDir = path.join(process.cwd(), '.claude', 'agents');
1338
+ if (await fs.pathExists(currentProjectAgentsDir)) {
1339
+ const currentProjectName = path.basename(process.cwd());
1340
+ projectAgentsDirs.push({
1341
+ path: currentProjectAgentsDir,
1342
+ projectName: currentProjectName
1343
+ });
1344
+ }
1345
+
1346
+ // 2. Check parent directories for .claude/agents (for monorepo/nested projects)
1347
+ let currentDir = process.cwd();
1348
+ let parentDir = path.dirname(currentDir);
1349
+
1350
+ // Search up to 3 levels up for .claude/agents
1351
+ for (let i = 0; i < 3 && parentDir !== currentDir; i++) {
1352
+ const parentProjectAgentsDir = path.join(parentDir, '.claude', 'agents');
1353
+
1354
+ if (await fs.pathExists(parentProjectAgentsDir)) {
1355
+ const parentProjectName = path.basename(parentDir);
1356
+
1357
+ // Avoid duplicates
1358
+ const exists = projectAgentsDirs.some(p => p.path === parentProjectAgentsDir);
1359
+ if (!exists) {
1360
+ projectAgentsDirs.push({
1361
+ path: parentProjectAgentsDir,
1362
+ projectName: parentProjectName
1363
+ });
1364
+ }
1365
+ break; // Found one, no need to go further up
1366
+ }
1367
+ currentDir = parentDir;
1368
+ parentDir = path.dirname(currentDir);
1369
+ }
1370
+
1371
+ // 3. Find all project directories that might have agents (in ~/.claude/projects)
1372
+ const projectsDir = path.join(this.claudeDir, 'projects');
1373
+ if (await fs.pathExists(projectsDir)) {
1374
+ const projectDirs = await fs.readdir(projectsDir);
1375
+ for (const projectDir of projectDirs) {
1376
+ const projectAgentsDir = path.join(projectsDir, projectDir, '.claude', 'agents');
1377
+ if (await fs.pathExists(projectAgentsDir)) {
1378
+ projectAgentsDirs.push({
1379
+ path: projectAgentsDir,
1380
+ projectName: this.cleanProjectName(projectDir)
1381
+ });
1382
+ }
1383
+ }
1384
+ }
1385
+
1386
+ // Load user-level agents
1387
+ if (await fs.pathExists(userAgentsDir)) {
1388
+ const userAgents = await this.loadAgentsFromDirectory(userAgentsDir, 'user');
1389
+ agents.push(...userAgents);
1390
+ }
1391
+
1392
+ // Load project-level agents
1393
+ for (const projectInfo of projectAgentsDirs) {
1394
+ const projectAgents = await this.loadAgentsFromDirectory(
1395
+ projectInfo.path,
1396
+ 'project',
1397
+ projectInfo.projectName
1398
+ );
1399
+ agents.push(...projectAgents);
1400
+ }
1401
+
1402
+ // Log agents summary
1403
+ console.log(chalk.blue('🤖 Agents loaded:'), agents.length);
1404
+ if (agents.length > 0) {
1405
+ const projectAgents = agents.filter(a => a.level === 'project').length;
1406
+ const userAgents = agents.filter(a => a.level === 'user').length;
1407
+ console.log(chalk.gray(` 📦 Project agents: ${projectAgents}, 👤 User agents: ${userAgents}`));
1408
+ }
1409
+
1410
+ // Sort agents by name and prioritize project agents over user agents
1411
+ return agents.sort((a, b) => {
1412
+ if (a.level !== b.level) {
1413
+ return a.level === 'project' ? -1 : 1;
1414
+ }
1415
+ return a.name.localeCompare(b.name);
1416
+ });
1417
+
1418
+ } catch (error) {
1419
+ console.error(chalk.red('Error loading agents:'), error);
1420
+ return [];
1421
+ }
1422
+ }
1423
+
1424
+ /**
1425
+ * Load agents from a specific directory
1426
+ * @param {string} agentsDir - Directory containing agent files
1427
+ * @param {string} level - 'user' or 'project'
1428
+ * @param {string} projectName - Name of project (if project level)
1429
+ * @returns {Promise<Array>} Array of agent objects
1430
+ */
1431
+ async loadAgentsFromDirectory(agentsDir, level, projectName = null) {
1432
+ const agents = [];
1433
+
1434
+ try {
1435
+ const files = await fs.readdir(agentsDir);
1436
+
1437
+ for (const file of files) {
1438
+ if (file.endsWith('.md')) {
1439
+ const filePath = path.join(agentsDir, file);
1440
+ const agentData = await this.parseAgentFile(filePath, level, projectName);
1441
+ if (agentData) {
1442
+ agents.push(agentData);
1443
+ }
1444
+ }
1445
+ }
1446
+ } catch (error) {
1447
+ console.warn(chalk.yellow(`Warning: Could not read agents directory ${agentsDir}:`, error.message));
1448
+ }
1449
+
1450
+ return agents;
1451
+ }
1452
+
1453
+ /**
1454
+ * Parse agent markdown file
1455
+ * @param {string} filePath - Path to agent file
1456
+ * @param {string} level - 'user' or 'project'
1457
+ * @param {string} projectName - Name of project (if project level)
1458
+ * @returns {Promise<Object|null>} Agent object or null if parsing failed
1459
+ */
1460
+ async parseAgentFile(filePath, level, projectName = null) {
1461
+ try {
1462
+ const content = await fs.readFile(filePath, 'utf8');
1463
+ const stats = await fs.stat(filePath);
1464
+
1465
+ // Parse YAML frontmatter
1466
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
1467
+ if (!frontmatterMatch) {
1468
+ console.warn(chalk.yellow(`Agent file ${path.basename(filePath)} missing frontmatter`));
1469
+ return null;
1470
+ }
1471
+
1472
+ const frontmatter = {};
1473
+ const yamlContent = frontmatterMatch[1];
1474
+
1475
+ // Simple YAML parser for the fields we need
1476
+ const yamlLines = yamlContent.split('\n');
1477
+ for (const line of yamlLines) {
1478
+ const match = line.match(/^(\w+):\s*(.*)$/);
1479
+ if (match) {
1480
+ const [, key, value] = match;
1481
+ frontmatter[key] = value.trim();
1482
+ }
1483
+ }
1484
+
1485
+ // Log parsed frontmatter for debugging
1486
+ console.log(chalk.blue(`📋 Parsed agent frontmatter for ${path.basename(filePath)}:`), frontmatter);
1487
+
1488
+ if (!frontmatter.name || !frontmatter.description) {
1489
+ console.warn(chalk.yellow(`Agent file ${path.basename(filePath)} missing required fields`));
1490
+ return null;
1491
+ }
1492
+
1493
+ // Extract system prompt (content after frontmatter)
1494
+ const systemPrompt = content.substring(frontmatterMatch[0].length).trim();
1495
+
1496
+ // Parse tools if specified
1497
+ let tools = [];
1498
+ if (frontmatter.tools) {
1499
+ tools = frontmatter.tools.split(',').map(tool => tool.trim()).filter(Boolean);
1500
+ }
1501
+
1502
+ // Use color from frontmatter if available, otherwise generate one
1503
+ const color = frontmatter.color ? this.convertColorToHex(frontmatter.color) : this.generateAgentColor(frontmatter.name);
1504
+
1505
+ return {
1506
+ name: frontmatter.name,
1507
+ description: frontmatter.description,
1508
+ systemPrompt,
1509
+ tools,
1510
+ level,
1511
+ projectName,
1512
+ filePath,
1513
+ lastModified: stats.mtime,
1514
+ color,
1515
+ isActive: true // All loaded agents are considered active
1516
+ };
1517
+
1518
+ } catch (error) {
1519
+ console.warn(chalk.yellow(`Warning: Could not parse agent file ${filePath}:`, error.message));
1520
+ return null;
1521
+ }
1522
+ }
1523
+
1524
+ /**
1525
+ * Generate consistent color for agent based on name
1526
+ * @param {string} agentName - Name of the agent
1527
+ * @returns {string} Hex color code
1528
+ */
1529
+ generateAgentColor(agentName) {
1530
+ // Simple hash function to generate consistent colors
1531
+ let hash = 0;
1532
+ for (let i = 0; i < agentName.length; i++) {
1533
+ const char = agentName.charCodeAt(i);
1534
+ hash = ((hash << 5) - hash) + char;
1535
+ hash = hash & hash; // Convert to 32-bit integer
1536
+ }
1537
+
1538
+ // Generate RGB values with good contrast and visibility
1539
+ const hue = Math.abs(hash) % 360;
1540
+ const saturation = 70 + (Math.abs(hash) % 30); // 70-100%
1541
+ const lightness = 45 + (Math.abs(hash) % 20); // 45-65%
1542
+
1543
+ // Convert HSL to RGB
1544
+ const hslToRgb = (h, s, l) => {
1545
+ h /= 360;
1546
+ s /= 100;
1547
+ l /= 100;
1548
+
1549
+ const hue2rgb = (p, q, t) => {
1550
+ if (t < 0) t += 1;
1551
+ if (t > 1) t -= 1;
1552
+ if (t < 1/6) return p + (q - p) * 6 * t;
1553
+ if (t < 1/2) return q;
1554
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
1555
+ return p;
1556
+ };
1557
+
1558
+ let r, g, b;
1559
+ if (s === 0) {
1560
+ r = g = b = l;
1561
+ } else {
1562
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1563
+ const p = 2 * l - q;
1564
+ r = hue2rgb(p, q, h + 1/3);
1565
+ g = hue2rgb(p, q, h);
1566
+ b = hue2rgb(p, q, h - 1/3);
1567
+ }
1568
+
1569
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
1570
+ };
1571
+
1572
+ const [r, g, b] = hslToRgb(hue, saturation, lightness);
1573
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
1574
+ }
1575
+
1576
+ /**
1577
+ * Convert color names to hex values
1578
+ * @param {string} color - Color name or hex value
1579
+ * @returns {string} Hex color code
1580
+ */
1581
+ convertColorToHex(color) {
1582
+ if (!color) return '#007acc';
1583
+
1584
+ // If already hex, return as-is
1585
+ if (color.startsWith('#')) return color;
1586
+
1587
+ // Convert common color names to hex
1588
+ const colorMap = {
1589
+ 'red': '#ff4444',
1590
+ 'blue': '#4444ff',
1591
+ 'green': '#44ff44',
1592
+ 'yellow': '#ffff44',
1593
+ 'orange': '#ff8844',
1594
+ 'purple': '#8844ff',
1595
+ 'pink': '#ff44ff',
1596
+ 'cyan': '#44ffff',
1597
+ 'brown': '#8b4513',
1598
+ 'gray': '#888888',
1599
+ 'grey': '#888888',
1600
+ 'black': '#333333',
1601
+ 'white': '#ffffff',
1602
+ 'teal': '#008080',
1603
+ 'navy': '#000080',
1604
+ 'lime': '#00ff00',
1605
+ 'maroon': '#800000',
1606
+ 'olive': '#808000',
1607
+ 'silver': '#c0c0c0'
1608
+ };
1609
+
1610
+ return colorMap[color.toLowerCase()] || '#007acc';
1611
+ }
1612
+
1613
+ /**
1614
+ * Clean project name for display
1615
+ * @param {string} projectDir - Raw project directory name
1616
+ * @returns {string} Cleaned project name
1617
+ */
1618
+ cleanProjectName(projectDir) {
1619
+ // Convert encoded project paths like "-Users-user-Projects-MyProject" to "MyProject"
1620
+ const parts = projectDir.split('-').filter(Boolean);
1621
+ return parts[parts.length - 1] || projectDir;
1622
+ }
1623
+
1285
1624
  /**
1286
1625
  * Get Claude session information from statsig files
1287
1626
  */
@@ -25,14 +25,14 @@ function getAvailableCommands(language) {
25
25
  }
26
26
 
27
27
  // Scan framework-specific commands in examples
28
- const examplesDir = path.join(languageDir, 'examples');
29
- if (fs.existsSync(examplesDir)) {
30
- const frameworkDirs = fs.readdirSync(examplesDir).filter(dir => {
31
- return fs.statSync(path.join(examplesDir, dir)).isDirectory();
28
+ const frameworksDir = path.join(languageDir, 'examples');
29
+ if (fs.existsSync(frameworksDir)) {
30
+ const frameworkDirs = fs.readdirSync(frameworksDir).filter(dir => {
31
+ return fs.statSync(path.join(frameworksDir, dir)).isDirectory();
32
32
  });
33
33
 
34
34
  frameworkDirs.forEach(framework => {
35
- const frameworkCommandsDir = path.join(examplesDir, framework, '.claude', 'commands');
35
+ const frameworkCommandsDir = path.join(frameworksDir, framework, '.claude', 'commands');
36
36
  if (fs.existsSync(frameworkCommandsDir)) {
37
37
  const frameworkCommands = scanCommandsInDirectory(frameworkCommandsDir, framework);
38
38
  commands.push(...frameworkCommands);
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ const { getTemplateConfig, TEMPLATES_CONFIG } = require('./templates');
8
8
  const { createPrompts, interactivePrompts } = require('./prompts');
9
9
  const { copyTemplateFiles, runPostInstallationValidation } = require('./file-operations');
10
10
  const { getHooksForLanguage, getMCPsForLanguage } = require('./hook-scanner');
11
+ const { installAgents } = require('./agents');
11
12
  const { runCommandStats } = require('./command-stats');
12
13
  const { runHookStats } = require('./hook-stats');
13
14
  const { runMCPStats } = require('./mcp-stats');
@@ -15,7 +16,6 @@ const { runAnalytics } = require('./analytics');
15
16
  const { runHealthCheck } = require('./health-check');
16
17
 
17
18
  async function showMainMenu() {
18
- console.log(chalk.blue('🚀 Welcome to Claude Code Templates!'));
19
19
  console.log('');
20
20
 
21
21
  const initialChoice = await inquirer.prompt([{
@@ -33,15 +33,15 @@ async function showMainMenu() {
33
33
  value: 'chats',
34
34
  short: 'Chats Dashboard'
35
35
  },
36
- {
37
- name: '🔍 Health Check - Verify your Claude Code setup and configuration',
38
- value: 'health',
39
- short: 'Health Check'
40
- },
41
36
  {
42
37
  name: '⚙️ Project Setup - Configure Claude Code for your project',
43
38
  value: 'setup',
44
39
  short: 'Project Setup'
40
+ },
41
+ {
42
+ name: '🔍 Health Check - Verify your Claude Code setup and configuration',
43
+ value: 'health',
44
+ short: 'Health Check'
45
45
  }
46
46
  ],
47
47
  default: 'analytics'
@@ -193,6 +193,12 @@ async function createClaudeConfig(options = {}) {
193
193
  templateConfig.language = config.language; // Ensure language is available for MCP filtering
194
194
  }
195
195
 
196
+ // Install selected agents
197
+ if (config.agents && config.agents.length > 0) {
198
+ console.log(chalk.blue('🤖 Installing Claude Code agents...'));
199
+ await installAgents(config.agents, targetDir);
200
+ }
201
+
196
202
  if (options.dryRun) {
197
203
  console.log(chalk.yellow('🔍 Dry run - showing what would be copied:'));
198
204
  templateConfig.files.forEach(file => {
@@ -223,6 +229,7 @@ async function createClaudeConfig(options = {}) {
223
229
  console.log(chalk.white(' 3. Start using Claude Code with: claude'));
224
230
  console.log('');
225
231
  console.log(chalk.blue('🌐 View all available templates at: https://davila7.github.io/claude-code-templates/'));
232
+ console.log(chalk.blue('📖 Read the complete documentation at: https://davila7.github.io/claude-code-templates/docu/'));
226
233
 
227
234
  if (config.language !== 'common') {
228
235
  console.log(chalk.yellow(`💡 Language-specific features for ${config.language} have been configured`));
package/src/prompts.js CHANGED
@@ -6,6 +6,7 @@ const inquirer = require('inquirer');
6
6
  const { getAvailableLanguages, getFrameworksForLanguage } = require('./templates');
7
7
  const { getCommandsForLanguageAndFramework } = require('./command-scanner');
8
8
  const { getHooksForLanguage, getMCPsForLanguage } = require('./hook-scanner');
9
+ const { getAgentsForLanguageAndFramework, getInstalledAgents, formatAgentChoices } = require('./agents');
9
10
 
10
11
  async function interactivePrompts(projectInfo, options = {}) {
11
12
  const state = {
@@ -17,7 +18,7 @@ async function interactivePrompts(projectInfo, options = {}) {
17
18
  // Build steps array based on options
18
19
  if (!options.language) state.steps.push('language');
19
20
  if (!options.framework) state.steps.push('framework');
20
- state.steps.push('commands', 'hooks', 'mcps', 'analytics', 'confirm');
21
+ state.steps.push('commands', 'hooks', 'mcps', 'agents', 'analytics', 'confirm');
21
22
 
22
23
  while (state.currentStep < state.steps.length) {
23
24
  const stepName = state.steps[state.currentStep];
@@ -41,7 +42,13 @@ async function interactivePrompts(projectInfo, options = {}) {
41
42
  }
42
43
 
43
44
  async function showStep(stepName, currentAnswers, projectInfo, options) {
44
- const stepConfig = getStepConfig(stepName, currentAnswers, projectInfo, options);
45
+ // Handle async data fetching for agents step
46
+ let additionalData = {};
47
+ if (stepName === 'agents') {
48
+ additionalData.installedAgents = await getInstalledAgents();
49
+ }
50
+
51
+ const stepConfig = getStepConfig(stepName, currentAnswers, projectInfo, options, additionalData);
45
52
 
46
53
  if (!stepConfig) {
47
54
  return { action: 'next', value: null };
@@ -69,7 +76,7 @@ async function showStep(stepName, currentAnswers, projectInfo, options) {
69
76
  return { action: 'next', value };
70
77
  }
71
78
 
72
- function getStepConfig(stepName, currentAnswers, projectInfo, options) {
79
+ function getStepConfig(stepName, currentAnswers, projectInfo, options, additionalData = {}) {
73
80
  switch (stepName) {
74
81
  case 'language':
75
82
  return {
@@ -188,6 +195,32 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
188
195
  pageSize: 15
189
196
  };
190
197
 
198
+ case 'agents':
199
+ const agentLanguage = currentAnswers.language || options.language;
200
+ const agentFramework = currentAnswers.framework || options.framework;
201
+
202
+ if (!agentLanguage) {
203
+ return null; // Skip if no language selected
204
+ }
205
+
206
+ const availableAgents = getAgentsForLanguageAndFramework(agentLanguage, agentFramework);
207
+
208
+ if (availableAgents.length === 0) {
209
+ return null; // Skip if no agents available
210
+ }
211
+
212
+ const installedAgents = additionalData.installedAgents || [];
213
+ const agentChoices = formatAgentChoices(availableAgents, installedAgents);
214
+
215
+ return {
216
+ type: 'checkbox',
217
+ name: 'agents',
218
+ message: 'Select Claude Code agents to install (use space to select):',
219
+ choices: agentChoices,
220
+ prefix: chalk.magenta('🤖'),
221
+ pageSize: 10
222
+ };
223
+
191
224
  case 'analytics':
192
225
  return {
193
226
  type: 'confirm',
@@ -203,6 +236,7 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
203
236
  const commandCount = currentAnswers.commands ? currentAnswers.commands.length : 0;
204
237
  const hookCount = currentAnswers.hooks ? currentAnswers.hooks.length : 0;
205
238
  const mcpCount = currentAnswers.mcps ? currentAnswers.mcps.length : 0;
239
+ const agentCount = currentAnswers.agents ? currentAnswers.agents.length : 0;
206
240
 
207
241
  let message = `Setup Claude Code for ${chalk.cyan(confirmLanguage)}`;
208
242
  if (confirmFramework !== 'none') {
@@ -217,6 +251,9 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
217
251
  if (mcpCount > 0) {
218
252
  message += ` (${chalk.blue(mcpCount)} MCP)`;
219
253
  }
254
+ if (agentCount > 0) {
255
+ message += ` (${chalk.magenta(agentCount)} agents)`;
256
+ }
220
257
  message += '?';
221
258
 
222
259
  return {
package/src/templates.js CHANGED
@@ -6,10 +6,7 @@ const TEMPLATES_CONFIG = {
6
6
  name: 'Common (Language-agnostic)',
7
7
  description: 'Universal configuration for any project',
8
8
  files: [
9
- { source: 'common/CLAUDE.md', destination: 'CLAUDE.md' },
10
- { source: 'common/.claude', destination: '.claude' },
11
- { source: 'common/.claude/settings.json', destination: '.claude/settings.json' },
12
- { source: 'common/.mcp.json', destination: '.mcp.json' }
9
+ { source: 'common/CLAUDE.md', destination: 'CLAUDE.md' }
13
10
  ]
14
11
  },
15
12
  'javascript-typescript': {