Skill: neo-tf-module-generator (version 1.0.1)

--- name: tf-module-generator description: Automatically generates Terraform modules from existing cloud infrastructure resources with intelligent resource detection and best-practice code formatting. --- # tf-module-generator Skill Documentation ## 1. Skill Name tf-module-generator ## 2. Description Automatically generates Terraform modules from existing cloud infrastructure resources across AWS, Azure, and GCP. The skill discovers live cloud resources, creates optimized Terraform code with proper variable definitions and outputs, and validates the generated modules against Terraform best practices. ## 3. Core Capabilities - Scan and analyze existing cloud infrastructure resources across AWS, Azure, and GCP - Generate optimized Terraform module code with proper variable definitions and outputs - Support multiple resource types (compute, storage, networking, databases, etc.) - Generate documentation and example usage for each generated module - Validate generated Terraform syntax and best practices - Provide CLI interface with customizable output options and error handling - Integrate seamlessly with OpenClaw agent workflows for automated infrastructure management ## 4. Out of Scope - Infrastructure provisioning (only generates code from existing resources) - Cost optimization recommendations - Security policy enforcement - Multi-environment management (dev/staging/prod) - State management or backend configuration - Testing the generated modules beyond syntax validation - Support for non-cloud infrastructure (on-prem, hybrid environments) ## 5. Trigger Scenarios - "Generate Terraform module from my existing AWS EC2 instances" - "Convert my Azure resources to Terraform modules" - "Create Terraform code from my GCP cloud resources" - "Generate module for my current VPC configuration" - "Automate Terraform module creation for our cloud infrastructure" - "Generate Terraform from existing [service] resources" - "Create reusable Terraform modules from our deployed resources" ## 6. Requirements - Cloud provider credentials configured (AWS CLI, Azure CLI, or GCP CLI) - Node.js 18+ runtime - Access to `terraform` binary for validation (optional but recommended) - OpenCl agent with sessions_spawn capability ## 7. Key Files - `SKILL.md`: This documentation - `scripts/main.js`: CLI entry point and command parsing - `scripts/aws-scanner.js`: AWS resource discovery and analysis - `scripts/azure-scanner.js`: Azure resource discovery and analysis - `scripts/gcp-scanner.js`: GCP resource discovery and analysis - `scripts/terraform-generator.js`: Terraform module code generation logic - `scripts/validator.js`: Generated code syntax and best practice validation - `scripts/config.js`: Configuration management and provider settings - `references/providers.json`: Cloud provider resource schema mappings - `references/templates/`: Directory containing module templates for different resource types - `assets/examples/`: Sample usage examples and test cases - `assets/terraform-version`: Supported Terraform version constraints - `package.json`: npm package metadata and dependencies - `README.md`: Quick start guide ## 8. Acceptance Criteria - [ ] CLI successfully scans and identifies resources from at least 3 cloud providers - [ ] Generated Terraform modules pass syntax validation with `terraform validate` - [ ] All generated code follows Terraform best practices conventions - [ ] Skill integrates correctly with OpenClaw session_spawn for agent usage - [ ] Error handling provides clear, actionable feedback for common failure cases - [ ] Documentation includes usage examples for both CLI and agent integration - [ ] Code passes all automated tests including security checks - [ ] Generated modules include appropriate variables, outputs, and documentation - [ ] Skill triggers correctly for all defined trigger scenarios - [ ] Performance benchmarks meet targets for typical infrastructure sizes (<1k resources)

path: README.md
content: # tf-module-generator

