cloudshellgpt 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloudshellgpt/__init__.py +11 -0
- cloudshellgpt/audit.py +175 -0
- cloudshellgpt/bedrock_translator.py +525 -0
- cloudshellgpt/cli.py +612 -0
- cloudshellgpt/config.py +296 -0
- cloudshellgpt/cost.py +443 -0
- cloudshellgpt/executor.py +382 -0
- cloudshellgpt/formatter.py +328 -0
- cloudshellgpt/i18n.py +203 -0
- cloudshellgpt/intent.py +1080 -0
- cloudshellgpt/learning.py +969 -0
- cloudshellgpt/mcp_server.py +264 -0
- cloudshellgpt/safety.py +952 -0
- cloudshellgpt-1.0.0.dist-info/METADATA +426 -0
- cloudshellgpt-1.0.0.dist-info/RECORD +18 -0
- cloudshellgpt-1.0.0.dist-info/WHEEL +4 -0
- cloudshellgpt-1.0.0.dist-info/entry_points.txt +2 -0
- cloudshellgpt-1.0.0.dist-info/licenses/LICENSE +198 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
"""Learning mode — interactive tutorials and command explanations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import boto3
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.prompt import Prompt
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TutorialRunner:
|
|
15
|
+
"""Runs interactive tutorials for AWS services."""
|
|
16
|
+
|
|
17
|
+
TUTORIALS: dict[str, list[dict[str, str]]] = {
|
|
18
|
+
"s3": [
|
|
19
|
+
{
|
|
20
|
+
"title": "S3 — Tu primer bucket",
|
|
21
|
+
"command": "aws s3 mb s3://mi-primer-bucket-unico-12345",
|
|
22
|
+
"explanation": "Crea un bucket. Los nombres son únicos globalmente.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"title": "S3 — Subir un archivo",
|
|
26
|
+
"command": "aws s3 cp archivo.txt s3://mi-primer-bucket-unico-12345/",
|
|
27
|
+
"explanation": "Copia un archivo local al bucket.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"title": "S3 — Listar contenido",
|
|
31
|
+
"command": "aws s3 ls s3://mi-primer-bucket-unico-12345/ --recursive --human-readable",
|
|
32
|
+
"explanation": "Lista archivos con tamaño legible.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"title": "S3 — Generar URL pre-firmada",
|
|
36
|
+
"command": "aws s3 presign s3://mi-primer-bucket-unico-12345/archivo.txt --expires-in 3600",
|
|
37
|
+
"explanation": (
|
|
38
|
+
"Genera una URL temporal (1 hora) para compartir un objeto privado "
|
|
39
|
+
"sin cambiar permisos del bucket. Ideal para dar acceso temporal a "
|
|
40
|
+
"usuarios que no tienen credenciales AWS."
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"title": "S3 — Configurar política de bucket",
|
|
45
|
+
"command": (
|
|
46
|
+
"aws s3api put-bucket-policy --bucket mi-primer-bucket-unico-12345 "
|
|
47
|
+
"--policy file://policy.json"
|
|
48
|
+
),
|
|
49
|
+
"explanation": (
|
|
50
|
+
"Aplica una política JSON al bucket para controlar acceso. "
|
|
51
|
+
"Las políticas de bucket permiten definir reglas granulares: "
|
|
52
|
+
"quién puede leer, escribir o listar objetos. El archivo policy.json "
|
|
53
|
+
"debe seguir el formato de IAM Policy."
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
"ec2": [
|
|
58
|
+
{
|
|
59
|
+
"title": "EC2 — Listar instancias",
|
|
60
|
+
"command": (
|
|
61
|
+
"aws ec2 describe-instances "
|
|
62
|
+
"--query 'Reservations[].Instances[].[InstanceId,State.Name,InstanceType]' "
|
|
63
|
+
"--output table"
|
|
64
|
+
),
|
|
65
|
+
"explanation": (
|
|
66
|
+
"Lista todas las instancias con su ID, estado y tipo. "
|
|
67
|
+
"El flag --query usa JMESPath para filtrar la respuesta JSON "
|
|
68
|
+
"y --output table la muestra en formato tabular legible."
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"title": "EC2 — Lanzar una instancia",
|
|
73
|
+
"command": (
|
|
74
|
+
"aws ec2 run-instances --image-id ami-0c02fb55956c7d316 "
|
|
75
|
+
"--instance-type t2.micro --key-name mi-key-pair "
|
|
76
|
+
"--security-group-ids sg-0123456789abcdef0 --count 1"
|
|
77
|
+
),
|
|
78
|
+
"explanation": (
|
|
79
|
+
"Lanza una instancia EC2. Necesitas: una AMI (imagen base), "
|
|
80
|
+
"un tipo de instancia (t2.micro es capa gratuita), un key pair "
|
|
81
|
+
"para acceso SSH y un security group que defina reglas de red."
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"title": "EC2 — Detener una instancia",
|
|
86
|
+
"command": "aws ec2 stop-instances --instance-ids i-0123456789abcdef0",
|
|
87
|
+
"explanation": (
|
|
88
|
+
"Detiene una instancia sin terminarla. La instancia conserva "
|
|
89
|
+
"su volumen EBS y configuración. Dejas de pagar por cómputo "
|
|
90
|
+
"pero sigues pagando por el almacenamiento EBS asociado."
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"title": "EC2 — Crear security group",
|
|
95
|
+
"command": (
|
|
96
|
+
"aws ec2 create-security-group --group-name mi-sg-web "
|
|
97
|
+
"--description 'Security group para servidor web' "
|
|
98
|
+
"--vpc-id vpc-0123456789abcdef0"
|
|
99
|
+
),
|
|
100
|
+
"explanation": (
|
|
101
|
+
"Crea un security group (firewall virtual). Por defecto permite "
|
|
102
|
+
"todo el tráfico saliente pero bloquea todo el entrante. "
|
|
103
|
+
"Después debes agregar reglas de ingreso con "
|
|
104
|
+
"authorize-security-group-ingress."
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"title": "EC2 — Crear key pair",
|
|
109
|
+
"command": (
|
|
110
|
+
"aws ec2 create-key-pair --key-name mi-key-pair "
|
|
111
|
+
"--query 'KeyMaterial' --output text > mi-key-pair.pem"
|
|
112
|
+
),
|
|
113
|
+
"explanation": (
|
|
114
|
+
"Crea un par de claves para acceso SSH a instancias. "
|
|
115
|
+
"La clave privada solo se muestra una vez al crearla. "
|
|
116
|
+
"Guárdala en un archivo .pem y protégela con chmod 400."
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
"lambda": [
|
|
121
|
+
{
|
|
122
|
+
"title": "Lambda — Crear función",
|
|
123
|
+
"command": (
|
|
124
|
+
"aws lambda create-function --function-name mi-funcion "
|
|
125
|
+
"--runtime python3.12 --role arn:aws:iam::123456789012:role/lambda-role "
|
|
126
|
+
"--handler index.handler --zip-file fileb://function.zip"
|
|
127
|
+
),
|
|
128
|
+
"explanation": (
|
|
129
|
+
"Crea una función Lambda. Necesitas: un IAM role con permisos "
|
|
130
|
+
"de ejecución, un runtime (python3.12), un handler (archivo.función) "
|
|
131
|
+
"y un ZIP con tu código. El role debe tener la política "
|
|
132
|
+
"AWSLambdaBasicExecutionRole como mínimo."
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"title": "Lambda — Invocar función",
|
|
137
|
+
"command": (
|
|
138
|
+
"aws lambda invoke --function-name mi-funcion "
|
|
139
|
+
'--payload \'{"key": "value"}\' --cli-binary-format raw-in-base64-out '
|
|
140
|
+
"response.json"
|
|
141
|
+
),
|
|
142
|
+
"explanation": (
|
|
143
|
+
"Invoca la función de forma síncrona y guarda la respuesta en un archivo. "
|
|
144
|
+
"El flag --cli-binary-format raw-in-base64-out permite enviar JSON plano "
|
|
145
|
+
"sin codificarlo en base64. Útil para probar funciones rápidamente."
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"title": "Lambda — Actualizar código",
|
|
150
|
+
"command": (
|
|
151
|
+
"aws lambda update-function-code --function-name mi-funcion "
|
|
152
|
+
"--zip-file fileb://function.zip --publish"
|
|
153
|
+
),
|
|
154
|
+
"explanation": (
|
|
155
|
+
"Actualiza el código de una función existente con un nuevo ZIP. "
|
|
156
|
+
"El flag --publish crea una nueva versión inmutable, lo que permite "
|
|
157
|
+
"hacer rollback si algo falla. Sin --publish se actualiza solo $LATEST."
|
|
158
|
+
),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"title": "Lambda — Ver logs recientes",
|
|
162
|
+
"command": (
|
|
163
|
+
"aws logs filter-log-events "
|
|
164
|
+
"--log-group-name /aws/lambda/mi-funcion "
|
|
165
|
+
"--start-time $(date -d '10 minutes ago' +%s)000 "
|
|
166
|
+
"--query 'events[].message' --output text"
|
|
167
|
+
),
|
|
168
|
+
"explanation": (
|
|
169
|
+
"Consulta los logs de CloudWatch de tu función Lambda. "
|
|
170
|
+
"Cada invocación genera logs con START, END y REPORT. "
|
|
171
|
+
"El REPORT incluye duración, memoria usada y si hubo errores. "
|
|
172
|
+
"Fundamental para depurar funciones en la nube."
|
|
173
|
+
),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"title": "Lambda — Listar funciones",
|
|
177
|
+
"command": (
|
|
178
|
+
"aws lambda list-functions "
|
|
179
|
+
"--query 'Functions[].[FunctionName,Runtime,LastModified]' "
|
|
180
|
+
"--output table"
|
|
181
|
+
),
|
|
182
|
+
"explanation": (
|
|
183
|
+
"Lista todas las funciones Lambda en la región actual con su nombre, "
|
|
184
|
+
"runtime y última modificación. Útil para auditar qué funciones "
|
|
185
|
+
"tienes desplegadas y cuáles podrían necesitar actualización de runtime."
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
"dynamodb": [
|
|
190
|
+
{
|
|
191
|
+
"title": "DynamoDB — Crear tabla",
|
|
192
|
+
"command": (
|
|
193
|
+
"aws dynamodb create-table --table-name mi-tabla "
|
|
194
|
+
"--attribute-definitions AttributeName=id,AttributeType=S "
|
|
195
|
+
"--key-schema AttributeName=id,KeyType=HASH "
|
|
196
|
+
"--billing-mode PAY_PER_REQUEST"
|
|
197
|
+
),
|
|
198
|
+
"explanation": (
|
|
199
|
+
"Crea una tabla DynamoDB con clave primaria tipo string (S). "
|
|
200
|
+
"PAY_PER_REQUEST significa que pagas solo por las lecturas y "
|
|
201
|
+
"escrituras que hagas, sin provisionar capacidad. Ideal para "
|
|
202
|
+
"cargas de trabajo impredecibles o en desarrollo."
|
|
203
|
+
),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"title": "DynamoDB — Insertar un item",
|
|
207
|
+
"command": (
|
|
208
|
+
"aws dynamodb put-item --table-name mi-tabla "
|
|
209
|
+
'--item \'{"id": {"S": "001"}, "nombre": {"S": "Ejemplo"}, '
|
|
210
|
+
'"activo": {"BOOL": true}}\''
|
|
211
|
+
),
|
|
212
|
+
"explanation": (
|
|
213
|
+
"Inserta un item en la tabla. DynamoDB usa un formato JSON especial "
|
|
214
|
+
"donde cada valor indica su tipo: S (string), N (número), BOOL (booleano), "
|
|
215
|
+
"L (lista), M (mapa). Si el item con esa clave ya existe, se sobrescribe."
|
|
216
|
+
),
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"title": "DynamoDB — Obtener un item",
|
|
220
|
+
"command": (
|
|
221
|
+
"aws dynamodb get-item --table-name mi-tabla "
|
|
222
|
+
'--key \'{"id": {"S": "001"}}\' '
|
|
223
|
+
"--consistent-read"
|
|
224
|
+
),
|
|
225
|
+
"explanation": (
|
|
226
|
+
"Recupera un item por su clave primaria. El flag --consistent-read "
|
|
227
|
+
"garantiza leer la versión más reciente (lectura fuertemente consistente). "
|
|
228
|
+
"Sin ese flag, podrías obtener datos ligeramente desactualizados "
|
|
229
|
+
"pero con menor latencia y costo."
|
|
230
|
+
),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"title": "DynamoDB — Query por clave",
|
|
234
|
+
"command": (
|
|
235
|
+
"aws dynamodb query --table-name mi-tabla "
|
|
236
|
+
"--key-condition-expression 'id = :valor' "
|
|
237
|
+
'--expression-attribute-values \'{":valor": {"S": "001"}}\''
|
|
238
|
+
),
|
|
239
|
+
"explanation": (
|
|
240
|
+
"Query busca items por condición de clave. Es eficiente porque "
|
|
241
|
+
"usa el índice de la tabla. Puedes combinar con --filter-expression "
|
|
242
|
+
"para filtrar resultados adicionales después de la búsqueda por clave."
|
|
243
|
+
),
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"title": "DynamoDB — Scan completo",
|
|
247
|
+
"command": ("aws dynamodb scan --table-name mi-tabla --select COUNT"),
|
|
248
|
+
"explanation": (
|
|
249
|
+
"Scan lee TODOS los items de la tabla. Con --select COUNT solo "
|
|
250
|
+
"devuelve la cantidad sin los datos. Evita scan en producción: "
|
|
251
|
+
"es costoso y lento en tablas grandes. Usa query con condiciones "
|
|
252
|
+
"de clave siempre que sea posible."
|
|
253
|
+
),
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
"iam": [
|
|
257
|
+
{
|
|
258
|
+
"title": "IAM — Listar usuarios",
|
|
259
|
+
"command": (
|
|
260
|
+
"aws iam list-users --query 'Users[].[UserName,CreateDate]' --output table"
|
|
261
|
+
),
|
|
262
|
+
"explanation": (
|
|
263
|
+
"Lista todos los usuarios IAM de la cuenta con su nombre y fecha "
|
|
264
|
+
"de creación. Útil para auditar quién tiene acceso a la cuenta. "
|
|
265
|
+
"Recuerda que el usuario root no aparece aquí."
|
|
266
|
+
),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"title": "IAM — Crear usuario",
|
|
270
|
+
"command": "aws iam create-user --user-name nuevo-desarrollador",
|
|
271
|
+
"explanation": (
|
|
272
|
+
"Crea un nuevo usuario IAM. El usuario se crea sin permisos ni "
|
|
273
|
+
"credenciales. Después necesitas: 1) Adjuntar una política para "
|
|
274
|
+
"dar permisos, 2) Crear access keys o contraseña de consola "
|
|
275
|
+
"según el tipo de acceso que necesite."
|
|
276
|
+
),
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"title": "IAM — Adjuntar política a usuario",
|
|
280
|
+
"command": (
|
|
281
|
+
"aws iam attach-user-policy --user-name nuevo-desarrollador "
|
|
282
|
+
"--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess"
|
|
283
|
+
),
|
|
284
|
+
"explanation": (
|
|
285
|
+
"Adjunta una política administrada al usuario. Las políticas "
|
|
286
|
+
"administradas por AWS (como ReadOnlyAccess) son mantenidas "
|
|
287
|
+
"por Amazon. También puedes crear políticas personalizadas. "
|
|
288
|
+
"Principio de mínimo privilegio: da solo los permisos necesarios."
|
|
289
|
+
),
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"title": "IAM — Crear rol",
|
|
293
|
+
"command": (
|
|
294
|
+
"aws iam create-role --role-name mi-rol-lambda "
|
|
295
|
+
"--assume-role-policy-document file://trust-policy.json"
|
|
296
|
+
),
|
|
297
|
+
"explanation": (
|
|
298
|
+
"Crea un rol IAM. Los roles son identidades que los servicios AWS "
|
|
299
|
+
"(como Lambda o EC2) asumen para obtener permisos temporales. "
|
|
300
|
+
"El trust-policy.json define QUIÉN puede asumir el rol "
|
|
301
|
+
"(ej: lambda.amazonaws.com)."
|
|
302
|
+
),
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"title": "IAM — Listar roles",
|
|
306
|
+
"command": ("aws iam list-roles --query 'Roles[].[RoleName,Arn]' --output table"),
|
|
307
|
+
"explanation": (
|
|
308
|
+
"Lista todos los roles IAM con su nombre y ARN. "
|
|
309
|
+
"Los roles son fundamentales en AWS: cada servicio que necesite "
|
|
310
|
+
"interactuar con otros servicios usa un rol. Revisa periódicamente "
|
|
311
|
+
"para eliminar roles no utilizados (principio de mínimo privilegio)."
|
|
312
|
+
),
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
"vpc": [
|
|
316
|
+
{
|
|
317
|
+
"title": "VPC — Listar VPCs existentes",
|
|
318
|
+
"command": (
|
|
319
|
+
"aws ec2 describe-vpcs "
|
|
320
|
+
"--query 'Vpcs[].[VpcId,CidrBlock,IsDefault]' --output table"
|
|
321
|
+
),
|
|
322
|
+
"explanation": (
|
|
323
|
+
"Lista todas las VPCs de la región con su ID, rango CIDR y si es "
|
|
324
|
+
"la VPC por defecto. Toda cuenta AWS tiene una VPC default en cada "
|
|
325
|
+
"región. Las VPCs son redes virtuales aisladas donde viven tus recursos."
|
|
326
|
+
),
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"title": "VPC — Crear una VPC",
|
|
330
|
+
"command": (
|
|
331
|
+
"aws ec2 create-vpc --cidr-block 10.0.0.0/16 "
|
|
332
|
+
"--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=mi-vpc}]'"
|
|
333
|
+
),
|
|
334
|
+
"explanation": (
|
|
335
|
+
"Crea una VPC con un bloque CIDR /16 (65,536 IPs). "
|
|
336
|
+
"El CIDR define el rango de IPs privadas disponibles. "
|
|
337
|
+
"Después necesitas crear subnets, un Internet Gateway "
|
|
338
|
+
"y tablas de ruteo para que los recursos tengan conectividad."
|
|
339
|
+
),
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"title": "VPC — Crear subnet",
|
|
343
|
+
"command": (
|
|
344
|
+
"aws ec2 create-subnet --vpc-id vpc-0123456789abcdef0 "
|
|
345
|
+
"--cidr-block 10.0.1.0/24 --availability-zone us-east-1a"
|
|
346
|
+
),
|
|
347
|
+
"explanation": (
|
|
348
|
+
"Crea una subnet dentro de la VPC en una zona de disponibilidad "
|
|
349
|
+
"específica. Un /24 da 256 IPs (251 usables, AWS reserva 5). "
|
|
350
|
+
"Las subnets pueden ser públicas (con ruta a Internet Gateway) "
|
|
351
|
+
"o privadas (sin acceso directo a internet)."
|
|
352
|
+
),
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"title": "VPC — Crear security group en VPC",
|
|
356
|
+
"command": (
|
|
357
|
+
"aws ec2 create-security-group --group-name sg-web "
|
|
358
|
+
"--description 'Permite HTTP y HTTPS' "
|
|
359
|
+
"--vpc-id vpc-0123456789abcdef0"
|
|
360
|
+
),
|
|
361
|
+
"explanation": (
|
|
362
|
+
"Crea un security group asociado a tu VPC. Los security groups "
|
|
363
|
+
"actúan como firewalls virtuales a nivel de instancia. "
|
|
364
|
+
"Por defecto permiten todo el tráfico saliente y bloquean "
|
|
365
|
+
"todo el entrante. Debes agregar reglas de ingreso explícitas."
|
|
366
|
+
),
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"title": "VPC — Ver security groups",
|
|
370
|
+
"command": (
|
|
371
|
+
"aws ec2 describe-security-groups "
|
|
372
|
+
"--filters Name=vpc-id,Values=vpc-0123456789abcdef0 "
|
|
373
|
+
"--query 'SecurityGroups[].[GroupId,GroupName,Description]' "
|
|
374
|
+
"--output table"
|
|
375
|
+
),
|
|
376
|
+
"explanation": (
|
|
377
|
+
"Lista los security groups de una VPC específica. "
|
|
378
|
+
"Filtra por vpc-id para ver solo los grupos relevantes. "
|
|
379
|
+
"Revisa periódicamente las reglas para asegurar que no haya "
|
|
380
|
+
"puertos innecesariamente abiertos (buena práctica de seguridad)."
|
|
381
|
+
),
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
def __init__(self, topic: str) -> None:
|
|
387
|
+
self.topic = topic
|
|
388
|
+
self.console = Console()
|
|
389
|
+
|
|
390
|
+
def run(self) -> None:
|
|
391
|
+
"""Run the tutorial interactively."""
|
|
392
|
+
if self.topic not in self.TUTORIALS:
|
|
393
|
+
available = ", ".join(self.TUTORIALS.keys())
|
|
394
|
+
self.console.print(f"[red]Unknown topic: {self.topic}[/red]")
|
|
395
|
+
self.console.print(f"[yellow]Available: {available}[/yellow]")
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
self.console.print(
|
|
399
|
+
Panel(
|
|
400
|
+
f"[bold]Tutorial: {self.topic.upper()}[/bold]\n"
|
|
401
|
+
f"Aprenderás {len(self.TUTORIALS[self.topic])} comandos esenciales.",
|
|
402
|
+
border_style="cyan",
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
for i, step in enumerate(self.TUTORIALS[self.topic], 1):
|
|
407
|
+
self.console.print(
|
|
408
|
+
Panel(
|
|
409
|
+
f"[bold]{step['title']}[/bold]\n\n"
|
|
410
|
+
f"[cyan]{step['command']}[/cyan]\n\n"
|
|
411
|
+
f"{step['explanation']}",
|
|
412
|
+
title=f"Step {i}/{len(self.TUTORIALS[self.topic])}",
|
|
413
|
+
border_style="blue",
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
action = Prompt.ask(
|
|
417
|
+
"Press Enter to continue, 'r' to run, 'q' to quit",
|
|
418
|
+
default="",
|
|
419
|
+
)
|
|
420
|
+
if action == "q":
|
|
421
|
+
break
|
|
422
|
+
elif action == "r":
|
|
423
|
+
# Optionally execute via executor
|
|
424
|
+
self.console.print(f"[dim]Would execute: {step['command']}[/dim]")
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class Explainer:
|
|
428
|
+
"""Explains what AWS CLI commands do in detail."""
|
|
429
|
+
|
|
430
|
+
MODEL_ID = "us.anthropic.claude-sonnet-4-6"
|
|
431
|
+
REGION = "us-east-1"
|
|
432
|
+
|
|
433
|
+
EXPLAIN_SYSTEM_PROMPT = (
|
|
434
|
+
"Explain AWS CLI commands in detail. For each command, break down:\n"
|
|
435
|
+
"1. What service and operation it uses\n"
|
|
436
|
+
"2. Each non-obvious flag and its purpose\n"
|
|
437
|
+
"3. The expected output format\n"
|
|
438
|
+
"4. Common pitfalls\n"
|
|
439
|
+
"5. Link to relevant AWS docs (markdown format)\n\n"
|
|
440
|
+
"Provide a clear, educational explanation. Use markdown."
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def __init__(self, region: str = REGION, *, model_id: str = MODEL_ID) -> None:
|
|
444
|
+
self.console = Console()
|
|
445
|
+
self.model_id = model_id
|
|
446
|
+
self.bedrock = boto3.client("bedrock-runtime", region_name=region)
|
|
447
|
+
|
|
448
|
+
def explain_sync(self, command: str) -> str:
|
|
449
|
+
"""Generate a detailed explanation of a command (sync, for MCP).
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
command: The AWS CLI command to explain.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
A markdown-formatted explanation string.
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
response = self.bedrock.converse(
|
|
459
|
+
modelId=self.model_id,
|
|
460
|
+
messages=[{"role": "user", "content": [{"text": command}]}],
|
|
461
|
+
system=[{"text": self.EXPLAIN_SYSTEM_PROMPT}],
|
|
462
|
+
inferenceConfig={"maxTokens": 1024, "temperature": 0.3},
|
|
463
|
+
)
|
|
464
|
+
result: str = response["output"]["message"]["content"][0]["text"]
|
|
465
|
+
return result
|
|
466
|
+
except Exception as e:
|
|
467
|
+
return f"Error explaining command: {e}"
|
|
468
|
+
|
|
469
|
+
def explain(self, command: str) -> None:
|
|
470
|
+
"""Explain a command interactively."""
|
|
471
|
+
explanation = self.explain_sync(command)
|
|
472
|
+
self.console.print(
|
|
473
|
+
Panel(explanation, title="[bold]Explanation[/bold]", border_style="green")
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
def explain_last(self) -> None:
|
|
477
|
+
"""Explain the last command from audit log."""
|
|
478
|
+
from cloudshellgpt.audit import AuditLogger
|
|
479
|
+
|
|
480
|
+
audit = AuditLogger()
|
|
481
|
+
entries = audit.tail(1)
|
|
482
|
+
if not entries:
|
|
483
|
+
self.console.print("[yellow]No previous commands found[/yellow]")
|
|
484
|
+
return
|
|
485
|
+
|
|
486
|
+
last_command = entries[-1]["command"]
|
|
487
|
+
self.explain(str(last_command))
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# ---------------------------------------------------------------------------
|
|
491
|
+
# Pydantic models for learning features
|
|
492
|
+
# ---------------------------------------------------------------------------
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class FlagExplanation(BaseModel):
|
|
496
|
+
"""Explanation of a single CLI flag/option."""
|
|
497
|
+
|
|
498
|
+
flag: str
|
|
499
|
+
explanation: str
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class RelatedSuggestion(BaseModel):
|
|
503
|
+
"""A related command suggestion with description."""
|
|
504
|
+
|
|
505
|
+
command: str
|
|
506
|
+
description: str
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# ---------------------------------------------------------------------------
|
|
510
|
+
# PostExecutionTips — rule-based educational tips after command execution
|
|
511
|
+
# ---------------------------------------------------------------------------
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
class PostExecutionTips:
|
|
515
|
+
"""Provides educational tips after a command runs.
|
|
516
|
+
|
|
517
|
+
Uses a static dictionary of tips keyed by service+action.
|
|
518
|
+
No Bedrock call needed — purely rule-based.
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
TIPS: dict[str, str] = {
|
|
522
|
+
# S3
|
|
523
|
+
"s3 ls": ("Tip: Use --human-readable for friendlier sizes"),
|
|
524
|
+
"s3 cp": ("Tip: Use --recursive to copy entire directories"),
|
|
525
|
+
"s3 sync": ("Tip: Use --delete to remove files in dest that don't exist in source"),
|
|
526
|
+
"s3 mb": ("Tip: Bucket names must be globally unique across all AWS accounts"),
|
|
527
|
+
"s3 rb": ("Tip: Use --force to delete a non-empty bucket"),
|
|
528
|
+
"s3 rm": ("Tip: Use --recursive to delete all objects in a prefix"),
|
|
529
|
+
"s3api list-buckets": ("Tip: Use --query 'Buckets[].Name' to get just bucket names"),
|
|
530
|
+
"s3api put-object": ("Tip: Use --server-side-encryption AES256 to encrypt at rest"),
|
|
531
|
+
# EC2
|
|
532
|
+
"ec2 describe-instances": ("Tip: Use --query to filter output with JMESPath"),
|
|
533
|
+
"ec2 run-instances": ("Tip: Use --dry-run first to verify permissions without launching"),
|
|
534
|
+
"ec2 start-instances": (
|
|
535
|
+
"Tip: Use --instance-ids with multiple IDs to start several at once"
|
|
536
|
+
),
|
|
537
|
+
"ec2 stop-instances": ("Tip: Use --hibernate to save instance memory state"),
|
|
538
|
+
"ec2 terminate-instances": (
|
|
539
|
+
"Tip: Enable termination protection to prevent accidental deletion"
|
|
540
|
+
),
|
|
541
|
+
"ec2 describe-security-groups": (
|
|
542
|
+
"Tip: Use --filters Name=group-name,Values=my-sg to narrow results"
|
|
543
|
+
),
|
|
544
|
+
"ec2 create-security-group": (
|
|
545
|
+
"Tip: Remember to add ingress rules — new groups deny all inbound"
|
|
546
|
+
),
|
|
547
|
+
# Lambda
|
|
548
|
+
"lambda list-functions": ("Tip: Use --query 'Functions[].FunctionName' for just names"),
|
|
549
|
+
"lambda invoke": ("Tip: Use --log-type Tail to see execution logs inline"),
|
|
550
|
+
"lambda create-function": ("Tip: Use --timeout and --memory-size to tune performance"),
|
|
551
|
+
"lambda update-function-code": ("Tip: Use --publish to create a new version on update"),
|
|
552
|
+
# IAM
|
|
553
|
+
"iam list-users": ("Tip: Use --query 'Users[].UserName' for a clean list"),
|
|
554
|
+
"iam create-user": ("Tip: Don't forget to attach a policy — new users have no permissions"),
|
|
555
|
+
"iam list-roles": ("Tip: Use --path-prefix to filter roles by organizational path"),
|
|
556
|
+
# DynamoDB
|
|
557
|
+
"dynamodb list-tables": ("Tip: Use --query 'TableNames' to get just the names"),
|
|
558
|
+
"dynamodb scan": ("Tip: Avoid scan in production — use query with key conditions instead"),
|
|
559
|
+
"dynamodb put-item": (
|
|
560
|
+
"Tip: Use --condition-expression to prevent overwriting existing items"
|
|
561
|
+
),
|
|
562
|
+
# CloudFormation
|
|
563
|
+
"cloudformation list-stacks": ("Tip: Use --stack-status-filter to show only active stacks"),
|
|
564
|
+
"cloudformation deploy": (
|
|
565
|
+
"Tip: Use --no-execute-changeset to preview changes before applying"
|
|
566
|
+
),
|
|
567
|
+
# RDS
|
|
568
|
+
"rds describe-db-instances": (
|
|
569
|
+
"Tip: Use --query to extract specific fields like endpoint and status"
|
|
570
|
+
),
|
|
571
|
+
"rds create-db-instance": ("Tip: Use --multi-az for production workloads"),
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
def get_tip(self, command: str) -> str | None:
|
|
575
|
+
"""Return a contextual tip for the given command, or None.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
command: The AWS CLI command that was executed (e.g. "aws s3 ls ...").
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
A tip string if one matches the service+action, otherwise None.
|
|
582
|
+
"""
|
|
583
|
+
try:
|
|
584
|
+
normalized = self._extract_service_action(command)
|
|
585
|
+
if normalized:
|
|
586
|
+
return self.TIPS.get(normalized)
|
|
587
|
+
except Exception:
|
|
588
|
+
pass
|
|
589
|
+
return None
|
|
590
|
+
|
|
591
|
+
def _extract_service_action(self, command: str) -> str | None:
|
|
592
|
+
"""Extract service and action from an AWS CLI command.
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
command: Full AWS CLI command string.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
A string like "s3 ls" or "ec2 describe-instances", or None.
|
|
599
|
+
"""
|
|
600
|
+
# Remove leading "aws " if present
|
|
601
|
+
stripped = command.strip()
|
|
602
|
+
if stripped.startswith("aws "):
|
|
603
|
+
stripped = stripped[4:]
|
|
604
|
+
|
|
605
|
+
parts = stripped.split()
|
|
606
|
+
if len(parts) < 2:
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
service = parts[0]
|
|
610
|
+
action = parts[1]
|
|
611
|
+
|
|
612
|
+
# Skip if action looks like a flag
|
|
613
|
+
if action.startswith("-"):
|
|
614
|
+
return None
|
|
615
|
+
|
|
616
|
+
return f"{service} {action}"
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
# ---------------------------------------------------------------------------
|
|
620
|
+
# FlagExplainer — rule-based explanation of common AWS CLI flags
|
|
621
|
+
# ---------------------------------------------------------------------------
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class FlagExplainer:
|
|
625
|
+
"""Explains each flag/option used in an AWS CLI command.
|
|
626
|
+
|
|
627
|
+
Rule-based: uses a dictionary of common AWS CLI flags and their explanations.
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
FLAG_DEFINITIONS: dict[str, str] = {
|
|
631
|
+
"--output": "Sets the output format (json, text, table, yaml)",
|
|
632
|
+
"--query": "Filters output using JMESPath expressions",
|
|
633
|
+
"--region": "Overrides the default AWS region for this command",
|
|
634
|
+
"--profile": "Uses a named profile from ~/.aws/credentials",
|
|
635
|
+
"--no-paginate": "Disables automatic pagination of results",
|
|
636
|
+
"--dry-run": "Checks permissions without actually executing the command",
|
|
637
|
+
"--recursive": "Applies the operation to all objects under a prefix",
|
|
638
|
+
"--force": "Skips confirmation prompts and forces the operation",
|
|
639
|
+
"--human-readable": "Displays file sizes in human-readable format (KB, MB, GB)",
|
|
640
|
+
"--filters": "Applies server-side filters to narrow results",
|
|
641
|
+
"--max-items": "Limits the total number of items returned",
|
|
642
|
+
"--page-size": "Controls how many items are fetched per API call",
|
|
643
|
+
"--no-verify-ssl": "Disables SSL certificate verification (not recommended)",
|
|
644
|
+
"--endpoint-url": "Overrides the service endpoint URL",
|
|
645
|
+
"--cli-input-json": "Reads parameters from a JSON file",
|
|
646
|
+
"--cli-input-yaml": "Reads parameters from a YAML file",
|
|
647
|
+
"--generate-cli-skeleton": "Generates a JSON skeleton for the command input",
|
|
648
|
+
"--no-sign-request": "Sends request without signing (for public resources)",
|
|
649
|
+
"--debug": "Enables debug logging for troubleshooting",
|
|
650
|
+
"--color": "Controls colored output (on, off, auto)",
|
|
651
|
+
"--no-cli-pager": "Disables the CLI pager for output",
|
|
652
|
+
"--instance-ids": "Specifies one or more EC2 instance IDs",
|
|
653
|
+
"--function-name": "Specifies the Lambda function name or ARN",
|
|
654
|
+
"--bucket": "Specifies the S3 bucket name",
|
|
655
|
+
"--key": "Specifies the S3 object key",
|
|
656
|
+
"--table-name": "Specifies the DynamoDB table name",
|
|
657
|
+
"--stack-name": "Specifies the CloudFormation stack name",
|
|
658
|
+
"--role-arn": "Specifies the IAM role ARN to assume",
|
|
659
|
+
"--tags": "Adds key-value tags to the resource",
|
|
660
|
+
"--server-side-encryption": "Enables server-side encryption for stored objects",
|
|
661
|
+
"--acl": "Sets the access control list for the resource",
|
|
662
|
+
"--include": "Includes only files matching the pattern",
|
|
663
|
+
"--exclude": "Excludes files matching the pattern",
|
|
664
|
+
"--delete": "Removes files in destination not in source (sync)",
|
|
665
|
+
"--exact-timestamps": "Compares exact timestamps during sync (not just size)",
|
|
666
|
+
"--sse": "Specifies server-side encryption algorithm",
|
|
667
|
+
"--storage-class": "Sets the S3 storage class (STANDARD, IA, GLACIER, etc.)",
|
|
668
|
+
"--content-type": "Sets the MIME type of the uploaded object",
|
|
669
|
+
"--metadata": "Adds custom metadata to the object",
|
|
670
|
+
"--grants": "Sets permissions using grant format",
|
|
671
|
+
"--source-region": "Specifies the source region for cross-region copies",
|
|
672
|
+
"--copy-source": "Specifies the source object for server-side copy",
|
|
673
|
+
"--multi-az": "Enables Multi-AZ deployment for high availability",
|
|
674
|
+
"--publicly-accessible": "Makes the resource accessible from the internet",
|
|
675
|
+
"--no-publicly-accessible": "Blocks public access to the resource",
|
|
676
|
+
"--skip-final-snapshot": "Skips creating a final snapshot before deletion",
|
|
677
|
+
"--force-delete": "Forces deletion without recovery options",
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
def explain_flags(self, command: str) -> list[FlagExplanation]:
|
|
681
|
+
"""Explain each flag/option in the given AWS CLI command.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
command: The AWS CLI command with flags to explain.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
A list of FlagExplanation objects for each recognized flag.
|
|
688
|
+
"""
|
|
689
|
+
try:
|
|
690
|
+
flags = self._extract_flags(command)
|
|
691
|
+
explanations: list[FlagExplanation] = []
|
|
692
|
+
for flag in flags:
|
|
693
|
+
definition = self.FLAG_DEFINITIONS.get(flag)
|
|
694
|
+
if definition:
|
|
695
|
+
explanations.append(FlagExplanation(flag=flag, explanation=definition))
|
|
696
|
+
return explanations
|
|
697
|
+
except Exception:
|
|
698
|
+
return []
|
|
699
|
+
|
|
700
|
+
def _extract_flags(self, command: str) -> list[str]:
|
|
701
|
+
"""Extract all --flags from a command string.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
command: Full AWS CLI command.
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
List of unique flag names (e.g. ["--output", "--query"]).
|
|
708
|
+
"""
|
|
709
|
+
# Match --flag-name patterns (long flags only)
|
|
710
|
+
matches = re.findall(r"(--[a-zA-Z][a-zA-Z0-9-]*)", command)
|
|
711
|
+
# Deduplicate while preserving order
|
|
712
|
+
seen: set[str] = set()
|
|
713
|
+
unique: list[str] = []
|
|
714
|
+
for flag in matches:
|
|
715
|
+
if flag not in seen:
|
|
716
|
+
seen.add(flag)
|
|
717
|
+
unique.append(flag)
|
|
718
|
+
return unique
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
# ---------------------------------------------------------------------------
|
|
722
|
+
# RelatedCommands — suggests related commands based on the current one
|
|
723
|
+
# ---------------------------------------------------------------------------
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
class RelatedCommands:
|
|
727
|
+
"""Suggests related commands the user might want to run next.
|
|
728
|
+
|
|
729
|
+
Rule-based mapping of service+action to related commands.
|
|
730
|
+
"""
|
|
731
|
+
|
|
732
|
+
RELATED: dict[str, list[RelatedSuggestion]] = {
|
|
733
|
+
# S3
|
|
734
|
+
"s3 ls": [
|
|
735
|
+
RelatedSuggestion(
|
|
736
|
+
command="aws s3 cp",
|
|
737
|
+
description="Copy files to/from S3",
|
|
738
|
+
),
|
|
739
|
+
RelatedSuggestion(
|
|
740
|
+
command="aws s3 sync",
|
|
741
|
+
description="Sync a local directory with S3",
|
|
742
|
+
),
|
|
743
|
+
RelatedSuggestion(
|
|
744
|
+
command="aws s3 presign",
|
|
745
|
+
description="Generate a pre-signed URL for temporary access",
|
|
746
|
+
),
|
|
747
|
+
],
|
|
748
|
+
"s3 cp": [
|
|
749
|
+
RelatedSuggestion(
|
|
750
|
+
command="aws s3 ls",
|
|
751
|
+
description="List objects to verify the copy",
|
|
752
|
+
),
|
|
753
|
+
RelatedSuggestion(
|
|
754
|
+
command="aws s3 sync",
|
|
755
|
+
description="Sync entire directories instead of single files",
|
|
756
|
+
),
|
|
757
|
+
],
|
|
758
|
+
"s3 sync": [
|
|
759
|
+
RelatedSuggestion(
|
|
760
|
+
command="aws s3 ls",
|
|
761
|
+
description="List objects to verify sync results",
|
|
762
|
+
),
|
|
763
|
+
RelatedSuggestion(
|
|
764
|
+
command="aws s3 rm --recursive",
|
|
765
|
+
description="Remove all objects under a prefix",
|
|
766
|
+
),
|
|
767
|
+
],
|
|
768
|
+
"s3 mb": [
|
|
769
|
+
RelatedSuggestion(
|
|
770
|
+
command="aws s3 ls",
|
|
771
|
+
description="List all buckets to verify creation",
|
|
772
|
+
),
|
|
773
|
+
RelatedSuggestion(
|
|
774
|
+
command="aws s3 cp",
|
|
775
|
+
description="Upload your first file to the new bucket",
|
|
776
|
+
),
|
|
777
|
+
],
|
|
778
|
+
"s3 rb": [
|
|
779
|
+
RelatedSuggestion(
|
|
780
|
+
command="aws s3 ls",
|
|
781
|
+
description="Verify the bucket was removed",
|
|
782
|
+
),
|
|
783
|
+
],
|
|
784
|
+
# EC2
|
|
785
|
+
"ec2 describe-instances": [
|
|
786
|
+
RelatedSuggestion(
|
|
787
|
+
command="aws ec2 start-instances",
|
|
788
|
+
description="Start stopped instances",
|
|
789
|
+
),
|
|
790
|
+
RelatedSuggestion(
|
|
791
|
+
command="aws ec2 stop-instances",
|
|
792
|
+
description="Stop running instances",
|
|
793
|
+
),
|
|
794
|
+
RelatedSuggestion(
|
|
795
|
+
command="aws ec2 create-tags",
|
|
796
|
+
description="Tag instances for organization",
|
|
797
|
+
),
|
|
798
|
+
],
|
|
799
|
+
"ec2 run-instances": [
|
|
800
|
+
RelatedSuggestion(
|
|
801
|
+
command="aws ec2 describe-instances",
|
|
802
|
+
description="Check the status of your new instance",
|
|
803
|
+
),
|
|
804
|
+
RelatedSuggestion(
|
|
805
|
+
command="aws ec2 create-tags",
|
|
806
|
+
description="Tag the instance for organization",
|
|
807
|
+
),
|
|
808
|
+
RelatedSuggestion(
|
|
809
|
+
command="aws ec2 describe-security-groups",
|
|
810
|
+
description="Verify security group rules",
|
|
811
|
+
),
|
|
812
|
+
],
|
|
813
|
+
"ec2 stop-instances": [
|
|
814
|
+
RelatedSuggestion(
|
|
815
|
+
command="aws ec2 start-instances",
|
|
816
|
+
description="Start the instances again",
|
|
817
|
+
),
|
|
818
|
+
RelatedSuggestion(
|
|
819
|
+
command="aws ec2 describe-instances",
|
|
820
|
+
description="Verify the instances are stopped",
|
|
821
|
+
),
|
|
822
|
+
],
|
|
823
|
+
"ec2 terminate-instances": [
|
|
824
|
+
RelatedSuggestion(
|
|
825
|
+
command="aws ec2 describe-instances",
|
|
826
|
+
description="Verify instances are terminated",
|
|
827
|
+
),
|
|
828
|
+
RelatedSuggestion(
|
|
829
|
+
command="aws ec2 release-address",
|
|
830
|
+
description="Release any associated Elastic IPs",
|
|
831
|
+
),
|
|
832
|
+
],
|
|
833
|
+
"ec2 create-security-group": [
|
|
834
|
+
RelatedSuggestion(
|
|
835
|
+
command="aws ec2 authorize-security-group-ingress",
|
|
836
|
+
description="Add inbound rules to the new group",
|
|
837
|
+
),
|
|
838
|
+
RelatedSuggestion(
|
|
839
|
+
command="aws ec2 describe-security-groups",
|
|
840
|
+
description="Verify the group was created",
|
|
841
|
+
),
|
|
842
|
+
],
|
|
843
|
+
# Lambda
|
|
844
|
+
"lambda list-functions": [
|
|
845
|
+
RelatedSuggestion(
|
|
846
|
+
command="aws lambda invoke",
|
|
847
|
+
description="Invoke a function to test it",
|
|
848
|
+
),
|
|
849
|
+
RelatedSuggestion(
|
|
850
|
+
command="aws lambda get-function",
|
|
851
|
+
description="Get details about a specific function",
|
|
852
|
+
),
|
|
853
|
+
],
|
|
854
|
+
"lambda create-function": [
|
|
855
|
+
RelatedSuggestion(
|
|
856
|
+
command="aws lambda invoke",
|
|
857
|
+
description="Test the new function",
|
|
858
|
+
),
|
|
859
|
+
RelatedSuggestion(
|
|
860
|
+
command="aws lambda list-functions",
|
|
861
|
+
description="Verify the function appears in the list",
|
|
862
|
+
),
|
|
863
|
+
],
|
|
864
|
+
"lambda invoke": [
|
|
865
|
+
RelatedSuggestion(
|
|
866
|
+
command="aws logs filter-log-events",
|
|
867
|
+
description="Check CloudWatch logs for execution details",
|
|
868
|
+
),
|
|
869
|
+
RelatedSuggestion(
|
|
870
|
+
command="aws lambda update-function-code",
|
|
871
|
+
description="Update the function code",
|
|
872
|
+
),
|
|
873
|
+
],
|
|
874
|
+
# IAM
|
|
875
|
+
"iam list-users": [
|
|
876
|
+
RelatedSuggestion(
|
|
877
|
+
command="aws iam list-user-policies",
|
|
878
|
+
description="Check policies attached to a user",
|
|
879
|
+
),
|
|
880
|
+
RelatedSuggestion(
|
|
881
|
+
command="aws iam create-user",
|
|
882
|
+
description="Create a new IAM user",
|
|
883
|
+
),
|
|
884
|
+
],
|
|
885
|
+
"iam create-user": [
|
|
886
|
+
RelatedSuggestion(
|
|
887
|
+
command="aws iam attach-user-policy",
|
|
888
|
+
description="Attach a policy to the new user",
|
|
889
|
+
),
|
|
890
|
+
RelatedSuggestion(
|
|
891
|
+
command="aws iam create-access-key",
|
|
892
|
+
description="Create access keys for programmatic access",
|
|
893
|
+
),
|
|
894
|
+
],
|
|
895
|
+
# DynamoDB
|
|
896
|
+
"dynamodb list-tables": [
|
|
897
|
+
RelatedSuggestion(
|
|
898
|
+
command="aws dynamodb describe-table",
|
|
899
|
+
description="Get details about a specific table",
|
|
900
|
+
),
|
|
901
|
+
RelatedSuggestion(
|
|
902
|
+
command="aws dynamodb scan",
|
|
903
|
+
description="Read all items from a table",
|
|
904
|
+
),
|
|
905
|
+
],
|
|
906
|
+
"dynamodb scan": [
|
|
907
|
+
RelatedSuggestion(
|
|
908
|
+
command="aws dynamodb query",
|
|
909
|
+
description="Query items by key condition (more efficient)",
|
|
910
|
+
),
|
|
911
|
+
RelatedSuggestion(
|
|
912
|
+
command="aws dynamodb put-item",
|
|
913
|
+
description="Insert a new item into the table",
|
|
914
|
+
),
|
|
915
|
+
],
|
|
916
|
+
# RDS
|
|
917
|
+
"rds describe-db-instances": [
|
|
918
|
+
RelatedSuggestion(
|
|
919
|
+
command="aws rds create-db-snapshot",
|
|
920
|
+
description="Create a snapshot for backup",
|
|
921
|
+
),
|
|
922
|
+
RelatedSuggestion(
|
|
923
|
+
command="aws rds modify-db-instance",
|
|
924
|
+
description="Modify instance settings",
|
|
925
|
+
),
|
|
926
|
+
],
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
def suggest(self, command: str) -> list[RelatedSuggestion]:
|
|
930
|
+
"""Suggest related commands based on the current one.
|
|
931
|
+
|
|
932
|
+
Args:
|
|
933
|
+
command: The AWS CLI command that was executed.
|
|
934
|
+
|
|
935
|
+
Returns:
|
|
936
|
+
A list of related command suggestions, or an empty list.
|
|
937
|
+
"""
|
|
938
|
+
try:
|
|
939
|
+
key = self._extract_service_action(command)
|
|
940
|
+
if key:
|
|
941
|
+
return self.RELATED.get(key, [])
|
|
942
|
+
except Exception:
|
|
943
|
+
pass
|
|
944
|
+
return []
|
|
945
|
+
|
|
946
|
+
def _extract_service_action(self, command: str) -> str | None:
|
|
947
|
+
"""Extract service and action from an AWS CLI command.
|
|
948
|
+
|
|
949
|
+
Args:
|
|
950
|
+
command: Full AWS CLI command string.
|
|
951
|
+
|
|
952
|
+
Returns:
|
|
953
|
+
A string like "s3 ls" or "ec2 run-instances", or None.
|
|
954
|
+
"""
|
|
955
|
+
stripped = command.strip()
|
|
956
|
+
if stripped.startswith("aws "):
|
|
957
|
+
stripped = stripped[4:]
|
|
958
|
+
|
|
959
|
+
parts = stripped.split()
|
|
960
|
+
if len(parts) < 2:
|
|
961
|
+
return None
|
|
962
|
+
|
|
963
|
+
service = parts[0]
|
|
964
|
+
action = parts[1]
|
|
965
|
+
|
|
966
|
+
if action.startswith("-"):
|
|
967
|
+
return None
|
|
968
|
+
|
|
969
|
+
return f"{service} {action}"
|