claude-code-templates 1.26.4 → 1.28.0

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/index.js CHANGED
@@ -16,11 +16,48 @@ const { runAnalytics } = require('./analytics');
16
16
  const { startChatsMobile } = require('./chats-mobile');
17
17
  const { runHealthCheck } = require('./health-check');
18
18
  const { runPluginDashboard } = require('./plugin-dashboard');
19
+ const { runSkillDashboard } = require('./skill-dashboard');
19
20
  const { trackingService } = require('./tracking-service');
20
21
  const { createGlobalAgent, listGlobalAgents, removeGlobalAgent, updateGlobalAgent } = require('./sdk/global-agent-manager');
21
22
  const SessionSharing = require('./session-sharing');
22
23
  const ConversationAnalyzer = require('./analytics/core/ConversationAnalyzer');
23
24
 
25
+ /**
26
+ * Get platform-appropriate Python command candidates
27
+ * Returns array of commands to try in order
28
+ * @returns {string[]} Array of Python commands to try
29
+ */
30
+ function getPlatformPythonCandidates() {
31
+ if (process.platform === 'win32') {
32
+ // Windows: Try py launcher (PEP 397) first, then python, then python3
33
+ return ['py', 'python', 'python3'];
34
+ } else {
35
+ // Unix/Linux/Mac: Try python3 first, then python
36
+ return ['python3', 'python'];
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Replace python3 commands with platform-appropriate Python command in configuration
42
+ * Windows typically uses 'python' or 'py', while Unix/Linux uses 'python3'
43
+ * @param {Object} config - Configuration object to process
44
+ * @returns {Object} Processed configuration with platform-appropriate Python commands
45
+ */
46
+ function replacePythonCommands(config) {
47
+ if (!config || typeof config !== 'object') {
48
+ return config;
49
+ }
50
+
51
+ // On Windows, replace python3 with python for better compatibility
52
+ if (process.platform === 'win32') {
53
+ const configString = JSON.stringify(config);
54
+ const replacedString = configString.replace(/python3\s/g, 'python ');
55
+ return JSON.parse(replacedString);
56
+ }
57
+
58
+ return config;
59
+ }
60
+
24
61
  async function showMainMenu() {
25
62
  console.log('');
26
63
 
@@ -213,6 +250,14 @@ async function createClaudeConfig(options = {}) {
213
250
  return;
214
251
  }
215
252
 
253
+ // Handle skills dashboard
254
+ if (options.skills) {
255
+ trackingService.trackCommandExecution('skills');
256
+ trackingService.trackAnalyticsDashboard({ page: 'skills', source: 'command_line' });
257
+ await runSkillDashboard(options);
258
+ return;
259
+ }
260
+
216
261
  // Handle chats dashboard (now points to mobile chats interface)
217
262
  if (options.chats) {
218
263
  trackingService.trackCommandExecution('chats', { tunnel: options.tunnel || false });
@@ -692,7 +737,10 @@ async function installIndividualSetting(settingName, targetDir, options) {
692
737
  }
693
738
 
694
739
  const settingConfigText = await response.text();
695
- const settingConfig = JSON.parse(settingConfigText);
740
+ let settingConfig = JSON.parse(settingConfigText);
741
+
742
+ // Replace python3 with platform-appropriate command for Windows compatibility
743
+ settingConfig = replacePythonCommands(settingConfig);
696
744
 
697
745
  // Check if there are additional files to download (e.g., Python scripts)
698
746
  const additionalFiles = {};
@@ -1021,7 +1069,10 @@ async function installIndividualHook(hookName, targetDir, options) {
1021
1069
  }
1022
1070
 
1023
1071
  const hookConfigText = await response.text();
1024
- const hookConfig = JSON.parse(hookConfigText);
1072
+ let hookConfig = JSON.parse(hookConfigText);
1073
+
1074
+ // Replace python3 with platform-appropriate command for Windows compatibility
1075
+ hookConfig = replacePythonCommands(hookConfig);
1025
1076
 
1026
1077
  // Check if there are additional files to download (e.g., Python scripts for hooks)
1027
1078
  const additionalFiles = {};
@@ -2447,37 +2498,38 @@ async function launchClaudeCodeStudio(options, targetDir) {
2447
2498
  }
2448
2499
 
2449
2500
  async function executeSandbox(options, targetDir) {
2450
- const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey } = options;
2501
+ const { sandbox, command, mcp, setting, hook, e2bApiKey, anthropicApiKey, yes } = options;
2451
2502
  let { agent, prompt } = options;
2452
-
2503
+
2453
2504
  // Validate sandbox provider
2454
- if (sandbox !== 'e2b' && sandbox !== 'cloudflare') {
2505
+ if (sandbox !== 'e2b' && sandbox !== 'cloudflare' && sandbox !== 'docker') {
2455
2506
  console.log(chalk.red('āŒ Error: Invalid sandbox provider'));
2456
- console.log(chalk.yellow('šŸ’” Available providers: e2b, cloudflare'));
2507
+ console.log(chalk.yellow('šŸ’” Available providers: e2b, cloudflare, docker'));
2457
2508
  console.log(chalk.gray(' Example: --sandbox e2b --prompt "Create a web app"'));
2458
2509
  console.log(chalk.gray(' Example: --sandbox cloudflare --prompt "Calculate factorial of 5"'));
2510
+ console.log(chalk.gray(' Example: --sandbox docker --prompt "Write a function"'));
2459
2511
  return;
2460
2512
  }
2461
-
2462
- // Interactive agent selection if not provided
2463
- if (!agent) {
2513
+
2514
+ // Interactive agent selection if not provided and --yes not used
2515
+ if (!agent && !yes) {
2464
2516
  const inquirer = require('inquirer');
2465
-
2517
+
2466
2518
  console.log(chalk.blue('\nšŸ¤– Agent Selection'));
2467
2519
  console.log(chalk.cyan('═══════════════════════════════════════'));
2468
2520
  console.log(chalk.gray('Select one or more agents for your task (use SPACE to select, ENTER to confirm).\n'));
2469
-
2521
+
2470
2522
  // Fetch available agents
2471
2523
  console.log(chalk.gray('ā³ Fetching available agents...'));
2472
2524
  const agents = await getAvailableAgentsFromGitHub();
2473
-
2525
+
2474
2526
  // Format agents for selection with full path
2475
2527
  const agentChoices = agents.map(a => ({
2476
2528
  name: `${a.path} ${chalk.gray(`- ${a.category}`)}`,
2477
2529
  value: a.path, // This already includes folder/agent-name format
2478
2530
  short: a.path
2479
2531
  }));
2480
-
2532
+
2481
2533
  // First ask if they want to select agents
2482
2534
  const { wantAgents } = await inquirer.prompt([{
2483
2535
  type: 'confirm',
@@ -2485,7 +2537,7 @@ async function executeSandbox(options, targetDir) {
2485
2537
  message: 'Do you want to select specific agents for this task?',
2486
2538
  default: true
2487
2539
  }]);
2488
-
2540
+
2489
2541
  if (wantAgents) {
2490
2542
  const { selectedAgents } = await inquirer.prompt([{
2491
2543
  type: 'checkbox',
@@ -2495,7 +2547,7 @@ async function executeSandbox(options, targetDir) {
2495
2547
  pageSize: 15
2496
2548
  // Removed validation - allow empty selection
2497
2549
  }]);
2498
-
2550
+
2499
2551
  if (selectedAgents && selectedAgents.length > 0) {
2500
2552
  // Join multiple agents with comma
2501
2553
  agent = selectedAgents.join(',');
@@ -2507,6 +2559,9 @@ async function executeSandbox(options, targetDir) {
2507
2559
  } else {
2508
2560
  console.log(chalk.yellow('āš ļø Continuing without specific agents'));
2509
2561
  }
2562
+ } else if (!agent && yes) {
2563
+ // --yes flag used without --agent, proceed without agents
2564
+ console.log(chalk.yellow('āš ļø No agent specified, continuing without specific agents'));
2510
2565
  }
2511
2566
 
2512
2567
  // Get prompt from user if not provided
@@ -2603,6 +2658,19 @@ async function executeSandbox(options, targetDir) {
2603
2658
 
2604
2659
  // Execute Cloudflare sandbox
2605
2660
  await executeCloudflareSandbox({ sandbox, agent, prompt, command, mcp, setting, hook, anthropicKey }, targetDir);
2661
+
2662
+ } else if (sandbox === 'docker') {
2663
+ if (!anthropicKey) {
2664
+ console.log(chalk.red('āŒ Error: Anthropic API key is required for Docker sandbox'));
2665
+ console.log(chalk.yellow('šŸ’” Options:'));
2666
+ console.log(chalk.gray(' 1. Set environment variable: ANTHROPIC_API_KEY=your_key'));
2667
+ console.log(chalk.gray(' 2. Use CLI parameter: --anthropic-api-key your_key'));
2668
+ console.log(chalk.blue(' Get your key at: https://console.anthropic.com'));
2669
+ return;
2670
+ }
2671
+
2672
+ // Execute Docker sandbox
2673
+ await executeDockerSandbox({ sandbox, agent, prompt, command, mcp, setting, hook, anthropicKey, yes: options.yes }, targetDir);
2606
2674
  }
2607
2675
  }
2608
2676
 
@@ -2817,6 +2885,199 @@ async function executeCloudflareSandbox(options, targetDir) {
2817
2885
  }
2818
2886
  }
2819
2887
 
2888
+ async function executeDockerSandbox(options, targetDir) {
2889
+ const { agent, command, mcp, setting, hook, prompt, anthropicKey, yes } = options;
2890
+
2891
+ console.log(chalk.blue('\n🐳 Docker Sandbox Execution'));
2892
+ console.log(chalk.cyan('═══════════════════════════════════════'));
2893
+
2894
+ if (agent) {
2895
+ const agentList = agent.split(',');
2896
+ if (agentList.length > 1) {
2897
+ console.log(chalk.white(`šŸ“‹ Agents (${agentList.length}):`));
2898
+ agentList.forEach(a => console.log(chalk.yellow(` • ${a.trim()}`)));
2899
+ } else {
2900
+ console.log(chalk.white(`šŸ“‹ Agent: ${chalk.yellow(agent)}`));
2901
+ }
2902
+ } else {
2903
+ console.log(chalk.white(`šŸ“‹ Agent: ${chalk.yellow('default')}`));
2904
+ }
2905
+
2906
+ const truncatedPrompt = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
2907
+ console.log(chalk.white(`šŸ’­ Prompt: ${chalk.cyan('"' + truncatedPrompt + '"')}`));
2908
+ console.log(chalk.white(`🐳 Provider: ${chalk.green('Docker Local')}`));
2909
+ console.log(chalk.gray('\nšŸ”§ Execution details:'));
2910
+ console.log(chalk.gray(' • Uses Claude Agent SDK for execution'));
2911
+ console.log(chalk.gray(' • Executes in isolated Docker container'));
2912
+ console.log(chalk.gray(' • Local execution with full filesystem access\n'));
2913
+
2914
+ // Skip confirmation prompt if --yes flag is used
2915
+ if (!yes) {
2916
+ const inquirer = require('inquirer');
2917
+
2918
+ const { shouldExecute } = await inquirer.prompt([{
2919
+ type: 'confirm',
2920
+ name: 'shouldExecute',
2921
+ message: 'Execute this prompt in Docker sandbox?',
2922
+ default: true
2923
+ }]);
2924
+
2925
+ if (!shouldExecute) {
2926
+ console.log(chalk.yellow('ā¹ļø Docker sandbox execution cancelled by user.'));
2927
+ return;
2928
+ }
2929
+ }
2930
+
2931
+ try {
2932
+ console.log(chalk.blue('šŸ”® Setting up Docker sandbox environment...'));
2933
+
2934
+ const spinner = ora('Installing Docker sandbox component...').start();
2935
+
2936
+ // Create .claude/sandbox/docker directory
2937
+ const sandboxDir = path.join(targetDir, '.claude', 'sandbox', 'docker');
2938
+ await fs.ensureDir(sandboxDir);
2939
+
2940
+ // Copy Docker component files
2941
+ const componentsDir = path.join(__dirname, '..', 'components', 'sandbox', 'docker');
2942
+
2943
+ try {
2944
+ if (await fs.pathExists(componentsDir)) {
2945
+ console.log(chalk.gray('šŸ“¦ Using local Docker component files...'));
2946
+ console.log(chalk.dim(` Source: ${componentsDir}`));
2947
+ console.log(chalk.dim(` Target: ${sandboxDir}`));
2948
+
2949
+ // Copy all files from docker directory
2950
+ await fs.copy(componentsDir, sandboxDir, {
2951
+ overwrite: true
2952
+ });
2953
+
2954
+ // Verify files were copied
2955
+ const copiedFiles = await fs.readdir(sandboxDir);
2956
+ console.log(chalk.dim(` Copied ${copiedFiles.length} items`));
2957
+ if (copiedFiles.length === 0) {
2958
+ throw new Error('No files were copied from Docker component directory');
2959
+ }
2960
+ } else {
2961
+ throw new Error(`Docker component files not found at: ${componentsDir}`);
2962
+ }
2963
+ } catch (error) {
2964
+ spinner.fail(`Failed to install Docker component: ${error.message}`);
2965
+ throw error;
2966
+ }
2967
+
2968
+ spinner.succeed('Docker sandbox component installed successfully');
2969
+
2970
+ // Check for Docker
2971
+ const dockerSpinner = ora('Checking Docker environment...').start();
2972
+
2973
+ try {
2974
+ const { spawn } = require('child_process');
2975
+
2976
+ // Check Docker installation
2977
+ const checkDocker = () => {
2978
+ return new Promise((resolve) => {
2979
+ const check = spawn('docker', ['--version'], { stdio: 'pipe' });
2980
+ check.on('close', (code) => resolve(code === 0));
2981
+ check.on('error', () => resolve(false));
2982
+ });
2983
+ };
2984
+
2985
+ const dockerAvailable = await checkDocker();
2986
+ if (!dockerAvailable) {
2987
+ dockerSpinner.fail('Docker not found');
2988
+ console.log(chalk.red('āŒ Docker is required for Docker sandbox'));
2989
+ console.log(chalk.yellow('šŸ’” Please install Docker and try again'));
2990
+ console.log(chalk.blue(' Visit: https://docs.docker.com/get-docker/'));
2991
+ return;
2992
+ }
2993
+
2994
+ // Check Docker daemon
2995
+ const checkDockerRunning = () => {
2996
+ return new Promise((resolve) => {
2997
+ const check = spawn('docker', ['ps'], { stdio: 'pipe' });
2998
+ check.on('close', (code) => resolve(code === 0));
2999
+ check.on('error', () => resolve(false));
3000
+ });
3001
+ };
3002
+
3003
+ const dockerRunning = await checkDockerRunning();
3004
+ if (!dockerRunning) {
3005
+ dockerSpinner.fail('Docker daemon not running');
3006
+ console.log(chalk.red('āŒ Docker daemon is not running'));
3007
+ console.log(chalk.yellow('šŸ’” Please start Docker and try again'));
3008
+ return;
3009
+ }
3010
+
3011
+ dockerSpinner.succeed('Docker environment ready');
3012
+
3013
+ // Build components string for installation inside sandbox
3014
+ let componentsToInstall = '';
3015
+ if (agent) {
3016
+ const agentList = agent.split(',').map(a => `--agent ${a.trim()}`);
3017
+ componentsToInstall += agentList.join(' ');
3018
+ }
3019
+ if (command) {
3020
+ const commandList = command.split(',').map(c => ` --command ${c.trim()}`);
3021
+ componentsToInstall += commandList.join(' ');
3022
+ }
3023
+ if (mcp) {
3024
+ const mcpList = mcp.split(',').map(m => ` --mcp ${m.trim()}`);
3025
+ componentsToInstall += mcpList.join(' ');
3026
+ }
3027
+ if (setting) {
3028
+ const settingList = setting.split(',').map(s => ` --setting ${s.trim()}`);
3029
+ componentsToInstall += settingList.join(' ');
3030
+ }
3031
+ if (hook) {
3032
+ const hookList = hook.split(',').map(h => ` --hook ${h.trim()}`);
3033
+ componentsToInstall += hookList.join(' ');
3034
+ }
3035
+
3036
+ // Execute Docker launcher
3037
+ const execSpinner = ora('Executing Docker sandbox...').start();
3038
+
3039
+ const launcherPath = path.join(sandboxDir, 'docker-launcher.js');
3040
+
3041
+ const dockerExec = spawn('node', [launcherPath, prompt, componentsToInstall.trim()], {
3042
+ cwd: sandboxDir,
3043
+ stdio: 'inherit',
3044
+ env: {
3045
+ ...process.env,
3046
+ ANTHROPIC_API_KEY: anthropicKey
3047
+ }
3048
+ });
3049
+
3050
+ await new Promise((resolve, reject) => {
3051
+ dockerExec.on('close', (dockerCode) => {
3052
+ if (dockerCode === 0) {
3053
+ execSpinner.succeed('Docker sandbox execution completed successfully');
3054
+ console.log(chalk.green('\nāœ… Docker sandbox execution finished!'));
3055
+ console.log(chalk.white('šŸ“ Output files are in the output/ directory'));
3056
+ resolve();
3057
+ } else {
3058
+ execSpinner.fail(`Docker sandbox execution failed with code ${dockerCode}`);
3059
+ reject(new Error(`Docker execution failed with code ${dockerCode}`));
3060
+ }
3061
+ });
3062
+
3063
+ dockerExec.on('error', (error) => {
3064
+ execSpinner.fail('Failed to execute Docker sandbox');
3065
+ reject(error);
3066
+ });
3067
+ });
3068
+
3069
+ } catch (error) {
3070
+ dockerSpinner.fail('Failed to check Docker environment');
3071
+ console.log(chalk.red(`āŒ Error: ${error.message}`));
3072
+ throw error;
3073
+ }
3074
+
3075
+ } catch (error) {
3076
+ console.log(chalk.red(`āŒ Error setting up Docker sandbox: ${error.message}`));
3077
+ console.log(chalk.yellow('šŸ’” Please check your Docker installation and try again'));
3078
+ }
3079
+ }
3080
+
2820
3081
  async function executeE2BSandbox(options, targetDir) {
2821
3082
  const { agent, prompt, command, mcp, setting, hook, e2bKey, anthropicKey } = options;
2822
3083
 
@@ -2948,21 +3209,30 @@ async function executeE2BSandbox(options, targetDir) {
2948
3209
  check.on('error', () => resolve(false));
2949
3210
  });
2950
3211
  };
2951
-
2952
- // Check for Python 3.11 first, fallback to python3
2953
- let pythonCmd = 'python3';
3212
+
3213
+ // Try to find Python 3.11 first (recommended for E2B)
3214
+ let pythonCmd = null;
2954
3215
  const python311Available = await checkPythonVersion('python3.11');
2955
3216
  if (python311Available) {
2956
3217
  pythonCmd = 'python3.11';
2957
3218
  console.log(chalk.blue('āœ“ Using Python 3.11 (recommended for E2B)'));
2958
3219
  } else {
2959
- console.log(chalk.yellow('⚠ Python 3.11 not found, using python3 (may have package restrictions)'));
3220
+ // Fall back to platform-appropriate Python commands
3221
+ console.log(chalk.yellow('⚠ Python 3.11 not found, trying platform defaults...'));
3222
+ const candidates = getPlatformPythonCandidates();
3223
+
3224
+ for (const candidate of candidates) {
3225
+ if (await checkPythonVersion(candidate)) {
3226
+ pythonCmd = candidate;
3227
+ console.log(chalk.blue(`āœ“ Using ${candidate} for E2B`));
3228
+ break;
3229
+ }
3230
+ }
2960
3231
  }
2961
-
2962
- // Verify chosen Python version works
2963
- const pythonAvailable = await checkPythonVersion(pythonCmd);
2964
- if (!pythonAvailable) {
2965
- pythonSpinner.fail('Python 3 not found');
3232
+
3233
+ // Verify we found a working Python installation
3234
+ if (!pythonCmd) {
3235
+ pythonSpinner.fail('Python not found');
2966
3236
  console.log(chalk.red('āŒ Python 3.11+ is required for E2B sandbox'));
2967
3237
  console.log(chalk.yellow('šŸ’” Please install Python 3.11+ and try again'));
2968
3238
  console.log(chalk.blue(' Visit: https://python.org/downloads'));