![Audit](https://img.shields.io/badge/audit%3A%20FAIL-red) ![License](https://img.shields.io/badge/license-MIT-blue) ![OpenClaw](https://img.shields.io/badge/OpenClaw-skill-orange)

> A skill that automatically generates Terraform modules from existing cloud infrastructure resources.

I don't have any context about where you left off or what the previous attempt was. Could you tell me what task you were working on or what you'd like me to help you with?

## OpenClaw Marketplace

This skill is available on [ClawHub](https://clawhub.ai) — the OpenClaw skill marketplace.
Install it in any OpenClaw agent workspace with:

```bash
clawhub install tf-module-generator
```

**Recommended price when commercial:** $29

## License

MIT © NeoSkillFactory


path: assets/examples/example-aws-module/README.md
content: # Example AWS Infrastructure Module

This is an example Terraform module generated by the `tf-module-generator` skill. It provisions a complete AWS infrastructure including VPC, subnets, NAT gateways, security groups, and EC2 instances.

## Architecture

```
VPC (10.0.0.0/16)
├── Public Subnets (10.0.101.0/24, 10.0.102.0/24)
│   └── NAT Gateways (one per AZ)
│   └── EC2 Instances (optional public IP)
└── Private Subnets (10.0.1.0/24, 10.0.2.0/24)
    └── EC2 Instances (private IP only)
```

## Features

- Multi-AZ deployment for high availability
- Public and private subnets
- NAT Gateways for outbound internet access from private subnets
- Internet Gateway for public internet access
- Security groups with sensible defaults (SSH, HTTP, HTTPS)
- Configurable instance types and AMIs
- Comprehensive tagging
- IPv4-only networking

## Requirements

| Name | Version |
|------|---------|
| terraform | >= 1.0.0 |
| aws | ~> 5.0 |

## Providers

- `aws` (HashiCorp) - Amazon Web Services

## Usage

### Basic Example

```hcl
module "aws_infrastructure" {
  source = "./example-aws-module"

  aws_region  = "us-east-1"
  environment = "dev"

  vpc_cidr           = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b"]

  private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnet_cidrs  = ["10.0.101.0/24", "10.0.102.0/24"]

  instance_type = "t3.micro"
  ami_id        = "ami-0c55b159cbfafe1f0"  # Amazon Linux 2023 (us-east-1)
  key_name      = "my-ssh-key"

  default_tags = {
    Owner       = "team-infrastructure"
    CostCenter  = "engineering"
    Environment = "dev"
  }
}
```

### Minimal Example

```hcl
module "minimal" {
  source = "./example-aws-module"

  environment = "test"
}
```

This uses all default values and creates:
- 2 t3.micro EC2 instances (Amazon Linux 2023 AMI)
- VPC with CIDR 10.0.0.0/16
- 2 public subnets and 2 private subnets
- NAT Gateways and Internet Gateway
- Security groups allowing SSH (from anywhere - adjust!), HTTP, HTTPS

## Arguments

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| `aws_region` | AWS region to deploy resources | `string` | `"us-east-1"` | no |
| `environment` | Environment name for tagging | `string` | `"dev"` | no |
| `vpc_cidr` | CIDR block for the VPC | `string` | `"10.0.0.0/16"` | no |
| `availability_zones` | List of availability zones | `list(string)` | `["us-east-1a", "us-east-1b"]` | no |
| `private_subnet_cidrs` | CIDR blocks for private subnets | `list(string)` | `["10.0.1.0/24", "10.0.2.0/24"]` | no |
| `public_subnet_cidrs` | CIDR blocks for public subnets | `list(string)` | `["10.0.101.0/24", "10.0.102.0/24"]` | no |
| `instance_type` | EC2 instance type | `string` | `"t3.micro"` | no |
| `ami_id` | AMI ID for EC2 instances | `string` | (Amazon Linux 2023 in us-east-1) | no |
| `key_name` | SSH key pair name | `string` | `null` | no |
| `ssh_cidr_blocks` | CIDR blocks allowed to SSH | `list(string)` | `["0.0.0.0/0"]` | no |
| `default_tags` | Default tags for all resources | `map(string)` | see below | no |

### Default Tags

The module applies these tags to all resources:

```hcl
{
  Project     = "tf-module-generator-example"
  ManagedBy   = "Terraform"
  Repo        = "https://github.com/example/repo"
  Environment = var.environment
}
```

## Outputs

| Name | Description |
|------|-------------|
| `vpc_id` | ID of the created VPC |
| `vpc_cidr_block` | CIDR block of the VPC |
| `public_subnet_ids` | IDs of the public subnets |
| `private_subnet_ids` | IDs of the private subnets |
| `instance_ids` | IDs of the EC2 instances |
| `instance_public_ips` | Public IP addresses of EC2 instances |
| `instance_private_ips` | Private IP addresses of EC2 instances |
| `security_group_id` | ID of the instance security group |

## Security Considerations

- **SSH Access**: By default, SSH is allowed from `0.0.0.0/0`. Change `ssh_cidr_blocks` to restrict access.
- **NAT Gateways**: NAT Gateways incur hourly charges. Consider using only public subnets for cost-sensitive environments.
- **Instance Key Pair**: Using SSH key pairs is more secure than passwords. Ensure `key_name` points to an existing key pair.
- **AMI Selection**: The default AMI is specific to `us-east-1`. Override `ami_id` for other regions.

## Cost Estimate (as of 2025)

| Resource | Monthly Cost (us-east-1) |
|----------|-------------------------|
| EC2 (t3.micro, Linux) | ~$8.50 per instance |
| NAT Gateway | ~$33.60 per AZ |
| EIP (attached) | Free |
| EIP (idle) | ~$3.60 each |
| VPC | Free |
| Subnets | Free |
| Security Groups | Free |

**Total (2 instances + 2 NAT Gateways)**: ~$74/month

## Clean Up

```bash
terraform destroy
```

## Generated By

This example was automatically generated by the `tf-module-generator` skill. Original infrastructure was discovered in an existing AWS account and converted to reusable Terraform code.

## License

MIT - see LICENSE for details.


path: assets/examples/example-azure-module/README.md
content: # Example Azure Infrastructure Module

This is an example Terraform module generated by the `tf-module-generator` skill. It provisions a complete Azure infrastructure including Resource Group, Virtual Network, subnets, NAT Gateway, Network Security Group, and Linux Virtual Machines.

## Architecture

```
Resource Group
└── Virtual Network (10.0.0.0/16)
    ├── Public Subnets (10.0.101.0/24, 10.0.102.0/24)
    │   └── NAT Gateway (outbound internet for private subnets)
    │   └── Network Security Group (SSH, HTTP, HTTPS)
    │   └── Linux Virtual Machines (optional public IP)
    └── Private Subnets (10.0.1.0/24, 10.0.2.0/24)
        └── Linux Virtual Machines (private IP only, NAT for outbound)
```

## Features

- Multi-region / zone support (using availability zones)
- Public and private subnets
- NAT Gateway for outbound internet access from private subnets
- Network Security Group with sensible defaults (SSH, HTTP, HTTPS)
- Configurable VM sizes and images
- Comprehensive tagging
- IPv4 networking

## Requirements

| Name | Version |
|------|---------|
| terraform | >= 1.0.0 |
| azurerm | ~> 4.0 |

## Providers

- `azurerm` (HashiCorp) - Microsoft Azure

## Usage

### Basic Example

```hcl
module "azure_infrastructure" {
  source = "./example-azure-module"

  azure_region  = "eastus"
  environment   = "dev"

  resource_group_name = "rg-example-dev"

  virtual_network_name = "vnet-example"
  vpc_cidr             = "10.0.0.0/16"

  private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnet_cidrs  = ["10.0.101.0/24", "10.0.102.0/24"]

  vm_size          = "Standard_B1s"
  admin_username   = "azureuser"
  ssh_public_key   = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB..."

  default_tags = {
    Owner       = "team-infrastructure"
    CostCenter  = "engineering"
    Environment = "dev"
  }
}
```

### Minimal Example

```hcl
module "minimal" {
  source = "./example-azure-module"

  environment = "test"
}
```

This uses all default values and creates:
- 2 Standard_B1s Linux VMs (Ubuntu 22.04)
- Resource Group "rg-example-test"
- VNet with CIDR 10.0.0.0/16
- 2 public subnets and 2 private subnets
- NAT Gateway and Public IP Prefix
- Network Security Group allowing SSH (from anywhere - adjust!), HTTP, HTTPS

## Arguments

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| `azure_region` | Azure region to deploy resources | `string` | `"eastus"` | no |
| `environment` | Environment name for tagging | `string` | `"dev"` | no |
| `resource_group_name` | Name for the resource group | `string` | `"rg-${var.environment}"` | no |
| `virtual_network_name` | Name for the virtual network | `string` | `"vnet-${var.environment}"` | no |
| `vpc_cidr` | CIDR block for the VNet | `string` | `"10.0.0.0/16"` | no |
| `private_subnet_cidrs` | CIDR blocks for private subnets | `list(string)` | `["10.0.1.0/24", "10.0.2.0/24"]` | no |
| `public_subnet_cidrs` | CIDR blocks for public subnets | `list(string)` | `["10.0.101.0/24", "10.0.102.0/24"]` | no |
| `vm_size` | Azure VM size | `string` | `"Standard_B1s"` | no |
| `admin_username` | Admin username for VMs | `string` | `"azureuser"` | no |
| `ssh_public_key` | SSH public key content (use `file()` to read) | `string` | `""` | no |
| `ssh_cidr_blocks` | CIDR blocks allowed to SSH | `list(string)` | `["0.0.0.0/0"]` | no |
| `default_tags` | Default tags for all resources | `map(string)` | see below | no |

### Default Tags

The module applies these tags to all resources:

```hcl
{
  Project     = "tf-module-generator-example"
  ManagedBy   = "Terraform"
  Repo        = "https://github.com/example/repo"
  Environment = var.environment
}
```

## Outputs

| Name | Description |
|------|-------------|
| `resource_group_id` | ID of the resource group |
| `vnet_id` | ID of the virtual network |
| `public_subnet_ids` | IDs of the public subnets |
| `private_subnet_ids` | IDs of the private subnets |
| `vm_ids` | IDs of the virtual machines |
| `vm_private_ips` | Private IP addresses of VMs |
| `vm_public_ips` | Public IP addresses of VMs (if assigned) |
| `nsg_id` | ID of the network security group |
| `nat_gateway_id` | ID of the NAT Gateway |

## Security Considerations

- **SSH Access**: By default, SSH is allowed from `0.0.0.0/0`. Change `ssh_cidr_blocks` to restrict access.
- **NAT Gateway**: NAT Gateway incurs hourly + data processing charges. Consider using only public subnets for cost-sensitive environments.
- **Public IPs**: VMs in public subnets get public IPs; this increases exposure. Use NSG rules to restrict inbound traffic.
- **SSH Key**: Using SSH keys is more secure than passwords. Ensure `ssh_public_key` is set and `admin_password` is null.

## Cost Estimate (as of 2025, East US)

| Resource | Monthly Cost |
|----------|--------------|
| Linux VM (B1s) | ~$8.50 per instance |
| NAT Gateway | ~$33.60 |
| Public IP (static) | ~$4.48 each |
| VNet | Free |
| Subnets | Free |
| NSG | Free |
| Resource Group | Free |

**Total (2 VMs + NAT + 2 Public IPs)**: ~$60/month

## Clean Up

```bash
terraform destroy
```

## Generated By

This example was automatically generated by the `tf-module-generator` skill. Original infrastructure was discovered in an existing Azure account and converted to reusable Terraform code.

## License

MIT - see LICENSE for details.


path: assets/examples/example-gcp-module/README.md
content: # Example GCP Infrastructure Module

This is an example Terraform module generated by the `tf-module-generator` skill. It provisions a complete GCP infrastructure including VPC network, subnets, firewall rules, and Compute Engine instances.

## Architecture

```
Project (or dedicated)
└── VPC Network (default or custom)
    ├── Subnets (regional)
    │   ├── Compute Instances (with or without external IP)
    │   └── Cloud SQL (optional, if detected)
    └── Firewall Rules (allow SSH, HTTP, HTTPS)
```

## Features

- Multi-region subnet support
- Firewall rules for common services (SSH, HTTP, HTTPS)
- Configurable VM machine types and images
- Optional external IP assignment
- Comprehensive labeling
- IPv4 networking

## Requirements

| Name | Version |
|------|---------|
| terraform | >= 1.0.0 |
| google | ~> 5.0 |

## Providers

- `google` (HashiCorp) - Google Cloud Platform

## Usage

### Basic Example

```hcl
module "gcp_infrastructure" {
  source = "./example-gcp-module"

  gcp_region  = "us-central1"
  project     = "my-workspace-project"
  environment = "dev"

  network_name          = "network-example"
  vpc_cidr              = "10.0.0.0/16"
  private_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnet_cidrs   = ["10.0.101.0/24", "10.0.102.0/24"]

  instance_count       = 2
  machine_type         = "e2-micro"
  image_family         = "debian-12"
  admin_username       = "gcpuser"
  ssh_keys             = {
    gcpuser = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB..."
  }

  default_labels = {
    Owner       = "team-infrastructure"
    CostCenter  = "engineering"
    Environment = "dev"
  }
}
```

### Minimal Example

```hcl
module "minimal" {
  source = "./example-gcp-module"

  environment = "test"
}
```

This uses all default values and creates:
- 2 e2-micro Compute instances (Debian 12)
- VPC network with CIDR 10.0.0.0/16
- 2 public subnets and 2 private subnets (us-central1)
- Firewall rules allowing SSH (from anywhere - adjust!), HTTP, HTTPS
- Instances without external IPs (private) in private subnets

## Arguments

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| `gcp_region` | GCP region for all resources | `string` | `"us-central1"` | no |
| `project` | GCP project ID | `string` | `""` (uses provider default) | no |
| `environment` | Environment name for labeling | `string` | `"dev"` | no |
| `network_name` | Name for the VPC network | `string` | `"network-${var.environment}"` | no |
| `vpc_cidr` | CIDR block for the VPC network (auto-mode ignored) | `string` | `"10.0.0.0/16"` | no |
| `private_subnet_cidrs` | CIDR blocks for private subnets | `list(string)` | `["10.0.1.0/24", "10.0.2.0/24"]` | no |
| `public_subnet_cidrs` | CIDR blocks for public subnets | `list(string)` | `["10.0.101.0/24", "10.0.102.0/24"]` | no |
| `instance_count` | Number of Compute instances to create | `number` | `2` | no |
| `machine_type` | Machine type for instances | `string` | `"e2-micro"` | no |
| `image_family` | OS image family (e.g., debian-12, ubuntu-2204-lts) | `string` | `"debian-12"` | no |
| `admin_username` | Admin username for instances | `string` | `"gcpuser"` | no |
| `ssh_keys` | Map of username => SSH public key | `map(string)` | `{}` | no |
| `assign_external_ip` | Whether public subnets get external IPs | `bool` | `true` | no |
| `ssh_cidr_blocks` | CIDR blocks allowed to SSH | `list(string)` | `["0.0.0.0/0"]` | no |
| `default_labels` | Default labels for all resources | `map(string)` | see below | no |

### Default Labels

The module applies these labels to all resources that support labeling:

```hcl
{
  project     = "tf-module-generator-example"
  managed-by  = "Terraform"
  repo        = "https://github.com/example/repo"
  environment = var.environment
}
```

## Outputs

| Name | Description |
|------|-------------|
| `network_id` | ID of the VPC network |
| `network_name` | Name of the VPC network |
| `subnet_ids` | IDs of all subnets |
| `subnet_names` | Names of all subnets |
| `instance_ids` | IDs of the Compute instances |
| `instance_private_ips` | Private IP addresses of the instances |
| `instance_public_ips` | Public IP addresses (if assigned) |
| `firewall_rule_ids` | IDs of the firewall rules |

## Security Considerations

- **SSH Access**: By default, SSH is allowed from `0.0.0.0/0`. Change `ssh_cidr_blocks` to restrict access.
- **External IPs**: Instances in public subnets may have external IPs if `assign_external_ip = true`. This increases exposure. Use firewall rules to restrict inbound traffic.
- **OS Login**: Consider enabling OS Login for better SSH key management.
- **Service Accounts**: Assign minimal IAM roles to the service account used by instances.

## Cost Estimate (as of 2025, us-central1)

| Resource | Monthly Cost |
|----------|--------------|
| e2-micro instance (per instance) | ~$4.50 (if sustained use) |
| External IP (static) | ~$7.15 per IP |
| VPC Network | Free |
| Subnets | Free |
| Firewall Rules | Free |
| Cloud NAT (if used) | ~$23.04 per gateway + data |

**Total (2 instances + 2 external IPs)**: ~$22/month

## Clean Up

```bash
terraform destroy
```

## Generated By

This example was automatically generated by the `tf-module-generator` skill. Original infrastructure was discovered in an existing GCP project and converted to reusable Terraform code.

## License

MIT - see LICENSE for details.


path: package.json
content: {
  "name": "tf-module-generator",
  "version": "1.0.1",
  "description": "OpenClaw skill that automatically generates Terraform modules from existing cloud infrastructure resources with intelligent resource detection and best-practice code formatting.",
  "main": "scripts/main.js",
  "bin": {
    "tf-module-generator": "./scripts/main.js"
  },
  "scripts": {
    "test": "node scripts/validator.js --test",
    "lint": "eslint scripts/"
  },
  "keywords": [
    "openclaw",
    "terraform",
    "cloud",
    "aws",
    "azure",
    "gcp",
    "infrastructure",
    "iac"
  ],
  "author": "OpenClaw Community",
  "license": "MIT",
  "dependencies": {
    "aws-sdk": "^2.1505.0",
    "@azure/arm-resources": "^4.0.0",
    "@azure/identity": "^3.0.0",
    "googleapis": "^116.0.0",
    "commander": "^11.0.0",
    "chalk": "^4.1.2",
    "inquirer": "^8.2.5",
    "fs-extra": "^11.0.0",
    "ejs": "^3.1.9"
  },
  "devDependencies": {
    "eslint": "^8.45.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/openclaw/skill-tf-module-generator"
  }
}

path: references/plan.md
content: # Acceptance Test Plan for Terraform Module Generator Skill

## Overview
This document defines the acceptance criteria for the `tf-module-generator` skill. The skill must meet all criteria to be considered complete.

## Functional Acceptance Criteria

### CLI and Invocation
1. The CLI (`scripts/main.js`) must be executable with Node.js and accept the following command-line arguments:
   - `--provider` or `-p`: Cloud provider (aws, azure, gcp) – required.
   - `--resources` or `-r`: Comma-separated list of resource types to include – required.
   - `--output` or `-o`: Output directory path for the generated module – required.
   - `--config` or `-c`: Optional path to a configuration file for provider credentials.
2. The CLI must validate that required arguments are present and print usage instructions if missing.
3. The CLI must exit with code 0 on success and non-zero on failure, with error messages printed to stderr.

### Cloud Resource Scanning
4. For AWS:
   - `scripts/aws-scanner.js` must use the AWS SDK to query existing resources.
   - Supported resource types: `ec2`, `rds`, `s3`, `vpc`.
   - For each selected resource type, the scanner must return an array of resource objects containing all relevant attributes required for Terraform configuration.
5. For Azure:
   - `scripts/azure-scanner.js` must use the Azure SDK.
   - Supported resource types: `compute`, `network`, `storage`.
6. For GCP:
   - `scripts/gcp-scanner.js` must use the GCP SDK.
   - Supported resource types: `compute`, `networking`, `sql`.
7. Scanners must handle authentication via standard provider SDK credential chains (environment variables, shared credentials files, etc.).
8. Scanners must filter resources based on the selected types and return only those resources.

### Code Generation
9. `scripts/terraform-generator.js` must accept the scanner output and generate a Terraform module in the output directory.
10. The module must contain the following files:
    - `main.tf`: Holds resource blocks generated from the appropriate templates in `references/templates/<provider>/`. Each resource block must reference input variables for all configurable properties.
    - `variables.tf`: Defines an input variable for each configurable property used in the resources. Variables must include appropriate type constraints (string, number, bool, list, map) and descriptions. Sensitive variables must set `sensitive = true`.
    - `outputs.tf`: Defines outputs for key attributes of each resource (e.g., id, ARN, IP addresses). Outputs must have descriptions.
    - `versions.tf`: Specifies `required_version` for Terraform (from `assets/terraform-version`) and `required_providers` with source and version constraints for the selected provider.
    - `README.md`: Documents the module, including:
      - Description of what the module creates.
      - List of input variables with descriptions, types, and default values where applicable.
      - List of outputs.
      - Example usage snippet showing how to call the module.
11. If multiple resources of the same type are detected, the generator must create multiple resource blocks with distinct names (e.g., `aws_instance.this[0]`, `aws_instance.this[1]`) or use `for_each`/`count` appropriately to allow managing all discovered resources.
12. The generator must ensure that all variable names are valid Terraform identifiers and that references in resource blocks match the variable names.

### Validation
13. `scripts/validator.js` must perform the following checks automatically after generation:
    - Terraform syntax validation (e.g., using `terraform fmt -check` or an HCL parser).
    - Provider version compatibility check (ensure provider versions in `versions.tf` are valid).
    - Basic best practices: no hardcoded secrets, all external values passed via variables; proper use of `sensitive` flag; resource names follow conventions.
14. If validation fails, the CLI must stop and report errors without leaving incomplete output.

### Agent Integration
15. The skill must be callable via OpenClaw agent sessions using `sessions_spawn` with a task that invokes the CLI (e.g., `task="generate-module", arguments={...}`).
16. The agent must be able to retrieve the generated files and deliver them to the user (e.g., as a ZIP archive or by writing to a shared workspace).
17. The agent must translate user-friendly requests (e.g., "Generate a Terraform module for my AWS EC2 instances") into the appropriate CLI arguments.

### Examples and Documentation
18. The example modules under `assets/examples/` (AWS, Azure, GCP) must be valid, ready-to-use Terraform modules that demonstrate the structure produced by the generator.
19. Each example must include the same file set (`main.tf`, `variables.tf`, `outputs.tf`, `versions.tf`, `README.md`) and demonstrate typical resource parameters.
20. The `assets/terraform-version` file must contain the exact Terraform version string that the generated modules will require (e.g., `>= 1.0.0`).

### Data and Templates
21. `references/providers.json` must contain schema mappings for all supported resource types, including:
    - Terraform resource type name.
    - List of arguments (with type, optional/required, description).
    - List of attributes that can be exported as outputs.
22. Template files in `references/templates/<provider>/` (e.g., `ec2.tf.tpl`) must contain HCL snippets that can be inserted into `main.tf`. They must use variable interpolation (e.g., `var.<name>`) for all values that should be parameterized.
23. Templates must be valid HCL fragments and must not contain syntax errors.

### Error Handling and Edge Cases
24. If the scanner finds no resources of the requested types, the generator must create a module with placeholder outputs and a warning in the README, or fail gracefully with a clear message (behavior to be decided, but must be consistent).
25. If provider credentials are missing or invalid, the scanner must exit with an informative error.
26. The skill must handle large numbers of resources without performance degradation (batch processing if necessary).
27. The generated module must be idempotent: running the generator multiple times on the same infrastructure should produce functionally identical code (ignoring non-deterministic ordering).

## Non-Functional Acceptance Criteria
- The codebase must follow consistent style (e.g., ESLint for JavaScript).
- All scripts must have error handling and logging.
- The skill must run on standard Node.js (version specified in `package.json`).
- The memory footprint should not exceed 500MB during normal operation.


path: references/providers.json
content: {
  "providers": {
    "aws": {
      "name": "AWS",
      "terraformProvider": "hashicorp/aws",
      "defaultVersion": "~> 5.0",
      "regions": [
        "us-east-1", "us-east-2", "us-west-1", "us-west-2",
        "eu-west-1", "eu-west-2", "eu-west-3", "eu-central-1",
        "ap-southeast-1", "ap-southeast-2", "ap-northeast-1"
      ],
      "resourceTypes": {
        "ec2": {
          "terraformType": "aws_instance",
          "apiService": "EC2",
          "discoveryMethod": "describeInstances",
          "primaryKey": "InstanceId",
          "attributes": {
            "id": "InstanceId",
            "ami": "ImageId",
            "instanceType": "InstanceType",
            "availabilityZone": "Placement.AvailabilityZone",
            "subnetId": "SubnetId",
            "vpcSecurityGroupIds": "SecurityGroups",
            "privateIp": "PrivateIpAddress",
            "publicIp": "PublicIpAddress",
            "keyName": "KeyName",
            "tags": "Tags"
          },
          "variableTypes": {
            "instance_type": "string",
            "ami_id": "string",
            "key_name": "string",
            "vpc_security_group_ids": "list(string)",
            "subnet_id": "string",
            "tags": "map(string)"
          },
          "requiredArgs": ["ami", "instance_type"],
          "supportsCount": true
        },
        "vpc": {
          "terraformType": "aws_vpc",
          "apiService": "EC2",
          "discoveryMethod": "describeVpcs",
          "primaryKey": "VpcId",
          "attributes": {
            "id": "VpcId",
            "cidrBlock": "CidrBlock",
            "instanceTenancy": "InstanceTenancy",
            "isDefault": "IsDefault",
            "tags": "Tags"
          },
          "variableTypes": {
            "cidr_block": "string",
            "instance_tenancy": "string",
            "enableDnsHostnames": "bool",
            "enableDnsSupport": "bool",
            "tags": "map(string)"
          },
          "requiredArgs": ["cidr_block"]
        },
        "subnet": {
          "terraformType": "aws_subnet",
          "apiService": "EC2",
          "discoveryMethod": "describeSubnets",
          "primaryKey": "SubnetId",
          "attributes": {
            "id": "SubnetId",
            "vpcId": "VpcId",
            "cidrBlock": "CidrBlock",
            "availabilityZone": "AvailabilityZone",
            "mapPublicIpOnLaunch": "MapPublicIpOnLaunch",
            "tags": "Tags"
          },
          "variableTypes": {
            "vpc_id": "string",
            "cidr_block": "string",
            "availability_zone": "string",
            "map_public_ip_on_launch": "bool",
            "tags": "map(string)"
          },
          "requiredArgs": ["vpc_id", "cidr_block"]
        },
        "securityGroup": {
          "terraformType": "aws_security_group",
          "apiService": "EC2",
          "discoveryMethod": "describeSecurityGroups",
          "primaryKey": "GroupId",
          "attributes": {
            "id": "GroupId",
            "vpcId": "VpcId",
            "groupName": "GroupName",
            "description": "Description",
            "ingress": "IpPermissions",
            "egress": "IpPermissionsEgress",
            "tags": "Tags"
          },
          "variableTypes": {
            "name": "string",
            "description": "string",
            "vpc_id": "string",
            "ingress": "list(object)",
            "egress": "list(object)",
            "tags": "map(string)"
          },
          "requiredArgs": ["name", "vpc_id"]
        },
        "rds": {
          "terraformType": "aws_db_instance",
          "apiService": "RDS",
          "discoveryMethod": "describeDBInstances",
          "primaryKey": "DBInstanceIdentifier",
          "attributes": {
            "id": "DBInstanceIdentifier",
            "engine": "Engine",
            "engineVersion": "EngineVersion",
            "instanceClass": "DBInstanceClass",
            "allocatedStorage": "AllocatedStorage",
            "storageType": "StorageType",
            "availabilityZone": "AvailabilityZone",
            "dbSubnetGroupId": "DBSubnetGroup.DBSubnetGroupId",
            "vpcSecurityGroups": "VpcSecurityGroups[].VpcSecurityGroupId",
            "multiAz": "MultiAZ",
            "publiclyAccessible": "PubliclyAccessible",
            "tags": "TagList"
          },
          "variableTypes": {
            "identifier": "string",
            "engine": "string",
            "engine_version": "string",
            "instance_class": "string",
            "allocated_storage": "number",
            "storage_type": "string",
            "username": "string",
            "password": "string",
            "db_subnet_group_id": "string",
            "vpc_security_group_ids": "list(string)",
            "multi_az": "bool",
            "skip_final_snapshot": "bool"
          },
          "requiredArgs": ["identifier", "engine", "instance_class", "username", "password"]
        },
        "s3": {
          "terraformType": "aws_s3_bucket",
          "apiService": "S3",
          "discoveryMethod": "listBuckets",
          "primaryKey": "Name",
          "attributes": {
            "id": "Name",
            "region": "Region",
            "creationDate": "CreationDate"
          },
          "variableTypes": {
            "bucket": "string",
            "acl": "string",
            "versioning": "object",
            "serverSideEncryption": "object",
            "tags": "map(string)"
          },
          "requiredArgs": ["bucket"],
          "note": "S3 bucket configurations require additional API calls for full property discovery"
        },
        "elasticLoadBalancer": {
          "terraformType": "aws_lb",
          "apiService": "ElasticLoadBalancingV2",
          "discoveryMethod": "describeLoadBalancers",
          "primaryKey": "LoadBalancerArn",
          "attributes": {
            "id": "LoadBalancerArn",
            "name": "LoadBalancerName",
            "dnsName": "DNSName",
            "scheme": "Scheme",
            "type": "Type",
            "vpcId": "VpcId",
            "subnetIds": "AvailabilityZones[].SubnetId",
            "securityGroups": "SecurityGroups",
            "tags": "Tags"
          },
          "variableTypes": {
            "name": "string",
            "internal": "bool",
            "loadBalancerType": "string",
            "vpc_id": "string",
            "subnet_ids": "list(string)",
            "security_groups": "list(string)",
            "enableDeletionProtection": "bool",
            "tags": "map(string)"
          },
          "requiredArgs": ["name", "subnet_ids", "security_groups"]
        },
        "ebs": {
          "terraformType": "aws_ebs_volume",
          "apiService": "EC2",
          "discoveryMethod": "describeVolumes",
          "primaryKey": "VolumeId",
          "attributes": {
            "id": "VolumeId",
            "size": "Size",
            "volumeType": "VolumeType",
            "iops": "Iops",
            "throughput": "Throughput",
            "availabilityZone": "AvailabilityZone",
            "encrypted": "Encrypted",
            "snapshotId": "SnapshotId",
            "tags": "Tags"
          },
          "variableTypes": {
            "availability_zone": "string",
            "size": "number",
            "volume_type": "string",
            "iops": "number",
            "throughput": "number",
            "encrypted": "bool",
            "snapshot_id": "string",
            "tags": "map(string)"
          },
          "requiredArgs": ["availability_zone", "size"]
        },
        "iamRole": {
          "terraformType": "aws_iam_role",
          "apiService": "IAM",
          "discoveryMethod": "listRoles",
          "primaryKey": "RoleName",
          "attributes": {
            "id": "RoleId",
            "name": "RoleName",
            "arn": "Arn",
            "path": "Path",
            "description": "Description",
            "assumeRolePolicyDocument": "AssumeRolePolicyDocument",
            "maxSessionDuration": "MaxSessionDuration",
            "tags": "Tags"
          },
          "variableTypes": {
            "name": "string",
            "path": "string",
            "description": "string",
            "assume_role_policy": "string",
            "max_session_duration": "number",
            "tags": "map(string)"
          },
          "requiredArgs": ["name", "assume_role_policy"]
        }
      }
    },
    "azure": {
      "name": "Azure",
      "terraformProvider": "hashicorp/azurerm",
      "defaultVersion": "~> 4.0",
      "subscriptionScoped": true,
      "resourceTypes": {
        "virtualMachine": {
          "terraformType": "azurerm_linux_virtual_machine",
          "apiService": "Microsoft.Compute",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "resourceGroupName": "resourceGroupName",
            "size": "hardwareProfile.vmSize",
            "adminUsername": "osProfile.adminUsername",
            "networkInterfaceIds": "networkProfile.networkInterfaces[].id",
            "osDisk": "storageProfile.osDisk",
            "imageReference": "storageProfile.imageReference",
            "tags": "tags"
          },
          "variableTypes": {
            "virtual_machine_name": "string",
            "resource_group_name": "string",
            "location": "string",
            "size": "string",
            "admin_username": "string",
            "admin_password": "string",
            "network_interface_ids": "list(string)",
            "os_disk": "object",
            "image_reference": "object",
            "tags": "map(string)"
          },
          "requiredArgs": ["virtual_machine_name", "resource_group_name", "location", "size", "admin_username", "admin_password", "network_interface_ids", "os_disk", "image_reference"],
          "supportsLinux": true,
          "supportsWindows": true
        },
        "virtualNetwork": {
          "terraformType": "azurerm_virtual_network",
          "apiService": "Microsoft.Network",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "resourceGroupName": "resourceGroupName",
            "addressSpace": "addressSpace",
            "dnsServers": "dhcpOptions.dnsServers",
            "subnets": "subnets",
            "tags": "tags"
          },
          "variableTypes": {
            "virtual_network_name": "string",
            "resource_group_name": "string",
            "location": "string",
            "address_space": "list(string)",
            "dns_servers": "list(string)",
            "subnets": "list(object)",
            "tags": "map(string)"
          },
          "requiredArgs": ["virtual_network_name", "resource_group_name", "location", "address_space"]
        },
        "subnet": {
          "terraformType": "azurerm_subnet",
          "apiService": "Microsoft.Network",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "resourceGroupName": "resourceGroupName",
            "virtualNetworkName": "virtualNetworkName",
            "addressPrefixes": "addressPrefixes",
            "serviceEndpoints": "serviceEndpoints",
            "delegations": "delegations",
            "privateEndpointNetworkPolicies": "privateEndpointNetworkPolicies"
          },
          "variableTypes": {
            "subnet_name": "string",
            "resource_group_name": "string",
            "virtual_network_name": "string",
            "address_prefixes": "list(string)",
            "service_endpoints": "list(string)",
            "delegations": "list(object)",
            "private_endpoint_network_policies": "string"
          },
          "requiredArgs": ["subnet_name", "resource_group_name", "virtual_network_name", "address_prefixes"]
        },
        "networkSecurityGroup": {
          "terraformType": "azurerm_network_security_group",
          "apiService": "Microsoft.Network",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "resourceGroupName": "resourceGroupName",
            "securityRules": "securityRules",
            "tags": "tags"
          },
          "variableTypes": {
            "network_security_group_name": "string",
            "resource_group_name": "string",
            "location": "string",
            "security_rules": "list(object)",
            "tags": "map(string)"
          },
          "requiredArgs": ["network_security_group_name", "resource_group_name", "location"]
        },
        "sqlDatabase": {
          "terraformType": "azurerm_mssql_database",
          "apiService": "Microsoft.Sql",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "resourceGroupName": "resourceGroupName",
            "serverName": "serverName",
            "collation": "collation",
            "maxSizeBytes": "maxSizeBytes",
            "readScale": "readScale",
            "zoneRedundant": "zoneRedundant",
            "tags": "tags"
          },
          "variableTypes": {
            "database_name": "string",
            "resource_group_name": "string",
            "server_name": "string",
            "collation": "string",
            "max_size_gb": "number",
            "read_scale": "bool",
            "zone_redundant": "bool",
            "tags": "map(string)"
          },
          "requiredArgs": ["database_name", "resource_group_name", "server_name"]
        },
        "storageAccount": {
          "terraformType": "azurerm_storage_account",
          "apiService": "Microsoft.Storage",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "resourceGroupName": "resourceGroupName",
            "accountTier": "sku.tier",
            "accountReplicationType": "sku.name",
            "accessTier": "accessTier",
            "isHttpsTrafficOnly": "enableHttpsTrafficOnly",
            "blobProperties": "blobProperties",
            "tags": "tags"
          },
          "variableTypes": {
            "storage_account_name": "string",
            "resource_group_name": "string",
            "location": "string",
            "account_tier": "string",
            "account_replication_type": "string",
            "access_tier": "string",
            "enable_https_traffic_only": "bool",
            "tags": "map(string)"
          },
          "requiredArgs": ["storage_account_name", "resource_group_name", "location", "account_tier", "account_replication_type"],
          "nameConstraints": "Must be globally unique, 3-24 characters, lowercase alphanumeric"
        },
        "loadBalancer": {
          "terraformType": "azurerm_lb",
          "apiService": "Microsoft.Network",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "resourceGroupName": "resourceGroupName",
            "sku": "sku.name",
            "frontendIpConfigurations": "frontendIpConfigurations",
            "backendAddressPools": "backendAddressPools",
            "loadBalancingRules": "loadBalancingRules",
            "probes": "probes",
            "tags": "tags"
          },
          "variableTypes": {
            "load_balancer_name": "string",
            "resource_group_name": "string",
            "location": "string",
            "sku": "string",
            "frontend_ip_configuration": "object",
            "backend_address_pools": "list(object)",
            "load_balancing_rules": "list(object)",
            "health_probes": "list(object)",
            "tags": "map(string)"
          },
          "requiredArgs": ["load_balancer_name", "resource_group_name", "location", "sku", "frontend_ip_configuration", "backend_address_pools"]
        }
      }
    },
    "gcp": {
      "name": "Google Cloud Platform",
      "terraformProvider": "hashicorp/google",
      "defaultVersion": "~> 5.0",
      "projectScoped": true,
      "resourceTypes": {
        "computeInstance": {
          "terraformType": "google_compute_instance",
          "apiService": "compute",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "zone": "zone",
            "machineType": "machineType",
            "canIpForward": "canIpForward",
            "networkInterfaces": "networkInterfaces",
            "disks": "disks",
            "metadata": "metadata",
            "tags": "tags"
          },
          "variableTypes": {
            "name": "string",
            "zone": "string",
            "machine_type": "string",
            "can_ip_forward": "bool",
            "network_interfaces": "list(object)",
            "boot_disk": "object",
            "metadata": "map(string)",
            "tags": "list(string)"
          },
          "requiredArgs": ["name", "zone", "machine_type", "network_interfaces", "boot_disk"]
        },
        "vpcNetwork": {
          "terraformType": "google_compute_network",
          "apiService": "compute",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "autoCreateSubnetworks": "autoCreateSubnetworks",
            "routingConfig": "routingConfig",
            "description": "description",
            "mtu": "mtu"
          },
          "variableTypes": {
            "name": "string",
            "auto_create_subnetworks": "bool",
            "routing_mode": "string",
            "description": "string",
            "mtu": "number"
          },
          "requiredArgs": ["name"]
        },
        "subnetwork": {
          "terraformType": "google_compute_subnetwork",
          "apiService": "compute",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "region": "region",
            "network": "network",
            "ipCidrRange": "ipCidrRange",
            "privateIpGoogleAccess": "privateIpGoogleAccess",
            "secondaryIpRanges": "secondaryIpRanges",
            "stackType": "stackType"
          },
          "variableTypes": {
            "name": "string",
            "region": "string",
            "network": "string",
            "ip_cidr_range": "string",
            "private_ip_google_access": "bool",
            "secondary_ip_ranges": "list(object)",
            "stack_type": "string"
          },
          "requiredArgs": ["name", "region", "network", "ip_cidr_range"]
        },
        "firewall": {
          "terraformType": "google_compute_firewall",
          "apiService": "compute",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "network": "network",
            "direction": "direction",
            "priority": "priority",
            "sourceRanges": "sourceRanges",
            "destinationRanges": "destinationRanges",
            "allowed": "allowed",
            "denied": "denied",
            "sourceTags": "sourceTags",
            "targetTags": "targetTags"
          },
          "variableTypes": {
            "name": "string",
            "network": "string",
            "direction": "string",
            "priority": "number",
            "source_ranges": "list(string)",
            "destination_ranges": "list(string)",
            "allowed": "list(object)",
            "denied": "list(object)",
            "source_tags": "list(string)",
            "target_tags": "list(string)"
          },
          "requiredArgs": ["name", "network", "direction", "priority", "allowed"]
        },
        "cloudSql": {
          "terraformType": "google_sql_database_instance",
          "apiService": "sqladmin",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "region": "region",
            "settings": "settings",
            "databaseVersion": "databaseVersion",
            "state": "state",
            "connectionName": "connectionName",
            "serverCaCert": "serverCaCert"
          },
          "variableTypes": {
            "name": "string",
            "region": "string",
            "database_version": "string",
            "tier": "string",
            "settings": "object",
            "backup_configuration": "object",
            "ip_configuration": "object",
            "deletion_protection": "bool"
          },
          "requiredArgs": ["name", "region", "settings", "database_version"]
        },
        "cloudStorage": {
          "terraformType": "google_storage_bucket",
          "apiService": "storage",
          "discoveryMethod": "list",
          "primaryKey": "id",
          "attributes": {
            "id": "id",
            "name": "name",
            "location": "location",
            "storageClass": "storageClass",
            "cors": "cors",
            "versioning": "versioning",
            "encryption": "encryption",
            "labels": "labels"
          },
          "variableTypes": {
            "name": "string",
            "location": "string",
            "storage_class": "string",
            "uniform_bucket_level_access": "bool",
            "versioning": "object",
            "encryption": "object",
            "cors": "list(object)",
            "labels": "map(string)"
          },
          "requiredArgs": ["name", "location"]
        },
        "cloudDisk": {
          "terraformType": "google_compute_disk",
          "apiService": "compute",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "zone": "zone",
            "sizeGb": "sizeGb",
            "type": "type",
            "labels": "labels",
            "sourceSnapshot": "sourceSnapshot",
            "sourceImage": "sourceImage"
          },
          "variableTypes": {
            "name": "string",
            "zone": "string",
            "size": "number",
            "type": "string",
            "source_image": "string",
            "source_snapshot": "string",
            "labels": "map(string)"
          },
          "requiredArgs": ["name", "zone", "size"]
        },
        "loadBalancer": {
          "terraformType": "google_compute_global_address",
          "apiService": "compute",
          "discoveryMethod": "list",
          "primaryKey": "selfLink",
          "attributes": {
            "id": "id",
            "name": "name",
            "region": "region",
            "ipVersion": "ipVersion",
            "addressType": "addressType",
            "ipAddress": "address"
          },
          "variableTypes": {
            "name": "string",
            "region": "string",
            "ip_version": "string",
            "address_type": "string"
          },
          "requiredArgs": ["name", "region"],
          "note": "Full load balancer includes forwarding rule, target proxy, backend service, URL map. This generates only the IP address resource."
        }
      }
    }
  },
  "metadata": {
    "version": "1.0.0",
    "lastUpdated": "2025-02-25",
    "supportedProviders": ["aws", "azure", "gcp"],
    "totalResourceTypes": 25,
    "schemaUrl": "https://registry.terraform.io/providers/hashicorp/aws/latest/docs",
    "notes": "This schema defines the mapping between cloud provider APIs and Terraform resource types. Each entry includes discovery methods, attribute mappings, and variable type hints for code generation."
  }
}


