@lousy-agents/cli 2.5.3 → 2.6.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.
@@ -42587,6 +42587,60 @@ var yaml_dist = __webpack_require__(1198);
42587
42587
  return new FileSystemAgentFileGateway();
42588
42588
  }
42589
42589
 
42590
+ ;// CONCATENATED MODULE: ./src/gateways/claude-file-gateway.ts
42591
+ /**
42592
+ * Gateway interface for Claude Code file operations.
42593
+ * Handles reading and writing .claude/settings.json and CLAUDE.md files.
42594
+ */
42595
+
42596
+
42597
+ /**
42598
+ * File system implementation of ClaudeFileGateway
42599
+ */ class FileSystemClaudeFileGateway {
42600
+ async readSettings(targetDir) {
42601
+ const settingsPath = (0,external_node_path_.join)(targetDir, ".claude", "settings.json");
42602
+ if (!await file_system_utils_fileExists(settingsPath)) {
42603
+ return null;
42604
+ }
42605
+ try {
42606
+ const content = await (0,promises_.readFile)(settingsPath, "utf-8");
42607
+ return JSON.parse(content);
42608
+ } catch {
42609
+ // If JSON parsing fails, treat as if file doesn't exist
42610
+ return null;
42611
+ }
42612
+ }
42613
+ async writeSettings(targetDir, settings) {
42614
+ const claudeDir = (0,external_node_path_.join)(targetDir, ".claude");
42615
+ const settingsPath = (0,external_node_path_.join)(claudeDir, "settings.json");
42616
+ // Ensure .claude directory exists
42617
+ await (0,promises_.mkdir)(claudeDir, {
42618
+ recursive: true
42619
+ });
42620
+ // Write with 2-space indentation and trailing newline
42621
+ const content = `${JSON.stringify(settings, null, 2)}\n`;
42622
+ await (0,promises_.writeFile)(settingsPath, content, "utf-8");
42623
+ }
42624
+ async readDocumentation(targetDir) {
42625
+ const docPath = (0,external_node_path_.join)(targetDir, "CLAUDE.md");
42626
+ if (!await file_system_utils_fileExists(docPath)) {
42627
+ return null;
42628
+ }
42629
+ return (0,promises_.readFile)(docPath, "utf-8");
42630
+ }
42631
+ async writeDocumentation(targetDir, content) {
42632
+ const docPath = (0,external_node_path_.join)(targetDir, "CLAUDE.md");
42633
+ // Ensure content has trailing newline
42634
+ const normalizedContent = content.endsWith("\n") ? content : `${content}\n`;
42635
+ await (0,promises_.writeFile)(docPath, normalizedContent, "utf-8");
42636
+ }
42637
+ }
42638
+ /**
42639
+ * Factory function to create a Claude file gateway
42640
+ */ function createClaudeFileGateway() {
42641
+ return new FileSystemClaudeFileGateway();
42642
+ }
42643
+
42590
42644
  ;// CONCATENATED MODULE: ./src/entities/copilot-setup.ts