path: scripts/aws-scanner.js
content: #!/usr/bin/env node
/**
 * AWS Resource Scanner
 *
 * Usage: node aws-scanner.js [--region <region>] [--output <format>] <resource-type>...
 *
 * Example:
 *   node aws-scanner.js --region us-east-1 ec2 vpc subnet --output json
 *   node aws-scanner.js ec2
 *
 * Supported resource types:
 *   ec2        - EC2 instances
 *   vpc        - Virtual Private Clouds
 *   subnet     - Subnets
 *   rds        - RDS databases
 *   s3         - S3 buckets
 *   lambda     - Lambda functions
 *   sg         - Security groups
 *
 * Outputs JSON array to stdout with resource metadata:
 * [
 *   {
 *     "type": "aws_instance",
 *     "id": "i-1234567890abcdef0",
 *     "name": "my-server",
 *     "region": "us-east-1",
 *     "attributes": { ... },
 *     "tags": { ... }
 *   }
 * ]
 */

const { EC2Client, DescribeInstancesCommand, DescribeVpcsCommand, DescribeSubnetsCommand,
        DescribeSecurityGroupsCommand, DescribeRouteTablesCommand, DescribeInternetGatewaysCommand,
        DescribeNatGatewaysCommand, DescribeVolumesCommand } = require('@aws-sdk/client-ec2');
const { S3Client, ListBucketsCommand } = require('@aws-sdk/client-s3');
const { RDSClient, DescribeDBInstancesCommand } = require('@aws-sdk/client-rds');
const { LambdaClient, ListFunctionsCommand } = require('@aws-sdk/client-lambda');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Parse arguments
const argv = yargs(hideBin(process.argv))
  .option('region', {
    alias: 'r',
    type: 'string',
    description: 'AWS region to scan'
  })
  .option('output', {
    alias: 'o',
    type: 'string',
    default: 'json',
    choices: ['json', 'text']
  })
  .demandCommand(1, 'At least one resource type is required')
  .argv;

const resourceTypes = argv._;
const region = argv.region || process.env.AWS_REGION || 'us-east-1';
const outputFormat = argv.output;

/**
 * Create AWS client with region
 */
function createClient(Service) {
  return new Service({
    region,
    // Credentials from environment/chain automatically
  });
}

/**
 * Scan EC2 instances
 */
async function scanEC2() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeInstancesCommand({
        MaxResults: 1000,
        NextToken: nextToken
      });
      const resp = await client.send(cmd);

      for (const reservation of resp.Reservations || []) {
        for (const instance of reservation.Instances || []) {
          const nameTag = instance.Tags?.find(t => t.Key === 'Name');
          results.push({
            type: 'aws_instance',
            id: instance.InstanceId,
            name: nameTag?.Value || instance.InstanceId,
            region,
            attributes: {
              instance_type: instance.InstanceType,
              state: instance.State?.Name,
              image_id: instance.ImageId,
              availability_zone: instance.Placement?.AvailabilityZone,
              private_ip: instance.PrivateIpAddress,
              public_ip: instance.PublicIpAddress,
              launch_time: instance.LaunchTime
            },
            tags: instance.Tags?.reduce((acc, tag) => {
              acc[tag.Key] = tag.Value;
              return acc;
            }, {}) || {}
          });
        }
        nextToken = resp.NextToken;
      }
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning EC2: ${err.message}`);
  }

  return results;
}

/**
 * Scan VPCs
 */
async function scanVPC() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeVpcsCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const vpc of resp.Vpcs || []) {
        const nameTag = vpc.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_vpc',
          id: vpc.VpcId,
          name: nameTag?.Value || vpc.VpcId,
          region,
          attributes: {
            cidr_block: vpc.CidrBlock,
            state: vpc.State,
            is_default: vpc.IsDefault
          },
          tags: vpc.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextToken;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning VPCs: ${err.message}`);
  }

  return results;
}

/**
 * Scan Subnets
 */
async function scanSubnet() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeSubnetsCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const subnet of resp.Subnets || []) {
        const nameTag = subnet.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_subnet',
          id: subnet.SubnetId,
          name: nameTag?.Value || subnet.SubnetId,
          region,
          attributes: {
            vpc_id: subnet.VpcId,
            cidr_block: subnet.CidrBlock,
            availability_zone: subnet.AvailabilityZone,
            state: subnet.State,
            map_public_ip_on_launch: subnet.MapPublicIpOnLaunch
          },
          tags: subnet.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextToken;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning Subnets: ${err.message}`);
  }

  return results;
}

/**
 * Scan Security Groups
 */
async function scanSecurityGroups() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeSecurityGroupsCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const sg of resp.SecurityGroups || []) {
        const nameTag = sg.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_security_group',
          id: sg.GroupId,
          name: nameTag?.Value || sg.GroupName,
          region,
          attributes: {
            description: sg.Description,
            vpc_id: sg.VpcId,
            group_name: sg.GroupName,
            ip_permissions: sg.IpPermissions,
            ip_permissions_egress: sg.IpPermissionsEgress
          },
          tags: sg.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextToken;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning Security Groups: ${err.message}`);
  }

  return results;
}

/**
 * Scan RDS instances
 */
async function scanRDS() {
  const client = createClient(RDSClient);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeDBInstancesCommand({ MaxRecords: 100, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const db of resp.DBInstances || []) {
        results.push({
          type: 'aws_db_instance',
          id: db.DBInstanceIdentifier,
          name: db.DBInstanceIdentifier,
          region,
          attributes: {
            engine: db.Engine,
            engine_version: db.EngineVersion,
            instance_class: db.DBInstanceClass,
            allocated_storage: db.AllocatedStorage,
            storage_type: db.StorageType,
            multi_az: db.MultiAZ,
            publicly_accessible: db.PubliclyAccessible,
            availability_zone: db.AvailabilityZone,
            status: db.DBInstanceStatus
          },
          tags: (await client.send(new RDSClient.ListTagsForResourceCommand({
            ResourceName: db.DBInstanceArn
          }))).TagList?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
        nextToken = resp.NextToken;
      }
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning RDS: ${err.message}`);
  }

  return results;
}

/**
 * Scan S3 buckets (global, not regional)
 */
async function scanS3() {
  const client = createClient(S3Client);
  const results = [];

  try {
    const cmd = new ListBucketsCommand({});
    const resp = await client.send(cmd);

    for (const bucket of resp.Buckets || []) {
      // Get bucket location
      let bucketRegion = 'us-east-1';
      try {
        const locResp = await client.send(new S3Client.GetBucketLocationCommand({
          Bucket: bucket.Name
        }));
        bucketRegion = locResp.LocationConstraint || 'us-east-1';
      } catch (e) {
        // Ignore location errors (permissions)
      }

      results.push({
        type: 'aws_s3_bucket',
        id: bucket.Name,
        name: bucket.Name,
        region: bucketRegion,
        attributes: {
          creation_date: bucket.CreationDate
        },
        tags: {} // Would require additional API calls per bucket
      });
    }
  } catch (err) {
    console.error(`Error scanning S3: ${err.message}`);
  }

  return results;
}

/**
 * Scan Lambda functions
 */
async function scanLambda() {
  const { LambdaClient, ListFunctionsCommand } = require('@aws-sdk/client-lambda');
  const client = createClient(LambdaClient);
  const results = [];

  try {
    let nextMarker;
    do {
      const cmd = new ListFunctionsCommand({
        MaxItems: 1000,
        Marker: nextMarker
      });
      const resp = await client.send(cmd);

      for (const fn of resp.Functions || []) {
        results.push({
          type: 'aws_lambda_function',
          id: fn.FunctionName,
          name: fn.FunctionName,
          region,
          attributes: {
            runtime: fn.Runtime,
            handler: fn.Handler,
            code_size: fn.CodeSize,
            description: fn.Description,
            timeout: fn.Timeout,
            memory_size: fn.MemorySize,
            last_modified: fn.LastModified,
            revision_id: fn.RevisionId
          },
          tags: fn.Tags || {}
        });
      }
      nextMarker = resp.NextMarker;
    } while (nextMarker);
  } catch (err) {
    console.error(`Error scanning Lambda: ${err.message}`);
  }

  return results;
}

/**
 * Scan Route Tables (additional network resource)
 */
async function scanRouteTables() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeRouteTablesCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const rt of resp.RouteTables || []) {
        const nameTag = rt.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_route_table',
          id: rt.RouteTableId,
          name: nameTag?.Value || rt.RouteTableId,
          region,
          attributes: {
            vpc_id: rt.VpcId,
            routes: rt.Routes,
            associations: rt.Associations
          },
          tags: rt.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextToken;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning Route Tables: ${err.message}`);
  }

  return results;
}

/**
 * Scan Internet Gateways
 */
async function scanInternetGateways() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeInternetGatewaysCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const igw of resp.InternetGateways || []) {
        const nameTag = igw.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_internet_gateway',
          id: igw.InternetGatewayId,
          name: nameTag?.Value || igw.InternetGatewayId,
          region,
          attributes: {
            attachments: igw.Attachments
          },
          tags: igw.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextToken;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning Internet Gateways: ${err.message}`);
  }

  return results;
}

/**
 * Scan NAT Gateways
 */
async function scanNatGateways() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeNatGatewaysCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const nat of resp.NatGateways || []) {
        const nameTag = nat.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_nat_gateway',
          id: nat.NatGatewayId,
          name: nameTag?.Value || nat.NatGatewayId,
          region,
          attributes: {
            subnet_id: nat.SubnetId,
            connectivity_type: nat.ConnectivityType,
            state: nat.State,
            private_ip: nat.PrivateIp,
            public_ips: nat.PublicIpAddresses
          },
          tags: nat.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextMarker;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning NAT Gateways: ${err.message}`);
  }

  return results;
}

/**
 * Scan EBS volumes
 */
async function scanVolumes() {
  const client = createClient(EC2Client);
  const results = [];

  try {
    let nextToken;
    do {
      const cmd = new DescribeVolumesCommand({ MaxResults: 1000, NextToken: nextToken });
      const resp = await client.send(cmd);

      for (const vol of resp.Volumes || []) {
        const nameTag = vol.Tags?.find(t => t.Key === 'Name');
        results.push({
          type: 'aws_ebs_volume',
          id: vol.VolumeId,
          name: nameTag?.Value || vol.VolumeId,
          region,
          attributes: {
            size: vol.Size,
            volume_type: vol.VolumeType,
            state: vol.State,
            iops: vol.Iops,
            encrypted: vol.Encrypted,
            snapshot_id: vol.SnapshotId,
            availability_zone: vol.AvailabilityZone
          },
          tags: vol.Tags?.reduce((acc, tag) => {
            acc[tag.Key] = tag.Value;
            return acc;
          }, {}) || {}
        });
      }
      nextToken = resp.NextToken;
    } while (nextToken);
  } catch (err) {
    console.error(`Error scanning EBS Volumes: ${err.message}`);
  }

  return results;
}

/**
 * Resource scanner dispatcher
 */
const scanners = {
  ec2: scanEC2,
  vpc: scanVPC,
  subnet: scanSubnet,
  sg: scanSecurityGroups,
  security_group: scanSecurityGroups,
  rds: scanRDS,
  s3: scanS3,
  lambda: scanLambda,
  route_table: scanRouteTables,
  rt: scanRouteTables,
  igw: scanInternetGateways,
  internet_gateway: scanInternetGateways,
  nat: scanNatGateways,
  nat_gateway: scanNatGateways,
  volume: scanVolumes,
  ebs: scanVolumes
};

/**
 * Main entry
 */
async function main() {
  console.error(`AWS Resource Scanner (region: ${region})`);

  const allResources = [];

  for (const resourceType of resourceTypes) {
    const scanner = scanners[resourceType];
    if (!scanner) {
      console.error(`Warning: Unknown resource type '${resourceType}'. Skipping.`);
      continue;
    }

    console.error(`Scanning ${resourceType}...`);
    try {
      const resources = await scanner();
      allResources.push(...resources);
    } catch (err) {
      console.error(`Failed to scan ${resourceType}: ${err.message}`);
    }
  }

  // Output results
  if (outputFormat === 'json') {
    console.log(JSON.stringify(allResources, null, 2));
  } else {
    console.log(allResources.map(r =>
      `[${r.type}] ${r.name} (${r.id}) in ${r.region}`
    ).join('\\n'));
  }

  process.exit(0);
}

if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = { scanEC2, scanVPC, scanSubnet, scanSecurityGroups, scanRDS, scanS3, scanLambda };


path: scripts/azure-scanner.js
content: #!/usr/bin/env node
/**
 * Azure Resource Scanner
 *
 * Usage: node azure-scanner.js [--subscription <id>] [--location <region>] [--output <format>] <resource-type>...
 *
 * Example:
 *   node azure-scanner.js --location eastus vm vnet subnet --output json
 *   node azure-scanner.js vm
 *
 * Supported resource types:
 *   vm        - Virtual Machines
 *   vnet      - Virtual Networks
 *   subnet    - Subnets
 *   nic       - Network Interfaces
 *   pip       - Public IP Addresses
 *   disk      - Managed Disks
 *   nsg       - Network Security Groups
 *   lb        - Load Balancers
 *   appsvc    - App Services
 *
 * Outputs JSON array to stdout with resource metadata:
 * [
 *   {
 *     "type": "azurerm_virtual_machine",
 *     "id": "/subscriptions/.../resourceGroups/.../providers/Microsoft.Compute/virtualMachines/myvm",
 *     "name": "myvm",
 *     "resource_group": "my-rg",
 *     "location": "eastus",
 *     "attributes": { ... },
 *     "tags": { ... }
 *   }
 * ]
 */

const { DefaultAzureCredential } = require('@azure/identity');
const {
  ComputeManagementClient,
  VirtualMachines
} = require('@azure/arm-compute');
const {
  NetworkManagementClient,
  VirtualNetworks,
  Subnets,
  NetworkInterfaces,
  PublicIPAddresses,
  NetworkSecurityGroups
} = require('@azure/arm-network');
const {
  ResourceManagementClient,
  Resources
} = require('@azure/arm-resources');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Parse arguments
const argv = yargs(hideBin(process.argv))
  .option('subscription', {
    alias: 's',
    type: 'string',
    description: 'Azure subscription ID',
    default: process.env.AZURE_SUBSCRIPTION_ID
  })
  .option('location', {
    alias: 'l',
    type: 'string',
    description: 'Azure location/region to scan'
  })
  .option('output', {
    alias: 'o',
    type: 'string',
    default: 'json',
    choices: ['json', 'text']
  })
  .demandCommand(1, 'At least one resource type is required')
  .argv;

const resourceTypes = argv._;
const subscriptionId = argv.subscription;
const location = argv.location || process.env.AZURE_LOCATION;
const outputFormat = argv.output;

/**
 * Create authenticated Azure clients
 */
let computeClient, networkClient, resourceClient;

async function initClients() {
  const credential = new DefaultAzureCredential();

  computeClient = new ComputeManagementClient(credential, subscriptionId);
  networkClient = new NetworkManagementClient(credential, subscriptionId);
  resourceClient = new ResourceManagementClient(credential, subscriptionId);
}

/**
 * List all resource groups (optionally filtered by location)
 */
async function listResourceGroups() {
  const rgs = [];
  const iterator = resourceClient.resourceGroups.list();
  for await (const rg of iterator) {
    if (!location || rg.location === location) {
      rgs.push(rg);
    }
  }
  return rgs;
}

/**
 * Scan Virtual Machines
 */
async function scanVM() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = computeClient.virtualMachines.list(rg.name);
      for await (const vm of iterator) {
        results.push({
          type: 'azurerm_virtual_machine',
          id: vm.id,
          name: vm.name,
          resource_group: rg.name,
          location: vm.location,
          attributes: {
            vm_size: vm.hardwareProfile?.vmSize,
            os_type: vm.storageProfile?.osDisk?.osType,
            admin_username: vm.osProfile?.adminUsername,
            computer_name: vm.osProfile?.computerName,
            provisioning_state: vm.provisioningState
          },
          tags: vm.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning VMs: ${err.message}`);
  }

  return results;
}

/**
 * Scan Virtual Networks
 */
async function scanVNet() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = networkClient.virtualNetworks.list(rg.name);
      for await (const vnet of iterator) {
        results.push({
          type: 'azurerm_virtual_network',
          id: vnet.id,
          name: vnet.name,
          resource_group: rg.name,
          location: vnet.location,
          attributes: {
            address_space: vnet.addressSpace?.addressPrefixes,
            dns_servers: vnet.dhcpOptions?.dnsServers
          },
          tags: vnet.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning VNets: ${err.message}`);
  }

  return results;
}

/**
 * Scan Subnets
 */
async function scanSubnet() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = networkClient.subnets.list(rg.name);
      for await (const subnet of iterator) {
        results.push({
          type: 'azurerm_subnet',
          id: subnet.id,
          name: subnet.name,
          resource_group: rg.name,
          location: subnet.location,
          attributes: {
            address_prefix: subnet.addressPrefix,
            security_group_id: subnet.networkSecurityGroup?.id,
            route_table_id: subnet.routeTable?.id,
            service_endpoints: subnet.serviceEndpoints
          },
          tags: subnet.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning Subnets: ${err.message}`);
  }

  return results;
}

/**
 * Scan Network Interfaces
 */
async function scanNIC() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = networkClient.networkInterfaces.list(rg.name);
      for await (const nic of iterator) {
        results.push({
          type: 'azurerm_network_interface',
          id: nic.id,
          name: nic.name,
          resource_group: rg.name,
          location: nic.location,
          attributes: {
            ip_configurations: nic.ipConfigurations?.map(ipc => ({
              name: ipc.name,
              private_ip_address: ipc.privateIPAddress,
              private_ip_allocation_method: ipc.privateIPAllocationMethod,
              public_ip_address_id: ipc.publicIPAddress?.id,
              subnet_id: ipc.subnet?.id
            })),
            enable_ip_forwarding: nic.enableIPForwarding,
            network_security_group_id: nic.networkSecurityGroup?.id
          },
          tags: nic.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning NICs: ${err.message}`);
  }

  return results;
}

/**
 * Scan Public IP Addresses
 */
async function scanPublicIP() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = networkClient.publicIPAddresses.list(rg.name);
      for await (const pip of iterator) {
        results.push({
          type: 'azurerm_public_ip',
          id: pip.id,
          name: pip.name,
          resource_group: rg.name,
          location: pip.location,
          attributes: {
            public_ip_address: pip.ipAddress,
            public_ip_allocation_method: pip.publicIPAllocationMethod,
            sku: pip.sku?.name,
            domain_name_label: pip.dnsSettings?.domainNameLabel,
            fqdn: pip.dnsSettings?.fqdn
          },
          tags: pip.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning Public IPs: ${err.message}`);
  }

  return results;
}

/**
 * Scan Managed Disks
 */
async function scanDisk() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = computeClient.disks.list(rg.name);
      for await (const disk of iterator) {
        results.push({
          type: 'azurerm_managed_disk',
          id: disk.id,
          name: disk.name,
          resource_group: rg.name,
          location: disk.location,
          attributes: {
            disk_size_gb: disk.diskSizeGB,
            disk_type: disk.sku?.name,
            os_type: disk.osType,
            provisioning_state: disk.provisioningState
          },
          tags: disk.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning Disks: ${err.message}`);
  }

  return results;
}

/**
 * Scan Network Security Groups
 */