42591
42645
  /**
42592
42646
  * Core domain entities for the Copilot Setup Steps feature.
@@ -45979,6 +46033,7 @@ async function watchConfig(options) {
45979
46033
 
45980
46034
 
45981
46035
 
46036
+
45982
46037
  ;// CONCATENATED MODULE: ./src/mcp/tools/types.ts
45983
46038
  /**
45984
46039
  * Shared types and utilities for MCP tool handlers.
@@ -46091,6 +46146,441 @@ async function watchConfig(options) {
46091
46146
  });
46092
46147
  };
46093
46148
 
46149
+ ;// CONCATENATED MODULE: ./src/use-cases/claude-setup.ts
46150
+ /**
46151
+ * Use cases for Claude Code Web Environment Setup feature.
46152
+ * This module handles the logic of building SessionStart hooks from environment detection,
46153
+ * merging settings, and generating documentation.
46154
+ */
46155
+ /**
46156
+ * List of allowed version filenames.
46157
+ * Using a hardcoded allowlist prevents command injection via malicious filenames
46158
+ * in user-customizable config files.
46159
+ */ const ALLOWED_VERSION_FILENAMES = [
46160
+ ".nvmrc",
46161
+ ".node-version",
46162
+ ".python-version",
46163
+ ".java-version",
46164
+ ".ruby-version",
46165
+ ".go-version"
46166
+ ];
46167
+ /**
46168
+ * Validates that a filename is in the allowlist of known version files.
46169
+ * Prevents command injection by only allowing hardcoded safe filenames.
46170
+ *
46171
+ * @param filename The filename to validate
46172
+ * @returns true if filename is in the allowlist
46173
+ */ function isValidVersionFilename(filename) {
46174
+ return ALLOWED_VERSION_FILENAMES.includes(filename);
46175
+ }
46176
+ /**
46177
+ * Builds SessionStart hooks from detected environment.
46178
+ * Transforms environment configuration into Claude Code SessionStart commands.
46179
+ *
46180
+ * @param environment The detected environment configuration
46181
+ * @param config Optional copilot-setup configuration (for package manager mappings)
46182
+ * @returns Array of SessionStart hooks
46183
+ */ async function buildSessionStartHooks(environment, config) {
46184
+ const loadedConfig = config || await loadCopilotSetupConfig();
46185
+ const hooks = [];
46186
+ // If mise.toml is present, add mise install
46187
+ if (environment.hasMise) {
46188
+ hooks.push({
46189
+ command: "mise install",
46190
+ description: "Install runtimes from mise.toml"
46191
+ });
46192
+ // After mise install, add package manager install hooks
46193
+ const packageManagerHooks = buildPackageManagerHooks(environment.packageManagers, loadedConfig);
46194
+ hooks.push(...packageManagerHooks);
46195
+ return hooks;
46196
+ }
46197
+ // Otherwise, add runtime installation hooks for each version file
46198
+ const runtimeHooks = buildRuntimeHooks(environment.versionFiles);
46199
+ hooks.push(...runtimeHooks);
46200
+ // Add package manager install hooks
46201
+ const packageManagerHooks = buildPackageManagerHooks(environment.packageManagers, loadedConfig);
46202
+ hooks.push(...packageManagerHooks);
46203
+ return hooks;
46204
+ }
46205
+ /**
46206
+ * Builds runtime installation hooks from version files.
46207
+ * Maps version files to appropriate runtime manager commands (nvm, pyenv, etc.)
46208
+ *
46209
+ * @param versionFiles Array of detected version files
46210
+ * @returns Array of runtime installation hooks
46211
+ */ function buildRuntimeHooks(versionFiles) {
46212
+ const hooks = [];
46213
+ const addedTypes = new Set();
46214
+ for (const versionFile of versionFiles){
46215
+ // Deduplicate by type (e.g., .nvmrc and .node-version both use nvm)
46216
+ if (addedTypes.has(versionFile.type)) {
46217
+ continue;
46218
+ }
46219
+ addedTypes.add(versionFile.type);
46220
+ const hook = getRuntimeHookForType(versionFile.type, versionFile);
46221
+ if (hook) {
46222
+ hooks.push(hook);
46223
+ }
46224
+ }
46225
+ return hooks;
46226
+ }
46227
+ /**
46228
+ * Gets the runtime installation hook for a specific version file type.
46229
+ *
46230
+ * @param type The version file type
46231
+ * @param versionFile The version file metadata
46232
+ * @returns SessionStart hook or null if not supported
46233
+ */ function getRuntimeHookForType(type, versionFile) {
46234
+ const versionInfo = versionFile.version ? ` (${versionFile.version})` : "";
46235
+ if (!isValidVersionFilename(versionFile.filename)) {
46236
+ return null;
46237
+ }
46238
+ switch(type){
46239
+ case "node":
46240
+ return {
46241
+ command: "nvm install",
46242
+ description: `Install Node.js from ${versionFile.filename}${versionInfo}`
46243
+ };
46244
+ case "python":
46245
+ return {
46246
+ command: `pyenv install -s $(cat ${versionFile.filename})`,
46247
+ description: `Install Python from ${versionFile.filename}${versionInfo}`
46248
+ };
46249
+ case "ruby":
46250
+ return {
46251
+ command: `rbenv install -s $(cat ${versionFile.filename})`,
46252
+ description: `Install Ruby from ${versionFile.filename}${versionInfo}`
46253
+ };
46254
+ case "java":
46255
+ return null;
46256
+ case "go":
46257
+ // Go version management typically handled by asdf or gvm
46258
+ // For now, document but don't generate command as it's environment-specific
46259
+ return null;
46260
+ default:
46261
+ return null;
46262
+ }
46263
+ }
46264
+ /**
46265
+ * Builds package manager installation hooks from detected package managers.
46266
+ *
46267
+ * @param packageManagers Array of detected package managers
46268
+ * @param config Configuration for package manager mappings
46269
+ * @returns Array of package manager installation hooks
46270
+ */ function buildPackageManagerHooks(packageManagers, config) {
46271
+ const hooks = [];
46272
+ const addedTypes = new Set();
46273
+ for (const pm of packageManagers){
46274
+ // Skip if we've already added this package manager type
46275
+ if (addedTypes.has(pm.type)) {
46276
+ continue;
46277
+ }
46278
+ addedTypes.add(pm.type);
46279
+ // Find the config for this package manager
46280
+ const pmConfig = config.packageManagers.find((c)=>c.type === pm.type);
46281
+ if (!pmConfig) {
46282
+ continue;
46283
+ }
46284
+ const description = getPackageManagerDescription(pm.type, pm);
46285
+ hooks.push({
46286
+ command: pmConfig.installCommand,
46287
+ description
46288
+ });
46289
+ }
46290
+ return hooks;
46291
+ }
46292
+ /**
46293
+ * Gets a description for a package manager hook.
46294
+ */ function getPackageManagerDescription(packageManagerType, pm) {
46295
+ const lockfileInfo = pm.lockfile ? ` with ${pm.lockfile}` : "";
46296
+ const descriptions = {
46297
+ npm: `Install Node.js dependencies${lockfileInfo}`,
46298
+ yarn: `Install Node.js dependencies${lockfileInfo}`,
46299
+ pnpm: `Install Node.js dependencies${lockfileInfo}`,
46300
+ pip: `Install Python dependencies from ${pm.filename}`,
46301
+ pipenv: `Install Python dependencies${lockfileInfo}`,
46302
+ poetry: `Install Python dependencies${lockfileInfo}`,
46303
+ bundler: `Install Ruby dependencies${lockfileInfo}`,
46304
+ cargo: `Build Rust project`,
46305
+ composer: `Install PHP dependencies${lockfileInfo}`,
46306
+ maven: `Install Java dependencies`,
46307
+ gradle: `Build Gradle project`,
46308
+ gomod: `Download Go dependencies`,
46309
+ pub: `Install Dart dependencies`
46310
+ };
46311
+ return descriptions[packageManagerType] || `Install dependencies from ${pm.filename}`;
46312
+ }
46313
+ /**
46314
+ * Merges SessionStart hooks into existing Claude settings.
46315
+ * Preserves existing settings while adding or updating hooks without duplication.
46316
+ *
46317
+ * @param existing Existing Claude settings or null if none exist
46318
+ * @param hooks Array of SessionStart hooks to merge
46319
+ * @returns Merged Claude settings
46320
+ */ function mergeClaudeSettings(existing, hooks) {
46321
+ // If no existing settings, create new with hooks
46322
+ if (!existing) {
46323
+ return {
46324
+ // biome-ignore lint/style/useNamingConvention: SessionStart is the Claude Code API property name
46325
+ SessionStart: hooks.map((h)=>h.command)
46326
+ };
46327
+ }
46328
+ // Preserve all existing settings
46329
+ const merged = {
46330
+ ...existing
46331
+ };
46332
+ // Get existing SessionStart commands or empty array
46333
+ const existingCommands = existing.SessionStart || [];
46334
+ // Merge new hooks with existing commands in deterministic order:
46335
+ // 1) tool-generated commands (from hooks) in their original order
46336
+ // 2) any existing commands not already included, preserving their order
46337
+ const newCommands = hooks.map((h)=>h.command);
46338
+ const mergedCommands = [];
46339
+ for (const command of newCommands){
46340
+ if (!mergedCommands.includes(command)) {
46341
+ mergedCommands.push(command);
46342
+ }
46343
+ }
46344
+ for (const command of existingCommands){
46345
+ if (!mergedCommands.includes(command)) {
46346
+ mergedCommands.push(command);
46347
+ }
46348
+ }
46349
+ merged.SessionStart = mergedCommands;
46350
+ return merged;
46351
+ }
46352
+ /**
46353
+ * Generates an Environment Setup section for CLAUDE.md documentation.
46354
+ * Creates markdown documenting the detected environment and SessionStart hooks.
46355
+ *
46356
+ * @param environment The detected environment configuration
46357
+ * @param hooks Array of SessionStart hooks
46358
+ * @returns Markdown content for Environment Setup section
46359
+ */ function generateEnvironmentSetupSection(environment, hooks) {
46360
+ const lines = [];
46361
+ lines.push("## Environment Setup");
46362
+ lines.push("");
46363
+ // Document detected configuration
46364
+ if (environment.hasMise) {
46365
+ lines.push("This project uses [mise](https://mise.jdx.dev/) for runtime management.");
46366
+ lines.push("");
46367
+ }
46368
+ if (environment.versionFiles.length > 0) {
46369
+ lines.push("### Detected Runtimes");
46370
+ lines.push("");
46371
+ for (const vf of environment.versionFiles){
46372
+ const versionInfo = vf.version ? ` (${vf.version})` : "";
46373
+ lines.push(`- **${vf.type}**: ${vf.filename}${versionInfo}`);
46374
+ }
46375
+ lines.push("");
46376
+ }
46377
+ if (environment.packageManagers.length > 0) {
46378
+ lines.push("### Package Managers");
46379
+ lines.push("");
46380
+ for (const pm of environment.packageManagers){
46381
+ const lockfileInfo = pm.lockfile ? ` with ${pm.lockfile}` : "";
46382
+ lines.push(`- **${pm.type}**: ${pm.filename}${lockfileInfo}`);
46383
+ }
46384
+ lines.push("");
46385
+ }
46386
+ // Document SessionStart hooks
46387
+ if (hooks.length > 0) {
46388
+ lines.push("### SessionStart Hooks");
46389
+ lines.push("");
46390
+ lines.push("The following commands run automatically when a Claude Code session starts:");
46391
+ lines.push("");
46392
+ for (const hook of hooks){
46393
+ lines.push("```bash");
46394
+ lines.push(hook.command);
46395
+ lines.push("```");
46396
+ if (hook.description) {
46397
+ lines.push(`*${hook.description}*`);
46398
+ }
46399
+ lines.push("");
46400
+ }
46401
+ } else {
46402
+ lines.push("No environment-specific configuration detected. If this project requires specific runtimes or dependencies, add version files (e.g., `.nvmrc`, `.python-version`) or dependency manifests (e.g., `package.json`, `requirements.txt`) to enable automated setup.");
46403
+ lines.push("");
46404
+ }
46405
+ return lines.join("\n");
46406
+ }
46407
+ /**
46408
+ * Merges an Environment Setup section into existing CLAUDE.md documentation.
46409
+ * Replaces existing section if found, or appends if not present.
46410
+ *
46411
+ * @param existing Existing CLAUDE.md content or null if file doesn't exist
46412
+ * @param setupSection The Environment Setup section content
46413
+ * @returns Updated CLAUDE.md content
46414
+ */ function mergeClaudeDocumentation(existing, setupSection) {
46415
+ // If no existing documentation, create new with setup section
46416
+ if (!existing) {
46417
+ return `# Claude Code Environment\n\n${setupSection}`;
46418
+ }
46419
+ // Split content into lines and find Environment Setup section
46420
+ const lines = existing.split("\n");
46421
+ let inEnvSetup = false;
46422
+ let envSetupStartLine = -1;
46423
+ let envSetupEndLine = -1;
46424
+ for(let i = 0; i < lines.length; i++){
46425
+ const line = lines[i];
46426
+ if (line === "## Environment Setup") {
46427
+ inEnvSetup = true;
46428
+ envSetupStartLine = i;
46429
+ continue;
46430
+ }
46431
+ if (inEnvSetup && (line.startsWith("## ") || line.startsWith("# "))) {
46432
+ // Found next section
46433
+ envSetupEndLine = i;
46434
+ break;
46435
+ }
46436
+ }
46437
+ // If we found Environment Setup section
46438
+ if (envSetupStartLine >= 0) {
46439
+ // If we didn't find an end, it goes to the end of file
46440
+ if (envSetupEndLine === -1) {
46441
+ envSetupEndLine = lines.length;
46442
+ }
46443
+ // Replace the section
46444
+ const before = lines.slice(0, envSetupStartLine).join("\n");
46445
+ const after = lines.slice(envSetupEndLine).join("\n");
46446
+ const parts = [
46447
+ before.trimEnd()
46448
+ ];
46449
+ if (before.trim()) {
46450
+ parts.push("\n\n");
46451
+ }
46452
+ parts.push(setupSection.trimEnd());
46453
+ if (after.trim()) {
46454
+ parts.push("\n\n");
46455
+ parts.push(after);
46456
+ }
46457
+ return parts.join("");
46458
+ }
46459
+ // Append section to end
46460
+ const trimmedExisting = existing.trimEnd();
46461
+ return `${trimmedExisting}\n\n${setupSection}`;
46462
+ }
46463
+
46464
+ ;// CONCATENATED MODULE: ./src/mcp/tools/create-claude-code-web-setup.ts
46465
+ /**
46466
+ * MCP tool handler for creating or updating Claude Code Web Environment Setup.
46467
+ */
46468
+
46469
+
46470
+
46471
+ /**
46472
+ * Creates or updates Claude Code web environment setup files.
46473
+ */ const createClaudeCodeWebSetupHandler = async (args)=>{
46474
+ const dir = args.targetDir || process.cwd();
46475
+ if (!await file_system_utils_fileExists(dir)) {
46476
+ return types_errorResponse(`Target directory does not exist: ${dir}`);
46477
+ }
46478
+ const environmentGateway = createEnvironmentGateway();
46479
+ const claudeGateway = createClaudeFileGateway();
46480
+ // Detect environment configuration
46481
+ const environment = await environmentGateway.detectEnvironment(dir);
46482
+ // Build SessionStart hooks from environment
46483
+ const hooks = await buildSessionStartHooks(environment);
46484
+ // Read existing settings
46485
+ const existingSettings = await claudeGateway.readSettings(dir);
46486
+ // Merge settings
46487
+ const mergedSettings = mergeClaudeSettings(existingSettings, hooks);
46488
+ // Check if settings changed (normalize JSON for comparison)
46489
+ const settingsChanged = JSON.stringify(existingSettings, null, 2) !== JSON.stringify(mergedSettings, null, 2);
46490
+ // Read existing documentation
46491
+ const existingDocs = await claudeGateway.readDocumentation(dir);
46492
+ // Generate environment setup section
46493
+ const setupSection = generateEnvironmentSetupSection(environment, hooks);
46494
+ // Merge documentation
46495
+ const mergedDocs = mergeClaudeDocumentation(existingDocs, setupSection);
46496
+ // Check if documentation changed (normalize for comparison - trim and ensure trailing newline)
46497
+ const normalizeDoc = (doc)=>doc ? `${doc.trimEnd()}\n` : null;
46498
+ const docsChanged = normalizeDoc(existingDocs) !== normalizeDoc(mergedDocs);
46499
+ // Determine action before writing
46500
+ let action;
46501
+ if (!settingsChanged && !docsChanged) {
46502
+ action = "no_changes_needed";
46503
+ } else if (!existingSettings && !existingDocs) {
46504
+ action = "created";
46505
+ } else {
46506
+ action = "updated";
46507
+ }
46508
+ // Only write if there are changes
46509
+ if (settingsChanged) {
46510
+ await claudeGateway.writeSettings(dir, mergedSettings);
46511
+ }
46512
+ if (docsChanged) {
46513
+ await claudeGateway.writeDocumentation(dir, mergedDocs);
46514
+ }
46515
+ const settingsPath = (0,external_node_path_.join)(dir, ".claude", "settings.json");
46516
+ const documentationPath = (0,external_node_path_.join)(dir, "CLAUDE.md");
46517
+ // Build result
46518
+ const result = {
46519
+ hooks,
46520
+ environment,
46521
+ settingsPath,
46522
+ documentationPath,
46523
+ action,
46524
+ recommendations: buildRecommendations(environment)
46525
+ };
46526
+ // Format message
46527
+ const message = buildResultMessage(result);
46528
+ return successResponse({
46529
+ ...result,
46530
+ message,
46531
+ // Make hooks serializable for JSON response
46532
+ hooks: hooks.map((h)=>({
46533
+ command: h.command,
46534
+ description: h.description
46535
+ }))
46536
+ });
46537
+ };
46538
+ /**
46539
+ * Builds recommendations for UI-level environment configuration.
46540
+ */ function buildRecommendations(environment) {
46541
+ const recommendations = [];
46542
+ // If package managers detected, recommend network access
46543
+ if (environment.packageManagers && environment.packageManagers.length > 0) {
46544
+ recommendations.push({
46545
+ type: "network_access",
46546
+ description: "Enable internet access in Claude Code environment settings to allow package installation"
46547
+ });
46548
+ }
46549
+ return recommendations.length > 0 ? recommendations : undefined;
46550
+ }
46551
+ /**
46552
+ * Builds a human-readable result message.
46553
+ */ function buildResultMessage(result) {
46554
+ const lines = [];
46555
+ if (result.action === "created") {
46556
+ lines.push(`Created Claude Code environment setup with ${result.hooks.length} SessionStart hook(s)`);
46557
+ } else if (result.action === "updated") {
46558
+ lines.push(`Updated Claude Code environment setup with ${result.hooks.length} SessionStart hook(s)`);
46559
+ } else {
46560
+ lines.push("No changes needed - environment setup is already current");
46561
+ }
46562
+ if (result.hooks.length > 0) {
46563
+ lines.push("");
46564
+ lines.push("SessionStart hooks:");
46565
+ for (const hook of result.hooks){
46566
+ lines.push(` - ${hook.command}`);
46567
+ if (hook.description) {
46568
+ lines.push(` ${hook.description}`);
46569
+ }
46570
+ }
46571
+ }
46572
+ if (result.recommendations && result.recommendations.length > 0) {
46573
+ lines.push("");
46574
+ lines.push("Recommendations:");
46575
+ for (const rec of result.recommendations){
46576
+ lines.push(` - ${rec.description}`);
46577
+ }
46578
+ }
46579
+ lines.push("");
46580
+ lines.push(`Files: ${result.settingsPath}, ${result.documentationPath}`);
46581
+ return lines.join("\n");
46582
+ }
46583
+
46094
46584
  ;// CONCATENATED MODULE: ./src/use-cases/action-resolution.ts
46095
46585
  /**
46096
46586
  * Use case for building action resolution metadata.
@@ -47476,6 +47966,7 @@ function getGlobalWacContext() {
47476
47966
 
47477
47967
 
47478
47968
 
47969
+
47479
47970
  ;// CONCATENATED MODULE: ./src/mcp/server.ts
47480
47971
  /**
47481
47972
  * MCP Server for Copilot Setup Steps workflow management.
@@ -47551,6 +48042,12 @@ function getGlobalWacContext() {
47551
48042
  handler: createCopilotSetupWorkflowHandler,
47552
48043
  inputSchema: createWorkflowInputSchema
47553
48044
  },
48045
+ {
48046
+ name: "create_claude_code_web_setup",
48047
+ description: "Create or update Claude Code web environment setup (.claude/settings.json with SessionStart hooks and CLAUDE.md documentation) based on detected environment configuration.",
48048
+ handler: createClaudeCodeWebSetupHandler,
48049
+ inputSchema: targetDirInputSchema
48050
+ },
47554
48051
  {
47555
48052
  name: "analyze_action_versions",
47556
48053
  description: "Analyze GitHub Action versions used across all workflow files in a target directory",