async function scanNSG() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = networkClient.networkSecurityGroups.list(rg.name);
      for await (const nsg of iterator) {
        results.push({
          type: 'azurerm_network_security_group',
          id: nsg.id,
          name: nsg.name,
          resource_group: rg.name,
          location: nsg.location,
          attributes: {
            security_rules: nsg.securityRules?.map(rule => ({
              name: rule.name,
              priority: rule.priority,
              direction: rule.direction,
              access: rule.access,
              protocol: rule.protocol,
              source_port_range: rule.sourcePortRange,
              destination_port_range: rule.destinationPortRange,
              source_address_prefix: rule.sourceAddressPrefix,
              destination_address_prefix: rule.destinationAddressPrefix
            }))
          },
          tags: nsg.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning NSGs: ${err.message}`);
  }

  return results;
}

/**
 * Scan Load Balancers
 */
async function scanLB() {
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = networkClient.loadBalancers.list(rg.name);
      for await (const lb of iterator) {
        results.push({
          type: 'azurerm_lb',
          id: lb.id,
          name: lb.name,
          resource_group: rg.name,
          location: lb.location,
          attributes: {
            sku: lb.sku?.name,
            frontend_ip_configurations: lb.frontendIPConfigurations?.map(fic => ({
              name: fic.name,
              public_ip_address_id: fic.publicIPAddress?.id,
              private_ip_address: fic.privateIPAddress,
              subnet_id: fic.subnet?.id
            })),
            backend_address_pools: lb.backendAddressPools?.map(bap => ({
              name: bap.name
            }))
          },
          tags: lb.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning Load Balancers: ${err.message}`);
  }

  return results;
}

/**
 * Scan App Services (Web Apps)
 */
async function scanAppService() {
  const { WebSiteManagementClient } = require('@azure/arm-appservice');
  const client = new WebSiteManagementClient(subscriptionId, new DefaultAzureCredential());
  const results = [];

  try {
    const rgs = await listResourceGroups();
    for (const rg of rgs) {
      const iterator = client.webApps.listByResourceGroup(rg.name);
      for await (const app of iterator) {
        results.push({
          type: 'azurerm_linux_web_app',
          id: app.id,
          name: app.name,
          resource_group: rg.name,
          location: app.location,
          attributes: {
            server_farm_id: app.serverFarmId,
            site_config: app.siteConfig,
            https_only: app.httpsOnly
          },
          tags: app.tags || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning App Services: ${err.message}`);
  }

  return results;
}

/**
 * Resource scanner dispatcher
 */
const scanners = {
  vm: scanVM,
  vnet: scanVNet,
  subnet: scanSubnet,
  nic: scanNIC,
  pip: scanPublicIP,
  public_ip: scanPublicIP,
  disk: scanDisk,
  nsg: scanNSG,
  lb: scanLB,
  appsvc: scanAppService,
  app_service: scanAppService,
  webapp: scanAppService
};

/**
 * Main entry
 */
async function main() {
  console.error(`Azure Resource Scanner (subscription: ${subscriptionId}, location: ${location || 'all'})`);

  try {
    await initClients();
  } catch (err) {
    console.error(`Failed to authenticate with Azure: ${err.message}`);
    console.error('Ensure AZURE_SUBSCRIPTION_ID is set and credentials are configured');
    process.exit(1);
  }

  const allResources = [];

  for (const resourceType of resourceTypes) {
    const scanner = scanners[resourceType];
    if (!scanner) {
      console.error(`Warning: Unknown resource type '${resourceType}'. Skipping.`);
      continue;
    }

    console.error(`Scanning ${resourceType}...`);
    try {
      const resources = await scanner();
      allResources.push(...resources);
    } catch (err) {
      console.error(`Failed to scan ${resourceType}: ${err.message}`);
    }
  }

  // Output results
  if (outputFormat === 'json') {
    console.log(JSON.stringify(allResources, null, 2));
  } else {
    console.log(allResources.map(r =>
      `[${r.type}] ${r.name} (RG: ${r.resource_group}) in ${r.location}`
    ).join('\\n'));
  }

  process.exit(0);
}

if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = { scanVM, scanVNet, scanSubnet, scanNIC, scanPublicIP, scanDisk, scanNSG, scanLB, scanAppService };


path: scripts/config.js
content: #!/usr/bin/env node
/**
 * Configuration Manager
 *
 * Usage: node config.js [--provider <provider>] [--get <key>] [--list]
 *
 * Example:
 *   node config.js --list
 *   node config.js --provider aws --get region
 *   node config.js --provider azure
 *
 * Manages cloud provider credentials and settings:
 *   - Loads configuration from environment variables and config files
 *   - Provides unified config object for scanners and generators
 *   - Validates credential presence
 *   - Supports multiple providers
 *
 * Config sources (in order):
 *   1. Environment variables (preferred)
 *   2. ~/.terraform.d/credentials (if present)
 *   3. ./config.json (local project config)
 *   4. Default values
 */

const fs = require('fs');
const path = require('path');
const os = require('os');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Parse arguments
const argv = yargs(hideBin(process.argv))
  .option('provider', {
    alias: 'p',
    type: 'string',
    choices: ['aws', 'azure', 'gcp', 'all'],
    description: 'Cloud provider to configure'
  })
  .option('get', {
    alias: 'g',
    type: 'string',
    description: 'Get a specific config value'
  })
  .option('list', {
    alias: 'l',
    type: 'boolean',
    description: 'List all configuration settings'
  })
  .option('validate', {
    alias: 'v',
    type: 'boolean',
    description: 'Validate credentials and connectivity'
  })
  .argv;

// Configuration defaults
const DEFAULTS = {
  aws: {
    region: 'us-east-1',
    profile: null, // from AWS_PROFILE
    access_key: null, // from AWS_ACCESS_KEY_ID
    secret_key: null, // from AWS_SECRET_ACCESS_KEY
    session_token: null, // from AWS_SESSION_TOKEN
    role_arn: null, // from AWS_ROLE_ARN
    external_id: null // from AWS_EXTERNAL_ID
  },
  azure: {
    subscription_id: null, // from AZURE_SUBSCRIPTION_ID
    tenant_id: null, // from AZURE_TENANT_ID
    client_id: null, // from AZURE_CLIENT_ID (service principal)
    client_secret: null, // from AZURE_CLIENT_SECRET
    location: null, // from AZURE_LOCATION
    use_cli: false // Use 'az login' credentials if true
  },
  gcp: {
    project: null, // from GCP_PROJECT or GOOGLE_CLOUD_PROJECT
    region: null, // from GCP_REGION or GOOGLE_REGION
    zone: null, // from GCP_ZONE or GOOGLE_ZONE
    credentials_file: null, // from GOOGLE_APPLICATION_CREDENTIALS
    use_default: true // Use default credential chain
  }
};

/**
 * Load configuration from environment variables
 */
function loadFromEnv(provider) {
  const config = { ...DEFAULTS[provider] };

  switch (provider) {
    case 'aws':
      config.region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || config.region;
      config.profile = process.env.AWS_PROFILE || null;
      config.access_key = process.env.AWS_ACCESS_KEY_ID || null;
      config.secret_key = process.env.AWS_SECRET_ACCESS_KEY || null;
      config.session_token = process.env.AWS_SESSION_TOKEN || null;
      config.role_arn = process.env.AWS_ROLE_ARN || null;
      break;

    case 'azure':
      config.subscription_id = process.env.AZURE_SUBSCRIPTION_ID || null;
      config.tenant_id = process.env.AZURE_TENANT_ID || null;
      config.client_id = process.env.AZURE_CLIENT_ID || null;
      config.client_secret = process.env.AZURE_CLIENT_SECRET || null;
      config.location = process.env.AZURE_LOCATION || process.env.AZURE_REGION || null;
      config.use_cli = process.env.AZURE_USE_CLI === 'true';
      break;

    case 'gcp':
      config.project = process.env.GCP_PROJECT || process.env.GOOGLE_CLOUD_PROJECT || null;
      config.region = process.env.GCP_REGION || process.env.GOOGLE_REGION || null;
      config.zone = process.env.GCP_ZONE || process.env.GOOGLE_ZONE || null;
      config.credentials_file = process.env.GOOGLE_APPLICATION_CREDENTIALS || null;
      config.use_default = process.env.GOOGLE_USE_DEFAULT !== 'false';
      break;
  }

  return config;
}

/**
 * Load configuration from local file
 */
function loadFromFile(provider) {
  const candidates = [
    path.join(process.cwd(), 'config.json'),
    path.join(process.cwd(), '.terraformrc'),
    path.join(os.homedir(), '.terraform.d', 'credentials'),
    path.join(os.homedir(), '.config', 'terraform', 'credentials')
  ];

  for (const filePath of candidates) {
    if (fs.existsSync(filePath)) {
      try {
        const content = fs.readFileSync(filePath, 'utf8');
        const config = JSON.parse(content);

        if (config[provider]) {
          return config[provider];
        }

        // HCL-style credentials file (simplified)
        if (typeof config === 'object' && !config[provider]) {
          // Check if it's a credentials block
          const creds = config.credentials || config;
          if (creds[provider]) {
            return creds[provider];
          }
        }
      } catch (e) {
        // Ignore parse errors, try next file
      }
    }
  }

  return {};
}

/**
 * Validate AWS configuration
 */
function validateAWS(config) {
  const errors = [];
  const warnings = [];

  if (!config.access_key && !config.profile && !config.role_arn) {
    errors.push('AWS credentials not found. Set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or use AWS_PROFILE or IAM role.');
  }

  if (config.role_arn && !config.access_key && !config.secret_key) {
    warnings.push('Using IAM role with default credential chain (no explicit keys)');
  }

  if (!config.region) {
    warnings.push('AWS region not set, defaulting to us-east-1');
  }

  return { valid: errors.length === 0, errors, warnings };
}

/**
 * Validate Azure configuration
 */
function validateAzure(config) {
  const errors = [];
  const warnings = [];

  if (!config.subscription_id) {
    errors.push('Azure subscription ID not set (AZURE_SUBSCRIPTION_ID)');
  }

  if (!config.client_id && !config.use_cli) {
    errors.push('Azure credentials not configured. Set AZURE_CLIENT_ID or use AZURE_USE_CLI=true with az login');
  }

  if (config.use_cli && !config.location) {
    warnings.push('Using az login; consider setting AZURE_LOCATION');
  }

  return { valid: errors.length === 0, errors, warnings };
}

/**
 * Validate GCP configuration
 */
function validateGCP(config) {
  const errors = [];
  const warnings = [];

  if (!config.project) {
    errors.push('GCP project not set (GCP_PROJECT or GOOGLE_CLOUD_PROJECT)');
  }

  if (!config.credentials_file && !config.use_default) {
    warnings.push('GCP credentials not explicitly set; relying on default credential chain');
  }

  return { valid: errors.length === 0, errors, warnings };
}

/**
 * Get full configuration for a provider
 */
function getConfig(provider) {
  let config = loadFromEnv(provider);
  const fileConfig = loadFromFile(provider);

  // Merge: file config overrides env if present? No, env takes precedence
  // Actually: file config is fallback, env overrides file
  // So we already have env-loaded config; merge file config as defaults only
  const merged = { ...DEFAULTS[provider], ...fileConfig, ...config };
  return merged;
}

/**
 * List all configuration for one or all providers
 */
function listConfig(provider) {
  const providers = provider === 'all' ? ['aws', 'azure', 'gcp'] : [provider];
  const output = {};

  for (const p of providers) {
    output[p] = getConfig(p);
  }

  return output;
}

/**
 * Main entry
 */
async function main() {
  const provider = argv.provider || 'all';

  if (argv.list) {
    const config = listConfig(provider);
    console.log(JSON.stringify(config, null, 2));
    process.exit(0);
  }

  if (argv.get) {
    if (!argv.provider) {
      console.error('Error: --provider is required with --get');
      process.exit(1);
    }
    const config = getConfig(provider);
    console.log(config[argv.get] || '');
    process.exit(0);
  }

  if (argv.validate) {
    console.error(`Validating ${provider.toUpperCase()} configuration...`);

    const config = getConfig(provider);
    let validation;

    switch (provider) {
      case 'aws':
        validation = validateAWS(config);
        break;
      case 'azure':
        validation = validateAzure(config);
        break;
      case 'gcp':
        validation = validateGCP(config);
        break;
      default:
        console.error('Error: must specify a provider for validation');
        process.exit(1);
    }

    if (validation.valid) {
      console.error(`✓ ${provider.toUpperCase()} configuration is valid`);
      if (validation.warnings.length > 0) {
        console.error('Warnings:');
        validation.warnings.forEach(w => console.error(`  - ${w}`));
      }
      process.exit(0);
    } else {
      console.error(`✗ ${provider.toUpperCase()} configuration is invalid:`);
      validation.errors.forEach(e => console.error(`  - ${e}`));
      process.exit(1);
    }
  }

  // Default: print useful info
  const config = getConfig(provider);
  console.log(JSON.stringify(config, null, 2));
}

if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = {
  DEFAULTS,
  loadFromEnv,
  loadFromFile,
  getConfig,
  listConfig,
  validateAWS,
  validateAzure,
  validateGCP
};


path: scripts/gcp-scanner.js
content: #!/usr/bin/env node
/**
 * GCP Resource Scanner
 *
 * Usage: node gcp-scanner.js [--project <id>] [--region <region>] [--output <format>] <resource-type>...
 *
 * Example:
 *   node gcp-scanner.js --project my-gcp-project --region us-central1 compute subnet --output json
 *   node gcp-scanner.js compute
 *
 * Supported resource types:
 *   compute    - Compute Engine instances
 *   network    - VPC networks
 *   subnet     - Subnets
 *   disk       - Persistent disks
 *   firewall   - Firewall rules
 *   gke        - GKE clusters
 *   cloudsql   - Cloud SQL instances
 *   gcr        - Container Registry images (list only, metadata limited)
 *
 * Outputs JSON array to stdout with resource metadata:
 * [
 *   {
 *     "type": "google_compute_instance",
 *     "id": "projects/.../zones/.../instances/myvm",
 *     "name": "myvm",
 *     "project": "my-project",
 *     "zone": "us-central1-a",
 *     "attributes": { ... },
 *     "labels": { ... }
 *   }
 * ]
 */

const { Compute } = require('@google-cloud/compute');
const { ResourceManager } = require('@google-cloud/resource-manager');
const { SQL } = require('@google-cloud/sql');
const { ContainerAnalysisClient } = require('@google-cloud/containeranalysis').v1;
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Parse arguments
const argv = yargs(hideBin(process.argv))
  .option('project', {
    alias: 'p',
    type: 'string',
    description: 'GCP project ID',
    default: process.env.GCP_PROJECT || process.env.GOOGLE_CLOUD_PROJECT
  })
  .option('region', {
    alias: 'r',
    type: 'string',
    description: 'GCP region to scan (for regional resources)'
  })
  .option('output', {
    alias: 'o',
    type: 'string',
    default: 'json',
    choices: ['json', 'text']
  })
  .demandCommand(1, 'At least one resource type is required')
  .argv;

const resourceTypes = argv._;
const projectId = argv.project;
const region = argv.region;
const outputFormat = argv.output;

/**
 * Get all zones for the project if needed
 */
async function listZones(compute) {
  const [zones] = await compute.getZones();
  return zones;
}

/**
 * Scan Compute Engine instances
 */
async function scanCompute() {
  const results = [];

  try {
    const compute = new Compute();
    const zones = await listZones(compute);

    // If region specified, filter zones
    const targetZones = region
      ? zones.filter(z => z.name.startsWith(region))
      : zones;

    for (const zone of targetZones) {
      console.error(`Scanning instances in zone ${zone.name}...`);
      const instances = zone.getInstances();

      for await (const instance of instances) {
        results.push({
          type: 'google_compute_instance',
          id: instance.id,
          name: instance.name,
          project: projectId,
          zone: zone.name,
          region: zone.region?.name,
          attributes: {
            machine_type: instance.machineType,
            status: instance.status,
            cpu_platform: instance.cpuPlatform,
            disks: instance.disks?.map(d => ({
              device_name: d.deviceName,
              boot: d.boot,
              auto_delete: d.autoDelete,
              initialize_params: d.initializeParams
            })),
            network_interfaces: instance.networkInterfaces?.map(ni => ({
              network: ni.network,
              subnetwork: ni.subnetwork,
              access_configs: ni.accessConfigs
            })),
            service_accounts: instance.serviceAccounts
          },
          labels: instance.labels || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning Compute instances: ${err.message}`);
  }

  return results;
}

/**
 * Scan VPC networks
 */
async function scanNetwork() {
  const results = [];

  try {
    const compute = new Compute();
    const [networks] = await compute.getNetworks();

    for (const network of networks) {
      const [metadata] = await network.getMetadata();
      results.push({
        type: 'google_compute_network',
        id: network.id,
        name: network.name,
        project: projectId,
        attributes: {
          routing_config: network.routingConfig,
          auto_create_subnetworks: metadata?.autoCreateSubnetworks,
          ipv4_range: metadata?.ipv4Range
        },
        labels: network.labels || {}
      });
    }
  } catch (err) {
    console.error(`Error scanning Networks: ${err.message}`);
  }

  return results;
}

/**
 * Scan Subnets
 */
async function scanSubnet() {
  const results = [];

  try {
    const compute = new Compute();
    const [subnets] = await compute.getSubnetworks();

    // Filter by region if specified
    const targetSubnets = region
      ? subnets.filter(s => s.region?.endsWith(region))
      : subnets;

    for (const subnet of targetSubnets) {
      results.push({
        type: 'google_compute_subnetwork',
        id: subnet.id,
        name: subnet.name,
        project: projectId,
        region: subnet.region,
        attributes: {
          network: subnet.network,
          ip_cidr_range: subnet.ipCidrRange,
          private_ip_google_access: subnet.privateIpGoogleAccess,
          secondary_ip_ranges: subnet.secondaryIpRanges
        },
        labels: subnet.labels || {}
      });
    }
  } catch (err) {
    console.error(`Error scanning Subnets: ${err.message}`);
  }

  return results;
}

/**
 * Scan Persistent Disks
 */
async function scanDisk() {
  const results = [];

  try {
    const compute = new Compute();
    const zones = await listZones(compute);

    // If region specified, filter zones
    const targetZones = region
      ? zones.filter(z => z.name.startsWith(region))
      : zones;

    for (const zone of targetZones) {
      console.error(`Scanning disks in zone ${zone.name}...`);
      const disks = zone.getDisks();

      for await (const disk of disks) {
        results.push({
          type: 'google_compute_disk',
          id: disk.id,
          name: disk.name,
          project: projectId,
          zone: zone.name,
          region: zone.region?.name,
          attributes: {
            size_gb: disk.sizeGb,
            disk_type: disk.type,
            status: disk.status,
            source_snapshot: disk.sourceSnapshot,
            source_image: disk.sourceImage
          },
          labels: disk.labels || {}
        });
      }
    }
  } catch (err) {
    console.error(`Error scanning Disks: ${err.message}`);
  }

  return results;
}

/**
 * Scan Firewall Rules
 */
async function scanFirewall() {
  const results = [];

  try {
    const compute = new Compute();
    const [firewalls] = await compute.getFirewalls();

    for (const fw of firewalls) {
      results.push({
        type: 'google_compute_firewall',
        id: fw.id,
        name: fw.name,
        project: projectId,
        attributes: {
          network: fw.network,
          direction: fw.direction,
          priority: fw.priority,
          source_ranges: fw.sourceRanges,
          destination_ranges: fw.destinationRanges,
          allowed: fw.allowed,
          denied: fw.denied
        },
        labels: fw.labels || {}
      });
    }
  } catch (err) {
    console.error(`Error scanning Firewalls: ${err.message}`);
  }

  return results;
}

/**
 * Scan GKE clusters
 */
async function scanGKE() {
  const { KubernetesEngineClient } = require('@google-cloud/container').v1;
  const results = [];

  try {
    const client = new KubernetesEngineClient();
    const parent = `projects/${projectId}/locations/-`; // All locations

    const [clusters] = await client.listClusters({ parent });

    for (const cluster of clusters) {
      results.push({
        type: 'google_container_cluster',
        id: cluster.name,
        name: cluster.name,
        project: projectId,
        location: cluster.location,
        attributes: {
          initial_cluster_version: cluster.initialClusterVersion,
          current_master_version: cluster.currentMasterVersion,
          node_config: cluster.nodeConfig,
          network: cluster.network,
          subnetwork: cluster.subnetwork,
          ip_allocation_policy: cluster.ipAllocationPolicy,
          private_cluster_config: cluster.privateClusterConfig,
          master_auth: cluster.masterAuth
        },
        labels: cluster.labelFingerprint || {}
      });
    }
  } catch (err) {
    console.error(`Error scanning GKE clusters: ${err.message}`);
  }

  return results;
}

/**
 * Scan Cloud SQL instances
 */
async function scanCloudSQL() {
  const results = [];

  try {
    const sql = new SQL();
    const [instances] = await sql.getInstances(projectId);

    for (const instance of instances) {
      results.push({
        type: 'google_sql_database_instance',
        id: instance.name,
        name: instance.name,
        project: projectId,
        region: instance.region,
        attributes: {
          database_version: instance.databaseVersion,
          tier: instance.tier,
          state: instance.state,
          root_password_set: instance.rootPasswordSet || false,
          ip_addresses: instance.ipAddresses,
          settings: instance.settings
        },
        labels: instance.labels || {}
      });
    }
  } catch (err) {
    console.error(`Error scanning Cloud SQL: ${err.message}`);
  }

  return results;
}

/**
 * Scan Container Registry images (basic listing)
 */
async function scanGCR() {
  const { ContainerAnalysisClient } = require('@google-cloud/containeranalysis').v1;
  const results = [];

  try {
    // GCR images are stored in Artifact Registry or Container Registry
    // Full metadata requires scanning multiple projects; we'll do a basic scan
    // For full listing, gcloud or direct registry API calls would be needed

    console.error('GCR scanning limited - consider using artifact registry API for full lists');
    results.push({
      type: 'google_container_registry_repository',
      id: `projects/${projectId}/repositories`,
      name: 'gcr.io',
      project: projectId,
      region: 'global',
      attributes: {
        note: 'Full GCR image listing requires artifact registry API per-repository scanning'
      },
      labels: {}
    });
  } catch (err) {
    console.error(`Error scanning GCR: ${err.message}`);
  }

  return results;
}

/**
 * Resource scanner dispatcher
 */
const scanners = {
  compute: scanCompute,
  instance: scanCompute,
  vm: scanCompute,
  network: scanNetwork,
  vpc: scanNetwork,
  subnet: scanSubnet,
  disk: scanDisk,
  firewall: scanFirewall,
  gke: scanGKE,
  kubernetes: scanGKE,
  cloudsql: scanCloudSQL,
  sql: scanCloudSQL,
  gcr: scanGCR,
  registry: scanGCR
};

/**
 * Main entry
 */
async function main() {
  console.error(`GCP Resource Scanner (project: ${projectId}, region: ${region || 'all'})`);

  if (!projectId) {
    console.error('Error: GCP project ID is required');
    console.error('Set GCP_PROJECT environment variable or use --project <id>');
    process.exit(1);
  }

  const allResources = [];

  for (const resourceType of resourceTypes) {
    const scanner = scanners[resourceType];
    if (!scanner) {
      console.error(`Warning: Unknown resource type '${resourceType}'. Skipping.`);
      continue;
    }

    console.error(`Scanning ${resourceType}...`);
    try {
      const resources = await scanner();
      allResources.push(...resources);
    } catch (err) {
      console.error(`Failed to scan ${resourceType}: ${err.message}`);
    }
  }

  // Output results
  if (outputFormat === 'json') {
    console.log(JSON.stringify(allResources, null, 2));
  } else {
    console.log(allResources.map(r =>
      `[${r.type}] ${r.name} in ${r.zone || r.location || r.region || 'unknown'}`
    ).join('\\n'));
  }

  process.exit(0);
}

if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = { scanCompute, scanNetwork, scanSubnet, scanDisk, scanFirewall, scanGKE, scanCloudSQL };


path: scripts/main.js
content: #!/usr/bin/env node
/**
 * Terraform Module Generator - CLI Entry Point
 *
 * Usage: node main.js [provider] [resource-types...] [--output <dir>] [--region <region>]
 *
 * Example:
 *   node main.js aws ec2 vpc s3 --output ./my-module --region us-east-1
 *   node main.js azure vm vnet --output ./azure-module
 *   node main.js gcp compute network --output ./gcp-module
 *
 * This script orchestrates the scanning and generation workflow:
 * 1. Initializes cloud provider scanners
 * 2. Collects resource metadata from cloud APIs
 * 3. Generates Terraform module files
 * 4. Validates the output
 */

const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');

// Load config
const configPath = path.resolve(__dirname, '../config.js');
const config = require(configPath);

/**
 * Parse command-line arguments
 */
function parseArgs(args) {
  const options = {
    provider: null,
    resources: [],
    output: './generated-module',
    region: null
  };

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];

    if (arg === '--output' && i + 1 < args.length) {
      options.output = args[++i];
    } else if (arg === '--region' && i + 1 < args.length) {
      options.region = args[++i];
    } else if (!arg.startsWith('--')) {
      if (!options.provider) {
        options.provider = arg.toLowerCase();
      } else {
        options.resources.push(arg.toLowerCase());
      }
    }
  }

  return options;
}

/**
 * Run a script and capture its JSON output
 */
function runScript(scriptPath, args) {
  return new Promise((resolve, reject) => {
    const script = path.resolve(__dirname, scriptPath);
    const proc = spawn('node', [script, ...args], {
      stdio: ['ignore', 'pipe', 'pipe'],
      env: { ...process.env, NODE_ENV = 'production' }
    });

    let stdout = '';
    let stderr = '';

    proc.stdout.on('data', (data) => {
      stdout += data.toString();
    });

    proc.stderr.on('data', (data) => {
      stderr += data.toString();
    });

    proc.on('close', (code) => {
      if (code !== 0) {
        reject(new Error(`Script ${script} failed with exit code ${code}: ${stderr}`));
      } else {
        try {
          const result = JSON.parse(stdout);
          resolve(result);
        } catch (e) {
          // Non-JSON output is okay for some scripts
          resolve(stdout.trim());
        }
      }
    });

    proc.on('error', (err) => {
      reject(new Error(`Failed to start ${script}: ${err.message}`));
    });
  });
}

/**
 * Main workflow
 */
async function main() {
  const args = process.argv.slice(2);

  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
    console.log(`Usage: node main.js <provider> <resource-types...> [--output <dir>] [--region <region>]

Providers: aws, azure, gcp

Examples:
  node main.js aws ec2 vpc s3 --output ./my-module --region us-east-1
  node main.js azure vm vnet --output ./azure-module
  node main.js gcp compute network --output ./gcp-module

This tool scans existing cloud resources and generates a Terraform module.
`);
    process.exit(0);
  }

  const options = parseArgs(args);

  if (!options.provider || options.resources.length === 0) {
    console.error('Error: provider and at least one resource type are required');
    console.error('Run with --help for usage');
    process.exit(1);
  }

  console.log(`\\n=== Terraform Module Generator ===`);
  console.log(`Provider: ${options.provider.toUpperCase()}`);
  console.log(`Resources: ${options.resources.join(', ')}`);
  console.log(`Output: ${options.output}`);
  if (options.region) console.log(`Region: ${options.region}`);

  try {
    // Step 1: Scan cloud resources
    console.log(`\\n[1/4] Scanning ${options.provider.toUpperCase()} resources...`);

    let resources;
    const scannerArgs = options.region ? [options.region, '--output', 'json'] : ['--output', 'json'];

    switch (options.provider) {
      case 'aws':
        resources = await runScript('./aws-scanner.js', [...scannerArgs, ...options.resources]);
        break;
      case 'azure':
        resources = await runScript('./azure-scanner.js', [...scannerArgs, ...options.resources]);
        break;
      case 'gcp':
        resources = await runScript('./gcp-scanner.js', [...scannerArgs, ...options.resources]);
        break;
      default:
        throw new Error(`Unsupported provider: ${options.provider}`);
    }

    if (!resources || resources.length === 0) {
      console.warn(`No ${options.provider} resources found matching: ${options.resources.join(', ')}`);
      console.log('Continuing with empty module...');
    } else {
      console.log(`Found ${resources.length} resource(s)`);
    }

    // Step 2: Generate Terraform code
    console.log(`\\n[2/4] Generating Terraform module...`);

    // Ensure output directory exists
    fs.mkdirSync(options.output, { recursive: true });

    const generatorArgs = ['--input', 'json', '--output', options.output];
    await runScript('./terraform-generator.js', generatorArgs);

    // Pass resources to generator via stdin
    // (Implementation would need to be adjusted for actual stdin handling)
    // For now, we'll assume the generator reads from a temp file or environment

    // Step 3: Validate generated code
    console.log(`\\n[3/4] Validating Terraform syntax...`);

    const validatorResult = await runScript('./validator.js', [options.output]);
    console.log(`Validation: ${validatorResult.status || 'passed'}`);

    if (validatorResult.errors) {
      console.warn('Validation warnings:');
      validatorResult.errors.forEach(err => console.warn(`  - ${err}`));
    }

    // Step 4: Finalize
    console.log(`\\n[4/4] Module generation complete!`);
    console.log(`\\nOutput directory: ${path.resolve(options.output)}`);
    console.log(`\\nNext steps:`);
    console.log(`  - Review the generated main.tf, variables.tf, outputs.tf`);
    console.log(`  - Run 'terraform init' in the module directory`);
    console.log(`  - Run 'terraform validate' to double-check`);
    console.log(`  - Optional: Customize the README.md`);

    console.log(`\\n=== Done ===\\n`);

  } catch (error) {
    console.error(`\\nERROR: ${error.message}`);
    process.exit(1);
  }
}

// Execute
if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = { main };


path: scripts/provider-manager.js
content: #!/usr/bin/env node
/**
 * Provider Configuration Manager
 *
 * Usage: node provider-manager.js [--provider <provider>] [--validate] [--list]
 *
 * Functions:
 *   - loadProviderConfig(provider): returns config object for the provider
 *   - validateCredentials(provider): async, tests connectivity with cloud providers
 *
 * This module uses config.js for loading configuration and performs
 * active connectivity checks using cloud provider SDKs.
 */

const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Import config.js (must be in the same directory)
const config = require('./config.js');

// Import cloud SDKs
const { EC2Client, DescribeRegionsCommand } = require('@aws-sdk/client-ec2');
const { DefaultAzureCredential } = require('@azure/identity');
const { ResourceManagementClient } = require('@azure/arm-resources');
const { Compute } = require('@google-cloud/compute');

/**
 * Load provider configuration (from env, files, defaults)
 * @param {string} provider - 'aws', 'azure', or 'gcp'
 * @returns {object} Configuration object
 */
async function loadProviderConfig(provider) {
  return config.getConfig(provider);
}

/**
 * Validate credentials by performing a simple API call
 * @param {string} provider - 'aws', 'azure', or 'gcp'
 * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
 */
async function validateCredentials(provider) {
  const cfg = config.getConfig(provider);
  const errors = [];
  const warnings = [];

  switch (provider) {
    case 'aws':
      try {
        const client = new EC2Client({
          region: cfg.region || 'us-east-1',
          // Credentials are automatically resolved from environment/chain
        });
        // Perform a simple, low-cost call to verify connectivity
        await client.send(new DescribeRegionsCommand({}));
      } catch (err) {
        errors.push(`AWS connection failed: ${err.message}`);
      }
      break;

    case 'azure':
      try {
        if (!cfg.subscription_id) {
          errors.push('Azure subscription_id is required');
          break;
        }
        const credential = new DefaultAzureCredential();
        const resourceClient = new ResourceManagementClient(credential, cfg.subscription_id);
        // Test by fetching a provider list (limit to first page)
        const iterator = resourceClient.providers.list();
        let count = 0;
        for await (const p of iterator) {
          count++;
          if (count >= 1) break; // Only need to confirm access
        }
      } catch (err) {
        errors.push(`Azure connection failed: ${err.message}`);
      }
      break;

    case 'gcp':
      try {
        if (!cfg.project) {
          errors.push('GCP project is required');
          break;
        }
        const compute = new Compute();
        // Listing zones is a quick, project-scoped operation
        await compute.getZones();
      } catch (err) {
        errors.push(`GCP connection failed: ${err.message}`);
      }
      break;

    default:
      errors.push(`Unsupported provider: ${provider}`);
  }

  return { valid: errors.length === 0, errors, warnings };
}

/**
 * List available cloud providers
 * @returns {string[]}
 */
function listProviders() {
  return ['aws', 'azure', 'gcp'];
}

/**
 * Helper to check if a provider has basic credentials configured
 * @private
 */
function providerHasCreds(provider, cfg) {
  switch (provider) {
    case 'aws':
      return !!cfg.access_key || !!cfg.profile || !!cfg.role_arn;
    case 'azure':
      return !!cfg.subscription_id && (!!cfg.client_id || !!cfg.client_secret || cfg.use_cli);
    case 'gcp':
      return !!cfg.project || !!cfg.credentials_file;
    default:
      return false;
  }
}

/**
 * CLI entry point
 */
async function mainCLI() {
  const argv = yargs(hideBin(process.argv))
    .option('provider', {
      alias: 'p',
      type: 'string',
      choices: ['aws', 'azure', 'gcp', 'all'],
      description: 'Cloud provider to manage'
    })
    .option('validate', {
      alias: 'v',
      type: 'boolean',
      description: 'Validate credentials (connects to cloud provider)'
    })
    .option('list', {
      alias: 'l',
      type: 'boolean',
      description: 'List providers with configuration status'
    })
    .argv;

  const provider = argv.provider || 'all';

  // --list: show status for providers
  if (argv.list) {
    const providers = listProviders();
    const status = {};
    for (const p of providers) {
      const cfg = config.getConfig(p);
      status[p] = {
        configured: providerHasCreds(p, cfg),
        region: cfg.region || null,
        project: cfg.project || null,
        subscription: cfg.subscription_id || null
      };
    }
    console.log(JSON.stringify(status, null, 2));
    process.exit(0);
  }

  // --validate: test connectivity
  if (argv.validate) {
    if (provider === 'all') {
      console.error('Specify a provider for validation or use --list to see all.');
      process.exit(1);
    }
    const result = await validateCredentials(provider);
    console.log(JSON.stringify(result, null, 2));
    process.exit(result.valid ? 0 : 1);
  }

  // Default: print configuration for requested provider(s)
  const output = {};
  if (provider === 'all') {
    for (const p of listProviders()) {
      output[p] = config.getConfig(p);
    }
  } else {
    output[provider] = config.getConfig(provider);
  }
  console.log(JSON.stringify(output, null, 2));
}

if (require.main === module) {
  mainCLI().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = { loadProviderConfig, validateCredentials, listProviders };


path: scripts/schedule-manager.js
content: #!/usr/bin/env node
/**
 * Schedule Manager for Terraform Module Generator
 *
 * Usage: node schedule-manager.js <command> [options]
 *
 * Commands:
 *   list [--json]                       List scheduled jobs
 *   add <provider> <output> <resources...> [options]
 *                                      Add a new scheduled job
 *   remove <job-id>                     Remove a scheduled job
 *   enable <job-id>                      Enable a job
 *   disable <job-id>                     Disable a job
 *   run <job-id>                        Run a job immediately
 *
 * Add options:
 *   --cron "<cron expr>"                Cron expression (e.g. "0 2 * * *")
 *   --every <duration>                  Interval (e.g. "10m", "1h")
 *   --region <region>                   Region (optional)
 *   --name <name>                       Job name
 *   --description <text>                Description
 *   --agent <agent-id>                  Agent ID (default: tf-module-generator)
 *   --session <target>                  Session target: isolated or main (default: isolated)
 *   --model <model>                     Model override for agent jobs
 *   --thinking <level>                  Thinking level (off|minimal|low|medium|high)
 *
 * This script wraps OpenClaw's cron facility to schedule module generation jobs.
 */

const { execFile } = require('child_process');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Run an openclaw cron command via execFile to avoid shell quoting issues
function execOpenClaw(argsArray, opts = {}) {
  return new Promise((resolve, reject) => {
    execFile('openclaw', ['cron', ...argsArray], { ...opts, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
      if (err) {
        reject(new Error(stderr || err.message));
      } else {
        resolve({ stdout, stderr });
      }
    });
  });
}

// Command implementations
async function runList(args) {
  try {
    const { stdout } = await execOpenClaw(['list', '--json']);
    const jobs = JSON.parse(stdout);
    if (args.json) {
      console.log(stdout);
    } else {
      console.log('Scheduled jobs:');
      if (jobs.length === 0) {
        console.log('  (none)');
      } else {
        jobs.forEach(job => {
          console.log(`  ${job.id} | ${job.name || '(no name)'} | ${job.cron || job.every || 'one-shot'} | ${job.enabled ? 'enabled' : 'disabled'}`);
          console.log(`    Agent: ${job.agent || 'default'} | Next: ${job.nextRun || 'N/A'} | Last: ${job.lastRun || 'never'}`);
        });
      }
    }
  } catch (err) {
    console.error('Error listing jobs:', err.message);
    process.exit(1);
  }
}

async function runAdd(args) {
  const { provider, output, resources, region, cron, every, name, description, agent, session, model, thinking } = args;

  if (!cron && !every) {
    console.error('Error: must specify either --cron or --every');
    process.exit(1);
  }
  if (cron && every) {
    console.error('Error: --cron and --every are mutually exclusive');
    process.exit(1);
  }

  // Build payload for agent
  const payload = {
    task: 'generate-module',
    arguments: {
      provider,
      resources,
      output,
      region: region || undefined
    }
  };

  const message = JSON.stringify(payload);

  // Build argument array for openclaw cron add
  const cliArgs = ['cron', 'add'];
  cliArgs.push('--agent', agent || 'tf-module-generator');
  cliArgs.push('--message', message);
  cliArgs.push('--session', session || 'isolated');
  if (name) cliArgs.push('--name', name);
  if (description) cliArgs.push('--description', description);
  if (cron) cliArgs.push('--cron', cron);
  if (every) cliArgs.push('--every', every);
  if (model) cliArgs.push('--model', model);
  if (thinking) cliArgs.push('--thinking', thinking);
  // By default, no announce (--no-deliver is optional; default false is fine)

  try {
    const { stdout } = await execOpenClaw(cliArgs);
    const jobId = stdout.trim();
    console.log(`Scheduled job created: ${jobId}`);
  } catch (err) {
    console.error('Error adding job:', err.message);
    process.exit(1);
  }
}

async function runRemove(args) {
  try {
    await execOpenClaw(['rm', args.jobId]);
    console.log(`Removed job ${args.jobId}`);
  } catch (err) {
    console.error('Error removing job:', err.message);
    process.exit(1);
  }
}

async function runEnable(args) {
  try {
    await execOpenClaw(['enable', args.jobId]);
    console.log(`Enabled job ${args.jobId}`);
  } catch (err) {
    console.error('Error enabling job:', err.message);
    process.exit(1);
  }
}

async function runDisable(args) {
  try {
    await execOpenClaw(['disable', args.jobId]);
    console.log(`Disabled job ${args.jobId}`);
  } catch (err) {
    console.error('Error disabling job:', err.message);
    process.exit(1);
  }
}

async function runRun(args) {
  try {
    const { stdout } = await execOpenClaw(['run', args.jobId]);
    console.log(`Job ${args.jobId} executed:`);
    console.log(stdout);
  } catch (err) {
    console.error('Error running job:', err.message);
    process.exit(1);
  }
}

// Yargs command setup
yargs(hideBin(process.argv))
  .command('list [json]', 'List scheduled jobs', (y) => {
    return y.option('json', { type: 'boolean', description: 'Output as JSON' });
  }, runList)
  .command('add <provider> <output> <resources...>', 'Add a scheduled job', (y) => {
    return y
      .positional('provider', { type: 'string', choices: ['aws','azure','gcp'] })
      .positional('output', { type: 'string', describe: 'Output directory' })
      .positional('resources', { type: 'string', describe: 'Resource types to scan' })
      .option('region', { type: 'string', description: 'Cloud region' })
      .option('cron', { type: 'string', description: 'Cron expression' })
      .option('every', { type: 'string', description: 'Interval duration (e.g. 10m, 1h)' })
      .option('name', { type: 'string', description: 'Job name' })
      .option('description', { type: 'string', description: 'Job description' })
      .option('agent', { type: 'string', default: 'tf-module-generator', description: 'Agent ID' })
      .option('session', { type: 'string', default: 'isolated', description: 'Session target (isolated or main)' })
      .option('model', { type: 'string', description: 'Model override' })
      .option('thinking', { type: 'string', description: 'Thinking level (off|minimal|low|medium|high)' });
  }, runAdd)
  .command('remove <jobId>', 'Remove a scheduled job', (y) => {
    return y.positional('jobId', { type: 'string', describe: 'Job ID to remove' });
  }, runRemove)
  .command('enable <jobId>', 'Enable a job', (y) => {
    return y.positional('jobId', { type: 'string' });
  }, runEnable)
  .command('disable <jobId>', 'Disable a job', (y) => {
    return y.positional('jobId', { type: 'string' });
  }, runDisable)
  .command('run <jobId>', 'Run a job immediately', (y) => {
    return y.positional('jobId', { type: 'string' });
  }, runRun)
  .demandCommand(1, 'Command required')
  .help()
  .argv;


path: scripts/terraform-generator.js
content: #!/usr/bin/env node
/**
 * Terraform Module Generator
 *
 * Usage: node terraform-generator.js --input <format> --output <directory> [--template <dir>]
 *
 * Example:
 *   node terraform-generator.js --input json --output ./my-module
 *   cat resources.json | node terraform-generator.js --input json --output ./module
 *
 * This script reads cloud resource metadata (from stdin or file) and generates:
 *   - main.tf      (resource definitions)
 *   - variables.tf (input variables inferred from resource attributes)
 *   - outputs.tf   (output values for resource attributes)
 *   - README.md    (usage documentation)
 *
 * It uses template files from references/templates/ if available, otherwise
 * generates code using built-in formatters.
 */

const fs = require('fs');
const path = require('path');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Parse arguments
const argv = yargs(hideBin(process.argv))
  .option('input', {
    alias: 'i',
    type: 'string',
    choices: ['json', 'stdin'],
    default: 'stdin',
    description: 'Input format (stdin reads JSON from stdin)'
  })
  .option('output', {
    alias: 'o',
    type: 'string',
    demandOption: true,
    description: 'Output directory for generated module'
  })
  .option('template', {
    alias: 't',
    type: 'string',
    description: 'Custom template directory (optional)'
  })
  .argv;

const outputDir = path.resolve(argv.output);
const templateDir = argv.template ? path.resolve(argv.template) : path.resolve(__dirname, '../references/templates');

/**
 * Read input resources from stdin or file
 */
async function readResources() {
  let inputData;

  if (argv.input === 'stdin') {
    // Read from stdin (non-blocking)
    inputData = await new Promise((resolve, reject) => {
      let data = '';
      process.stdin.setEncoding('utf8');
      process.stdin.on('data', chunk => data += chunk);
      process.stdin.on('end', () => resolve(data));
      process.stdin.on('error', reject);
    });
  } else {
    // Could extend to file input
    throw new Error('Only stdin input is currently supported');
  }

  if (!inputData.trim()) {
    return [];
  }

  try {
    return JSON.parse(inputData);
  } catch (err) {
    throw new Error(`Failed to parse input JSON: ${err.message}`);
  }
}

/**
 * Collect unique variable definitions from resource attributes
 */
function inferVariables(resources) {
  const variables = new Map();

  for (const resource of resources) {
    const attrs = resource.attributes || {};
    const tags = resource.tags || {};

    // Add common attribute-based variables
    for (const [key, value] of Object.entries(attrs)) {
      if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
        if (!variables.has(key)) {
          variables.set(key, {
            type: typeof value === 'boolean' ? 'bool' : (typeof value === 'number' ? 'number' : 'string'),
            description: `Input for ${key}`,
            default: value
          });
        }
      }
    }

    // Add common tags-based variables (like environment, owner)
    const tagKeys = ['Environment', 'environment', 'Owner', 'owner', 'Team', 'team', 'Project', 'project'];
    for (const tagKey of tagKeys) {
      if (tags[tagKey] && !variables.has(tagKey)) {
        variables.set(tagKey, {
          type: 'string',
          description: `Tag: ${tagKey}`,
          default: tags[tagKey]
        });
      }
    }
  }

  return variables;
}

/**
 * Generate Terraform HCL for a single resource
 */
function generateResourceHCL(resource) {
  const { type, id, name, region, attributes, tags, resource_group, location, zone, project } = resource;

  // Map provider-specific type to Terraform resource type
  let terraformType;
  let resourceName = name.replace(/[^a-zA-Z0-9_-]/g, '_');
  let block = '';

  // AWS resources
  if (type.startsWith('aws_')) {
    terraformType = type; // Already in Terraform format
    block = `resource "${terraformType}" "${resourceName}" {
  ${generateAttributesHCL(attributes)}
  ${generateTagsHCL(tags)}
}`;
  }
  // Azure resources
  else if (type.startsWith('azure_')) {
    // Map to Terraform AzureRM provider types
    const typeMap = {
      'azurerm_virtual_machine': 'azurerm_linux_virtual_machine',
      'azurerm_virtual_network': 'azurerm_virtual_network',
      'azurerm_subnet': 'azurerm_subnet',
      'azurerm_network_interface': 'azurerm_network_interface',
      'azurerm_public_ip': 'azurerm_public_ip',
      'azurerm_managed_disk': 'azurerm_managed_disk',
      'azurerm_network_security_group': 'azurerm_network_security_group',
      'azurerm_lb': 'azurerm_lb',
      'azurerm_linux_web_app': 'azurerm_linux_web_app'
    };

    // Determine actual terraform type
    const baseType = type.replace('azurerm_', '');
    terraformType = typeMap[type] || `azurerm_${baseType}`;

    let attrs = { ...attributes };
    if (resource_group) attrs.resource_group_name = resource_group;
    if (location) attrs.location = location;

    block = `resource "${terraformType}" "${resourceName}" {
  ${generateAttributesHCL(attrs, { indent: 2 })}
  ${generateTagsHCL(tags, { indent: 2 })}
}`;
  }
  // GCP resources
  else if (type.startsWith('google_')) {
    terraformType = type; // Already in Terraform format
    let attrs = { ...attributes };
    if (project) attrs.project = project;
    if (region) attrs.region = region;
    if (zone) attrs.zone = zone;

    block = `resource "${terraformType}" "${resourceName}" {
  ${generateAttributesHCL(attrs)}
  ${generateLabelsHCL(labels, { indent: 2 })}
}`;
  }
  else {
    // Fallback: treat as unknown type
    block = `# Unknown resource type: ${type}
# Resource: ${JSON.stringify(resource, null, 2)}
`;
  }

  return block;
}

/**
 * Generate HCL attribute assignments
 */
function generateAttributesHCL(attributes, options = { indent: 2 }) {
  const indent = ' '.repeat(options.indent);
  let lines = [];

  for (const [key, value] of Object.entries(attributes)) {
    if (value === null || value === undefined) continue;

    const rendered = renderValue(value, options.indent + 2);
    if (rendered) {
      lines.push(`${indent}${key} = ${rendered}`);
    }
  }

  return lines.join('\\n');
}

/**
 * Generate Terraform tags block (AWS)
 */
function generateTagsHCL(tags, options = { indent: 2 }) {
  if (!tags || Object.keys(tags).length === 0) return '';

  const indent = ' '.repeat(options.indent);
  const innerIndent = ' '.repeat(options.indent + 2);
  const tagLines = [];

  for (const [key, value] of Object.entries(tags)) {
    if (value !== null && value !== undefined) {
      tagLines.push(`${innerIndent}"${key}" = "${value}"`);
    }
  }

  if (tagLines.length === 0) return '';

  return `\\n${indent}tags = {
${tagLines.join('\\n')}
${indent}}`;
}

/**
 * Generate Terraform labels block (GCP)
 */
function generateLabelsHCL(labels, options = { indent: 2 }) {
  if (!labels || Object.keys(labels).length === 0) return '';

  const indent = ' '.repeat(options.indent);
  const innerIndent = ' '.repeat(options.indent + 2);
  const labelLines = [];

  for (const [key, value] of Object.entries(labels)) {
    if (value !== null && value !== undefined) {
      labelLines.push(`${innerIndent}"${key}" = "${value}"`);
    }
  }

  if (labelLines.length === 0) return '';

  return `\\n${indent}labels = {
${labelLines.join('\\n')}
${indent}}`;
}

/**
 * Render a value to HCL literal
 */
function renderValue(value, indent = 0) {
  const space = ' '.repeat(indent);

  if (value === null || value === undefined) {
    return 'null';
  }

  if (typeof value === 'boolean') {
    return value ? 'true' : 'false';
  }

  if (typeof value === 'number') {
    return String(value);
  }

  if (typeof value === 'string') {
    // Check if it looks like a reference (already HCL)
    if (value.includes('${') || value.includes('"')) {
      return value;
    }
    // Escape quotes
    const escaped = value.replace(/"/g, '\\"');
    return `"${escaped}"`;
  }

  if (Array.isArray(value)) {
    if (value.length === 0) return '[]';

    // Simple arrays
    if (value.every(v => typeof v === 'string')) {
      return `[${value.map(v => `"${v}"`).join(', ')}]`;
    }

    // Arrays of objects - would need nested blocks, complex
    // For now, render as JSON string if simple
    return `jsonencode(${JSON.stringify(value)})`;
  }

  if (typeof value === 'object') {
    const entries = [];
    for (const [k, v] of Object.entries(value)) {
      const rendered = renderValue(v, indent);
      if (rendered) {
        entries.push(`${space}  "${k}" = ${rendered}`);
      }
    }
    if (entries.length === 0) return '{}';

    return `{\n${entries.join(',\\n')}\n${space}}`;
  }

  return null;
}

/**
 * Generate main.tf content
 */
function generateMainTF(resources) {
  const blocks = resources.map(r => generateResourceHCL(r)).filter(Boolean);
  return blocks.join('\\n\\n') + '\\n';
}

/**
 * Generate variables.tf content
 */
function generateVariablesTF(variables) {
  const varBlocks = [];

  for (const [name, def] of variables) {
    const type = def.type;
    const desc = def.description ? `"${def.description}"` : '';
    const defaultVal = def.default !== undefined ? ` = ${renderValue(def.default)}` : '';

    varBlocks.push(`variable "${name}" {
  type        = ${type}
  description = ${desc}${defaultVal ? '\\n  default     = ' + renderValue(def.default) : ''}
}`);
  }

  return varBlocks.join('\\n\\n') + '\\n';
}

/**
 * Generate outputs.tf content
 */
function generateOutputsTF(resources) {
  const outputBlocks = [];

  for (const resource of resources) {
    const resourceName = resource.name.replace(/[^a-zA-Z0-9_-]/g, '_');
    const type = resource.type;

    // Generate useful outputs
    outputBlocks.push(`output "${resource.name}_id" {
  description = "ID of the ${type} resource '${resource.name}'"
  value       = ${type}.${resourceName}.id
}`);

    // For compute resources, output common attributes
    if (['aws_instance', 'google_compute_instance', 'azurerm_virtual_machine'].includes(type)) {
      const attrs = resource.type.includes('aws') ? ['public_ip', 'private_ip', 'availability_zone'] :
                    resource.type.includes('google') ? ['network_interface', 'service_account'] :
                    ['admin_username', 'computer_name']; // azure

      for (const attr of attrs) {
        outputBlocks.push(`output "${resource.name}_${attr}" {
  description = "${attr} of ${resource.name}"
  value       = try(${type}.${resourceName}.${attr}, null)
}`);
      }
    }
  }

  return outputBlocks.join('\\n\\n') + '\\n';
}

/**
 * Generate README.md content
 */
function generateReadme(resources, moduleName = 'generated-terraform-module') {
  const lines = [
    `# ${moduleName}`,
    '',
    'Automatically generated Terraform module from existing cloud resources.',
    '',
    '## Usage',
    '',
    '```hcl',
    'module "example" {',
    '  source = "./modules/generated"',
    '',
    '  # Required variables (if any)',
    '  # region = "us-east-1"',
    '  # environment = "production"',
    '',
    '  # Optional: Override any resource attributes',
    '  # tags = {',
    '  #   Environment = "dev"',
    '  # }',
    '}',
    '```',
    '',
    '## Resources',
    ''
  ];

  for (const resource of resources) {
    lines.push(`- \`${resource.type}\`: ${resource.name} (ID: ${resource.id})`);
  }

  lines.push('');
  lines.push('## Generated Files');
  lines.push('');
  lines.push('- `main.tf` - Resource definitions');
  lines.push('- `variables.tf` - Input variables');
  lines.push('- `outputs.tf` - Output values');
  lines.push('');
  lines.push('## Notes');
  lines.push('');
  lines.push('- This module is generated automatically. Review before using in production.');
  lines.push('- Some resource attributes may require adjustments based on your provider version.');
  lines.push('- Generated on: ' + new Date().toISOString());
  lines.push('');

  return lines.join('\\n');
}

/**
 * Write files to output directory
 */
async function writeOutputs(outputDir, mainTF, variablesTF, outputsTF, readme) {
  await fs.promises.mkdir(outputDir, { recursive: true });

  await fs.promises.writeFile(path.join(outputDir, 'main.tf'), mainTF);
  await fs.promises.writeFile(path.join(outputDir, 'variables.tf'), variablesTF);
  await fs.promises.writeFile(path.join(outputDir, 'outputs.tf'), outputsTF);
  await fs.promises.writeFile(path.join(outputDir, 'README.md'), readme);

  console.error(`Generated module files in: ${outputDir}`);
}

/**
 * Main entry
 */
async function main() {
  console.error('Terraform Module Generator');

  try {
    // Read resources
    const resources = await readResources();
    console.error(`Loaded ${resources.length} resource(s)`);

    // Infer variables
    const variables = inferVariables(resources);
    console.error(`Inferred ${variables.size} variable(s)`);

    // Generate content
    const mainTF = generateMainTF(resources);
    const variablesTF = generateVariablesTF(variables);
    const outputsTF = generateOutputsTF(resources);
    const readme = generateReadme(resources);

    // Write outputs (to be called from main.js, not standalone)
    if (module.parent === null) {
      // Standalone execution: print to stdout or write to --output
      if (argv.output) {
        await writeOutputs(outputDir, mainTF, variablesTF, outputsTF, readme);
      } else {
        console.log('=== main.tf ===\\n' + mainTF);
        console.log('=== variables.tf ===\\n' + variablesTF);
        console.log('=== outputs.tf ===\\n' + outputsTF);
        console.log('=== README.md ===\\n' + readme);
      }
    } else {
      // Called from another script - return generated content
      return {
        main: mainTF,
        variables: variablesTF,
        outputs: outputsTF,
        readme: readme,
        resources,
        variables: Array.from(variables.entries())
      };
    }

  } catch (err) {
    console.error(`Error: ${err.message}`);
    process.exit(1);
  }
}

if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = {
  generateMainTF,
  generateVariablesTF,
  generateOutputsTF,
  generateReadme,
  inferVariables,
  generateResourceHCL,
  renderValue
};


path: scripts/validator.js
content: #!/usr/bin/env node
/**
 * Terraform Module Validator
 *
 * Usage: node validator.js [--strict] <directory>
 *
 * Example:
 *   node validator.js ./my-module
 *   node validator.js --strict ./generated-module
 *
 * Performs validation:
 *   - Check required files exist (main.tf, variables.tf, outputs.tf)
 *   - HCL syntax validation (using terraform CLI if available, otherwise basic parsing)
 *   - Resource type checking against known Terraform providers
 *   - Best practice checks (no hardcoded values in certain places, etc.)
 *
 * Exit codes:
 *   0 - validation passed
 *   1 - validation failed
 */

const fs = require('fs');
const { exec } = require('child_process');
const path = require('path');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

// Parse arguments
const argv = yargs(hideBin(process.argv))
  .option('strict', {
    alias: 's',
    type: 'boolean',
    default: false,
    description: 'Enable strict validation (more rules)'
  })
  .demandCommand(1, 'Directory containing Terraform module is required')
  .argv;

const moduleDir = path.resolve(argv._[0]);
const strict = argv.strict;

/**
 * Run terraform CLI command if available
 */
function runTerraform(args, cwd) {
  return new Promise((resolve, reject) => {
    exec(`terraform ${args}`, {
      cwd,
      env: { ...process.env, TF_INPUT: '0', TF_IN_AUTOMATION: '1' },
      timeout: 30000
    }, (err, stdout, stderr) => {
      if (err) {
        reject({ code: err.code, stdout, stderr });
      } else {
        resolve({ stdout, stderr });
      }
    });
  });
}

/**
 * Check if terraform CLI is available
 */
async function checkTerraform() {
  try {
    await new Promise((resolve, reject) => {
      exec('terraform version', { timeout: 5000 }, (err, stdout, stderr) => {
        if (err) reject(err);
        else resolve(stdout);
      });
    });
    return true;
  } catch {
    return false;
  }
}

/**
 * Validate required files exist
 */
function checkRequiredFiles(dir) {
  const required = ['main.tf', 'variables.tf', 'outputs.tf'];
  const missing = [];

  for (const file of required) {
    const filePath = path.join(dir, file);
    if (!fs.existsSync(filePath)) {
      missing.push(file);
    }
  }

  return missing;
}

/**
 * Basic HCL syntax validation (without terraform CLI)
 * This is a simplified parser that checks for balanced braces, quotes, etc.
 */
function basicHCLCheck(content, filename) {
  const errors = [];

  // Check for balanced braces
  let braceCount = 0;
  let inString = false;
  let escapeNext = false;

  for (let i = 0; i < content.length; i++) {
    const char = content[i];
    const nextChar = content[i + 1];

    if (escapeNext) {
      escapeNext = false;
      continue;
    }

    if (char === '\\\\') {
      escapeNext = true;
      continue;
    }

    if (char === '"' && !inString) {
      inString = true;
    } else if (char === '"' && inString) {
      inString = false;
    }

    if (!inString) {
      if (char === '{') braceCount++;
      if (char === '}') braceCount--;
    }

    if (braceCount < 0) {
      errors.push(`Unmatched closing brace at ${filename}:${i + 1}`);
      braceCount = 0; // Reset to avoid false positives
    }
  }

  if (braceCount > 0) {
    errors.push(`Unclosed ${braceCount} opening brace(s) in ${filename}`);
  }

  if (inString) {
    errors.push(`Unclosed string literal in ${filename}`);
  }

  // Check for suspicious patterns
  const lines = content.split('\\n');
  lines.forEach((line, idx) => {
    const lineNum = idx + 1;
    const trimmed = line.trim();

    // Check for HEREDOC without closing
    if (trimmed.startsWith('<<') && !trimmed.includes('EOF') && !trimmed.includes('EOT')) {
      // Might be okay with custom delimiter, but check for pattern
    }
  });

  return errors;
}

/**
 * Check resource types against known providers
 */
function checkResourceTypes(files, dir) {
  const errors = [];
  const validProviders = ['aws', 'azurerm', 'google', 'null', 'local', 'random', 'template', 'time', 'kubernetes', 'helm', 'github', 'gitlab'];

  for (const filename of files) {
    if (!filename.endsWith('.tf')) continue;

    const content = fs.readFileSync(path.join(dir, filename), 'utf8');
    const resourceRegex = /resource\\s+"([^"]+)"\\s+"([^"]+)"/g;
    let match;

    while ((match = resourceRegex.exec(content)) !== null) {
      const [full, type] = match;
      const provider = type.split('_')[0];

      if (!validProviders.includes(provider) && !['aws', 'azure', 'gcp', 'google'].includes(provider)) {
        errors.push(`Unknown provider '${provider}' in resource type '${type}' in ${filename}`);
      }

      // Check for common type errors
      if (type.includes('aws_') && !type.match(/^aws_[a-z_]+$/)) {
        errors.push(`Invalid AWS resource type format '${type}' in ${filename}`);
      }
    }
  }

  return errors;
}

/**
 * Check best practices (strict mode)
 */
function checkBestPractices(dir, files) {
  const warnings = [];

  for (const filename of files) {
    if (!filename.endsWith('.tf')) continue;

    const content = fs.readFileSync(path.join(dir, filename), 'utf8');
    const lines = content.split('\\n');

    lines.forEach((line, idx) => {
      const lineNum = idx + 1;

      // Check for hardcoded secrets (very basic)
      if ((line.includes('password') || line.includes('secret') || line.includes('token')) &&
          line.includes('=') &&
          !line.includes('var.') &&
          !line.includes('"')) {
        warnings.push(`${filename}:${lineNum}: Possible hardcoded secret`);
      }

      // Check for use of count/for_each without appropriate conditions
      if (line.includes('count =') || line.includes('for_each =')) {
        const nextLines = lines.slice(idx, idx + 3).join('\\n');
        if (!nextLines.includes('?') && !nextLines.includes('var.')) {
          warnings.push(`${filename}:${lineNum}: Consider using conditional with count/for_each`);
        }
      }
    });
  }

  return warnings;
}

/**
 * Main validation logic
 */
async function validate(dir) {
  const results = {
    valid: true,
    errors: [],
    warnings: [],
    file: null
  };

  // Check directory exists
  if (!fs.existsSync(dir)) {
    results.valid = false;
    results.errors.push(`Directory not found: ${dir}`);
    return results;
  }

  // Check required files
  const missingFiles = checkRequiredFiles(dir);
  if (missingFiles.length > 0) {
    results.valid = false;
    results.errors.push(`Missing required files: ${missingFiles.join(', ')}`);
  }

  // Collect .tf files
  const tfFiles = fs.readdirSync(dir).filter(f => f.endsWith('.tf'));

  if (tfFiles.length === 0 && !missingFiles.includes('main.tf')) {
    results.valid = false;
    results.errors.push('No Terraform configuration files (.tf) found');
  }

  // Validate each file
  for (const file of tfFiles) {
    const filePath = path.join(dir, file);
    const content = fs.readFileSync(filePath, 'utf8');

    // Basic HCL check
    const basicErrors = basicHCLCheck(content, file);
    if (basicErrors.length > 0) {
      results.valid = false;
      results.errors.push(...basicErrors.map(e => `${file}: ${e}`));
    }
  }

  // Check resource types
  const typeErrors = checkResourceTypes(tfFiles, dir);
  if (typeErrors.length > 0) {
    results.valid = false;
    results.errors.push(...typeErrors);
  }

  // Try terraform CLI if available
  const hasTerraform = await checkTerraform();
  if (hasTerraform) {
    try {
      const { stdout, stderr } = await runTerraform('init -backend=false', dir);
      console.error('terraform init succeeded');

      try {
        const validate = await runTerraform('validate -json', dir);
        try {
          const validationResult = JSON.parse(validate.stdout);
          if (!validationResult.valid) {
            results.valid = false;
            for (const diag of validationResult.diagnostics || []) {
              const severity = diag.severity || 'error';
              const message = diag.summary || diag.detail || '';
              const location = diag.range
                ? `${diag.range.filename}:${diag.range.start.line}`
                : 'unknown';

              if (severity === 'error') {
                results.errors.push(`${location}: ${message}`);
              } else {
                results.warnings.push(`${location}: ${message}`);
              }
            }
          } else {
            console.error('terraform validate passed');
          }
        } catch (e) {
          // Non-JSON output (older terraform)
          if (validate.stderr && validate.stderr.includes('Error')) {
            results.valid = false;
            results.errors.push(validate.stderr);
          } else {
            console.error('terraform validate succeeded');
          }
        }
      } catch (e) {
        results.warnings.push(`terraform validate failed: ${e.stderr || e.message}`);
      }

      // Cleanup .terraform directory
      try {
        await new Promise((resolve, reject) => {
          exec('rm -rf .terraform', { cwd: dir, timeout: 5000 }, (err) => {
            if (err) reject(err);
            else resolve();
          });
        });
      } catch {
        // Ignore cleanup errors
      }

    } catch (e) {
      results.warnings.push(`terraform init failed: ${e.stderr || e.message}`);
    }
  } else {
    results.warnings.push('terraform CLI not available, skipping provider-validated checks');
  }

  // Strict mode checks
  if (strict) {
    const strictWarnings = checkBestPractices(dir, tfFiles);
    results.warnings.push(...strictWarnings);
  }

  return results;
}

/**
 * Main entry
 */
async function main() {
  console.error(`Validating Terraform module: ${moduleDir}`);

  const results = await validate(moduleDir);

  console.error('\\nValidation Results:');
  console.error(`Status: ${results.valid ? 'PASS' : 'FAIL'}`);

  if (results.errors.length > 0) {
    console.error('\\nErrors:');
    results.errors.forEach(e => console.error(`  ✗ ${e}`));
  }

  if (results.warnings.length > 0) {
    console.error('\\nWarnings:');
    results.warnings.forEach(w => console.error(`  ⚠ ${w}`));
  }

  if (results.valid && results.warnings.length === 0) {
    console.error('\\n✓ Module is valid and follows best practices');
  } else if (results.valid) {
    console.error('\\n✓ Module is valid (with warnings)');
  } else {
    console.error('\\n✗ Module contains errors');
  }

  process.exit(results.valid ? 0 : 1);
}

if (require.main === module) {
  main().catch(err => {
    console.error(`Fatal: ${err.message}`);
    process.exit(1);
  });
}

module.exports = { validate, basicHCLCheck, checkRequiredFiles, checkResourceTypes };


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)