@icarusmx/creta 1.5.9 → 1.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/creta.js +69 -7
- package/lib/data/command-help/curl.js +80 -0
- package/lib/data/command-help/index.js +2 -0
- package/lib/data/lessons/sintaxis/curl-pipes.js +221 -0
- package/lib/data/menus.js +6 -0
- package/lib/executors/sintaxis-executor.js +3 -1
- package/lib/exercises/curl-and-pipes.md +844 -0
- package/package.json +1 -1
package/bin/creta.js
CHANGED
|
@@ -13,6 +13,7 @@ import { CommandHelpExecutor } from '../lib/executors/CommandHelpExecutor.js'
|
|
|
13
13
|
import { PapersExecutor } from '../lib/papers/PapersExecutor.js'
|
|
14
14
|
import { ExercisesExecutor } from '../lib/executors/ExercisesExecutor.js'
|
|
15
15
|
import { executeSintaxis } from '../lib/executors/sintaxis-executor.js'
|
|
16
|
+
import { UserState } from '../lib/utils/user-state.js'
|
|
16
17
|
import { LessonBuilder } from '../lib/builders/LessonBuilder.js'
|
|
17
18
|
import { LESSON_1_SYSTEM_DECOMPOSITION } from '../lib/data/lessons/lesson1-system-decomposition.js'
|
|
18
19
|
import { LESSON_2_OBJECT_REQUESTS } from '../lib/data/lessons/lesson2-object-requests.js'
|
|
@@ -77,8 +78,9 @@ if (command && isCommandHelpRequest(args)) {
|
|
|
77
78
|
process.exit(0)
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
-
|
|
81
|
+
// Show logo for interactive commands (skip for utility commands)
|
|
82
|
+
function showLogo() {
|
|
83
|
+
console.log(`
|
|
82
84
|
█████████████████████████
|
|
83
85
|
█ █ █ █ █ █ █
|
|
84
86
|
█ C █ R █ E █ T █ A █ █ █
|
|
@@ -88,16 +90,19 @@ console.log(`
|
|
|
88
90
|
Bienvenido al taller de software
|
|
89
91
|
Salgamos de este laberinto 🏛️
|
|
90
92
|
`)
|
|
93
|
+
}
|
|
91
94
|
|
|
92
95
|
if (!command) {
|
|
93
96
|
// Default behavior: show main menu
|
|
97
|
+
showLogo()
|
|
94
98
|
await startMainMenu()
|
|
95
99
|
process.exit(0)
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
if (command.startsWith('portafolio')) {
|
|
103
|
+
showLogo()
|
|
99
104
|
const level = command === 'portafolio' ? 0 : parseInt(command.split('-')[1]) || 0
|
|
100
|
-
|
|
105
|
+
|
|
101
106
|
// Check if we're in an existing Creta project
|
|
102
107
|
if (level > 0 && isInCretaProject()) {
|
|
103
108
|
await unstuckProject(level)
|
|
@@ -105,23 +110,46 @@ if (command.startsWith('portafolio')) {
|
|
|
105
110
|
await createPortfolioProject(level)
|
|
106
111
|
}
|
|
107
112
|
} else if (command === 'enunciados') {
|
|
113
|
+
showLogo()
|
|
108
114
|
// Start enunciados selector
|
|
109
115
|
await startEnunciadosSelector()
|
|
110
116
|
} else if (command === 'sintaxis') {
|
|
117
|
+
showLogo()
|
|
111
118
|
// Start sintaxis selector with interactive sandbox lessons
|
|
112
119
|
await startSintaxisSelector()
|
|
113
120
|
} else if (command === 'code') {
|
|
121
|
+
showLogo()
|
|
114
122
|
// Start interactive coding session
|
|
115
123
|
const session = new CretaCodeSession()
|
|
116
124
|
await session.start()
|
|
117
125
|
} else if (command === 'papers') {
|
|
126
|
+
showLogo()
|
|
118
127
|
// Start papers selector
|
|
119
128
|
const executor = new PapersExecutor()
|
|
120
129
|
await executor.execute()
|
|
121
130
|
} else if (command === 'aws') {
|
|
131
|
+
showLogo()
|
|
122
132
|
// Show AWS billing detective guide
|
|
123
133
|
const viewer = new AWSGuideViewer()
|
|
124
134
|
await viewer.start()
|
|
135
|
+
} else if (command === 'reset') {
|
|
136
|
+
// Reset user state with confirmation
|
|
137
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
138
|
+
const answer = await new Promise(resolve => {
|
|
139
|
+
rl.question('⚠️ ¿Estás seguro de eliminar tu progreso? (s/N): ', resolve)
|
|
140
|
+
})
|
|
141
|
+
rl.close()
|
|
142
|
+
|
|
143
|
+
if (answer.toLowerCase() === 's' || answer.toLowerCase() === 'si') {
|
|
144
|
+
UserState.reset()
|
|
145
|
+
console.log('✅ Estado de usuario reiniciado')
|
|
146
|
+
console.log(' Se eliminó ~/.creta/user.json')
|
|
147
|
+
} else {
|
|
148
|
+
console.log('❌ Cancelado')
|
|
149
|
+
}
|
|
150
|
+
} else if (command === 'privacy' || command === 'privacidad') {
|
|
151
|
+
// Show privacy information
|
|
152
|
+
showPrivacyInfo()
|
|
125
153
|
} else if (command === 'help' || command === 'ayuda') {
|
|
126
154
|
// Show available commands
|
|
127
155
|
showHelp()
|
|
@@ -611,13 +639,14 @@ async function unstuckProject(level) {
|
|
|
611
639
|
}
|
|
612
640
|
|
|
613
641
|
function showHelp() {
|
|
614
|
-
console.log("\nCreta es una herramienta de CLI
|
|
615
|
-
console.log("El propósito
|
|
616
|
-
console.log("
|
|
642
|
+
console.log("\nCreta es una herramienta de CLI para el taller de software.")
|
|
643
|
+
console.log("El propósito es acompañar a aprendices y constructores")
|
|
644
|
+
console.log("en su camino de aprendizaje.\n")
|
|
617
645
|
|
|
618
646
|
console.log("📖 Documentación de comandos:")
|
|
619
647
|
console.log(" creta ls - Documentación en español del comando ls")
|
|
620
648
|
console.log(" creta cd - Documentación en español del comando cd")
|
|
649
|
+
console.log(" creta curl - Documentación en español del comando curl")
|
|
621
650
|
console.log(" creta git status - Documentación en español de git status")
|
|
622
651
|
console.log(" creta [comando] - Documentación de cualquier comando soportado\n")
|
|
623
652
|
|
|
@@ -631,9 +660,42 @@ function showHelp() {
|
|
|
631
660
|
console.log(" creta portafolio - Crea tu portafolio personal")
|
|
632
661
|
console.log(" creta code - Sesión interactiva de programación con IA\n")
|
|
633
662
|
|
|
663
|
+
console.log("🔧 Utilidades:")
|
|
664
|
+
console.log(" creta reset - Reinicia tu progreso (elimina ~/.creta/user.json)")
|
|
665
|
+
console.log(" creta privacy - Explica qué datos guarda Creta y dónde\n")
|
|
666
|
+
|
|
634
667
|
console.log("💡 Usa 'creta' sin argumentos para ver el menú principal.")
|
|
635
668
|
}
|
|
636
669
|
|
|
670
|
+
function showPrivacyInfo() {
|
|
671
|
+
console.log('\n🔒 Privacidad y Datos en Creta\n')
|
|
672
|
+
console.log('Creta almacena datos localmente en tu computadora:')
|
|
673
|
+
console.log('')
|
|
674
|
+
console.log('📍 Ubicación: ~/.creta/')
|
|
675
|
+
console.log('')
|
|
676
|
+
console.log('📄 Archivos:')
|
|
677
|
+
console.log(' • user.json - Tu progreso y preferencias')
|
|
678
|
+
console.log('')
|
|
679
|
+
console.log('📊 ¿Qué se guarda?')
|
|
680
|
+
console.log(' • Nombre (opcional)')
|
|
681
|
+
console.log(' • Lecciones completadas')
|
|
682
|
+
console.log(' • Proyectos creados')
|
|
683
|
+
console.log(' • Papers completados')
|
|
684
|
+
console.log(' • Estadísticas de uso (sesiones, última visita)')
|
|
685
|
+
console.log('')
|
|
686
|
+
console.log('🔐 ¿Es privado?')
|
|
687
|
+
console.log(' • SÍ - Todo permanece en tu máquina')
|
|
688
|
+
console.log(' • No se envía nada a servidores externos')
|
|
689
|
+
console.log(' • No hay telemetría ni tracking')
|
|
690
|
+
console.log(' • Tú controlas tus datos')
|
|
691
|
+
console.log('')
|
|
692
|
+
console.log('🗑️ ¿Cómo borrarlo?')
|
|
693
|
+
console.log(' • creta reset - Elimina user.json')
|
|
694
|
+
console.log(' • rm -rf ~/.creta - Elimina todo manualmente')
|
|
695
|
+
console.log('')
|
|
696
|
+
console.log('💡 Filosofía: Tu progreso es tuyo. Local-first siempre.')
|
|
697
|
+
}
|
|
698
|
+
|
|
637
699
|
async function startMainMenu() {
|
|
638
700
|
// Always try interactive mode first, fall back only if it fails
|
|
639
701
|
try {
|
|
@@ -1806,7 +1868,7 @@ async function startVimSetupTutorial() {
|
|
|
1806
1868
|
// Helper function to detect command help requests
|
|
1807
1869
|
function isCommandHelpRequest(args) {
|
|
1808
1870
|
// List of supported commands for help
|
|
1809
|
-
const basicCommands = ['ls', 'cd', 'mkdir', 'touch', 'wc']
|
|
1871
|
+
const basicCommands = ['ls', 'cd', 'mkdir', 'touch', 'wc', 'curl']
|
|
1810
1872
|
const gitCommands = ['git status', 'git add', 'git commit', 'git push', 'git log']
|
|
1811
1873
|
|
|
1812
1874
|
const commandString = args.join(' ')
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const curl = {
|
|
2
|
+
command: 'curl',
|
|
3
|
+
description: 'Realiza peticiones HTTP desde la terminal - tu herramienta para APIs',
|
|
4
|
+
usage: 'curl [opciones] [URL]',
|
|
5
|
+
commonOptions: [
|
|
6
|
+
{
|
|
7
|
+
flag: '-X',
|
|
8
|
+
description: 'Especifica el método HTTP (GET, POST, PUT, DELETE)'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
flag: '-H',
|
|
12
|
+
description: 'Agrega un header a la petición'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
flag: '-d',
|
|
16
|
+
description: 'Envía datos en el body (JSON, form data, etc.)'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
flag: '-i',
|
|
20
|
+
description: 'Incluye los headers de respuesta en el output'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
flag: '-L',
|
|
24
|
+
description: 'Sigue redirects automáticamente'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
flag: '-o',
|
|
28
|
+
description: 'Guarda el output en un archivo'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
flag: '-s',
|
|
32
|
+
description: 'Modo silencioso (no muestra progreso)'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
flag: '-v',
|
|
36
|
+
description: 'Modo verbose (muestra toda la comunicación)'
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
examples: [
|
|
40
|
+
{
|
|
41
|
+
command: 'curl https://api.github.com',
|
|
42
|
+
description: 'GET simple - obtiene datos de una API'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
command: 'curl -i https://api.github.com/users/octocat',
|
|
46
|
+
description: 'GET con headers de respuesta incluidos'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
command: 'curl -X POST https://api.ejemplo.com/users -H "Content-Type: application/json" -d \'{"nombre": "Juan"}\'',
|
|
50
|
+
description: 'POST con JSON - envía datos a una API'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
command: 'curl -X POST https://httpbin.org/post -d "nombre=Juan&edad=25"',
|
|
54
|
+
description: 'POST con form data - formato URL-encoded'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
command: 'curl -H "Authorization: Bearer tu_token_aqui" https://api.ejemplo.com/protected',
|
|
58
|
+
description: 'GET con autenticación - usa un token JWT'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
command: 'curl -L https://github.com',
|
|
62
|
+
description: 'Sigue redirects automáticamente (importante para URLs acortadas)'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
command: 'curl -o archivo.json https://api.ejemplo.com/data',
|
|
66
|
+
description: 'Descarga la respuesta y guárdala en un archivo'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
command: 'curl -v https://api.ejemplo.com',
|
|
70
|
+
description: 'Modo verbose - ve toda la negociación HTTP (útil para debugging)'
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
relatedLesson: 'APIs y HTTP',
|
|
74
|
+
tips: [
|
|
75
|
+
'Pro tip: usa -i siempre para ver los status codes (200, 404, 500) en la respuesta',
|
|
76
|
+
'Para APIs modernas siempre agrega el header: -H "Content-Type: application/json"',
|
|
77
|
+
'curl -L es esencial cuando trabajas con URLs acortadas o redirects',
|
|
78
|
+
'Combina -s (silencioso) con herramientas como jq para parsear JSON: curl -s url | jq'
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -4,6 +4,7 @@ export { cd } from './cd.js'
|
|
|
4
4
|
export { mkdir } from './mkdir.js'
|
|
5
5
|
export { touch } from './touch.js'
|
|
6
6
|
export { wc } from './wc.js'
|
|
7
|
+
export { curl } from './curl.js'
|
|
7
8
|
|
|
8
9
|
// Git commands
|
|
9
10
|
export { gitStatus } from './git-status.js'
|
|
@@ -20,6 +21,7 @@ export const commandMap = {
|
|
|
20
21
|
'mkdir': 'mkdir',
|
|
21
22
|
'touch': 'touch',
|
|
22
23
|
'wc': 'wc',
|
|
24
|
+
'curl': 'curl',
|
|
23
25
|
|
|
24
26
|
// Git
|
|
25
27
|
'git status': 'gitStatus',
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
export const CURL_PIPES = {
|
|
2
|
+
id: 'curl-pipes',
|
|
3
|
+
|
|
4
|
+
intro: {
|
|
5
|
+
definition: 'curl + pipes: Procesando APIs en tiempo real',
|
|
6
|
+
explanation: 'Aprende a combinar curl con pipes para extraer insights de APIs sin guardar archivos intermedios.',
|
|
7
|
+
detail: 'En el mundo real, usarás curl diariamente para consultar APIs, monitorear servicios, y extraer datos. Combinado con pipes (grep, wc, sed), puedes procesar respuestas JSON instantáneamente.'
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
steps: [
|
|
11
|
+
// --- Introducción a curl ---
|
|
12
|
+
{
|
|
13
|
+
type: 'command-intro',
|
|
14
|
+
command: 'curl',
|
|
15
|
+
description: 'curl: Cliente HTTP para la terminal',
|
|
16
|
+
explanation: 'curl descarga datos de URLs sin abrir el navegador. Es la herramienta #1 para trabajar con APIs REST, probar endpoints, y automatizar descargas.',
|
|
17
|
+
example: 'curl https://api.github.com\ncurl -s https://jsonplaceholder.typicode.com/users/1',
|
|
18
|
+
instruction: 'Úsalo para traer datos desde cualquier URL. El flag -s (silent) oculta el progreso.'
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
content: '\nVamos a usar JSONPlaceholder, una API REST falsa para practicar.\n\nAPI endpoints disponibles:\n • https://jsonplaceholder.typicode.com/users (lista de 10 usuarios)\n • https://jsonplaceholder.typicode.com/posts (100 posts)\n • https://jsonplaceholder.typicode.com/comments (500 comentarios)\n'
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
{ type: 'pause' },
|
|
27
|
+
|
|
28
|
+
// --- Práctica 1: curl básico ---
|
|
29
|
+
{
|
|
30
|
+
type: 'text',
|
|
31
|
+
content: '📝 Práctica 1: Fetch básico con curl\n\nVamos a traer datos de un usuario. El flag -s (silent) oculta la barra de progreso para ver solo el JSON.'
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
type: 'interactive-command',
|
|
36
|
+
prompt: 'Ejecuta: curl -s https://jsonplaceholder.typicode.com/users/1',
|
|
37
|
+
expectedCommand: 'curl -s https://jsonplaceholder.typicode.com/users/1',
|
|
38
|
+
explanation: '¡Perfecto! Viste un objeto JSON con datos del usuario #1.\n\nNota: El JSON se mostró sin formato (una línea larga). En producción usarías jq para formatearlo, pero con pipes podemos extraer lo que necesitamos.'
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
{ type: 'pause' },
|
|
42
|
+
|
|
43
|
+
// --- curl + grep ---
|
|
44
|
+
{
|
|
45
|
+
type: 'text',
|
|
46
|
+
content: '🔍 Combinando curl con grep\n\nNo siempre necesitas TODO el JSON. A veces solo quieres buscar un campo específico.\n\nPuedes enviar la salida de curl directamente a grep sin guardar archivos.'
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
type: 'code',
|
|
51
|
+
title: 'curl + grep en acción',
|
|
52
|
+
code: `# Buscar el email en la respuesta JSON
|
|
53
|
+
curl -s https://jsonplaceholder.typicode.com/users/1 | grep "email"
|
|
54
|
+
|
|
55
|
+
# Buscar todos los nombres de usuario
|
|
56
|
+
curl -s https://jsonplaceholder.typicode.com/users | grep "name"
|
|
57
|
+
|
|
58
|
+
# Buscar posts que mencionan "dolor"
|
|
59
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "dolor"`,
|
|
60
|
+
after: ['', 'El pipe | conecta stdout de curl al stdin de grep.', 'grep filtra solo las líneas que contienen el patrón.']
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
{ type: 'pause' },
|
|
64
|
+
|
|
65
|
+
// --- Práctica 2: curl + grep ---
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
content: '📝 Práctica 2: Filtrar respuestas con grep\n\nVamos a buscar el campo "email" en un usuario.'
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
type: 'interactive-command',
|
|
73
|
+
prompt: 'Ejecuta: curl -s https://jsonplaceholder.typicode.com/users/1 | grep "email"',
|
|
74
|
+
expectedCommand: 'curl -s https://jsonplaceholder.typicode.com/users/1 | grep "email"',
|
|
75
|
+
explanation: '¡Exacto! grep filtró la respuesta y solo mostró la línea que contiene "email".\n\nViste algo como: "email": "Sincere@april.biz"\n\nEsto es útil cuando solo necesitas un campo específico y no quieres parsear todo el JSON.'
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
{ type: 'pause' },
|
|
79
|
+
|
|
80
|
+
// --- curl + wc ---
|
|
81
|
+
{
|
|
82
|
+
type: 'text',
|
|
83
|
+
content: '🔢 Contando datos con wc\n\nwc (word count) puede contar líneas, palabras, o caracteres.\n\nCombinado con curl, puedes contar registros en APIs sin procesar el JSON completo.'
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
type: 'code',
|
|
88
|
+
title: 'curl + wc para análisis rápido',
|
|
89
|
+
code: `# Contar líneas en la respuesta (aprox. registros)
|
|
90
|
+
curl -s https://jsonplaceholder.typicode.com/users | wc -l
|
|
91
|
+
|
|
92
|
+
# Contar palabras en la respuesta
|
|
93
|
+
curl -s https://jsonplaceholder.typicode.com/posts/1 | wc -w
|
|
94
|
+
|
|
95
|
+
# Contar caracteres (tamaño de la respuesta)
|
|
96
|
+
curl -s https://jsonplaceholder.typicode.com/posts | wc -c`,
|
|
97
|
+
after: ['', 'wc -l cuenta líneas, wc -w palabras, wc -c caracteres.', 'Útil para monitorear tamaños de respuesta o estimar registros.']
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
{ type: 'pause' },
|
|
101
|
+
|
|
102
|
+
// --- Práctica 3: curl + wc ---
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
content: '📝 Práctica 3: Contar líneas en respuesta\n\nVamos a contar cuántas líneas tiene la respuesta de /users.'
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
type: 'interactive-command',
|
|
110
|
+
prompt: 'Ejecuta: curl -s https://jsonplaceholder.typicode.com/users | wc -l',
|
|
111
|
+
expectedCommand: 'curl -s https://jsonplaceholder.typicode.com/users | wc -l',
|
|
112
|
+
explanation: '¡Correcto! Viste un número (probablemente alrededor de 500-700 líneas).\n\nEsto te da una idea rápida del tamaño de la respuesta sin tener que leer todo el JSON.'
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
{ type: 'pause' },
|
|
116
|
+
|
|
117
|
+
// --- Múltiples pipes ---
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
content: '⛓️ Encadenando múltiples pipes\n\nEl verdadero poder viene cuando combinas curl + grep + wc.\n\nPuedes hacer análisis complejos en una sola línea.'
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
type: 'code',
|
|
125
|
+
title: 'Triple combo: curl + grep + wc',
|
|
126
|
+
code: `# ¿Cuántos usuarios tienen email con "biz"?
|
|
127
|
+
curl -s https://jsonplaceholder.typicode.com/users | grep "biz" | wc -l
|
|
128
|
+
|
|
129
|
+
# ¿Cuántas veces aparece "id" en la respuesta?
|
|
130
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "id" | wc -l
|
|
131
|
+
|
|
132
|
+
# Buscar posts con "sunt" y contar cuántos
|
|
133
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "sunt" | wc -l`,
|
|
134
|
+
after: ['', 'Cada pipe pasa su output al siguiente comando.', 'curl → grep (filtra) → wc (cuenta)', 'Puedes encadenar cuantos pipes necesites.']
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
{ type: 'pause' },
|
|
138
|
+
|
|
139
|
+
// --- Práctica 4: múltiples pipes ---
|
|
140
|
+
{
|
|
141
|
+
type: 'text',
|
|
142
|
+
content: '📝 Práctica 4: curl + grep + wc\n\nVamos a contar cuántos emails aparecen en /users (buscando líneas con "email").'
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
type: 'interactive-command',
|
|
147
|
+
prompt: 'Ejecuta: curl -s https://jsonplaceholder.typicode.com/users | grep "email" | wc -l',
|
|
148
|
+
expectedCommand: 'curl -s https://jsonplaceholder.typicode.com/users | grep "email" | wc -l',
|
|
149
|
+
explanation: '¡Excelente! Viste el número de líneas que contienen "email".\n\nEn este caso probablemente 10 (uno por usuario).\n\nEsta técnica es SUPER útil en el mundo real:\n • Monitorear APIs (¿cuántos errores en logs?)\n • Contar registros sin parsear JSON\n • Análisis rápido de respuestas HTTP'
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
{ type: 'pause' },
|
|
153
|
+
|
|
154
|
+
// --- Escenarios del mundo real ---
|
|
155
|
+
{
|
|
156
|
+
type: 'text',
|
|
157
|
+
content: '🌍 Escenarios del mundo real\n\nAquí hay ejemplos que usarás en tu día a día como developer:'
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
{
|
|
161
|
+
type: 'code',
|
|
162
|
+
title: 'Casos de uso profesionales',
|
|
163
|
+
code: `# 1. Monitoreo de API: ¿está respondiendo?
|
|
164
|
+
curl -s https://api.miapp.com/health | grep "ok"
|
|
165
|
+
|
|
166
|
+
# 2. Análisis de logs: ¿cuántos errores en las últimas 24h?
|
|
167
|
+
curl -s https://logs.miapp.com/today | grep "ERROR" | wc -l
|
|
168
|
+
|
|
169
|
+
# 3. Testing: ¿la API devuelve el status code correcto?
|
|
170
|
+
curl -s -w "%{http_code}" https://api.miapp.com/users | grep "200"
|
|
171
|
+
|
|
172
|
+
# 4. Data pipeline: descargar, filtrar, y guardar
|
|
173
|
+
curl -s https://api.datos.com/export | grep "activo" > usuarios-activos.txt
|
|
174
|
+
|
|
175
|
+
# 5. Comparación: ¿cuántos posts vs comments?
|
|
176
|
+
curl -s https://jsonplaceholder.typicode.com/posts | wc -l
|
|
177
|
+
curl -s https://jsonplaceholder.typicode.com/comments | wc -l`,
|
|
178
|
+
after: ['', 'Estos patrones son DIARIOS en DevOps, backend, y testing.', 'curl + pipes reemplaza scripts complejos con one-liners simples.']
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
{ type: 'pause' },
|
|
182
|
+
|
|
183
|
+
// --- Tips pro ---
|
|
184
|
+
{
|
|
185
|
+
type: 'text',
|
|
186
|
+
content: '💡 Pro Tips\n\n1. Siempre usa -s (silent) para evitar info de progreso en pipes\n2. Usa -i para ver headers HTTP (status codes, content-type)\n3. Combina con head/tail para limitar resultados\n4. grep -i hace búsquedas case-insensitive\n5. En producción, usa jq para parsear JSON correctamente\n\nEjemplo avanzado:\n curl -s https://api.github.com/users/github | grep -i "login"\n'
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
{ type: 'pause' },
|
|
190
|
+
|
|
191
|
+
// --- Ejercicio final ---
|
|
192
|
+
{
|
|
193
|
+
type: 'text',
|
|
194
|
+
content: '🎯 Ejercicio final: Análisis completo\n\nVamos a hacer un análisis real: ¿cuántos posts mencionan "qui" en su contenido?'
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
{
|
|
198
|
+
type: 'interactive-command',
|
|
199
|
+
prompt: 'Ejecuta: curl -s https://jsonplaceholder.typicode.com/posts | grep "qui" | wc -l',
|
|
200
|
+
expectedCommand: 'curl -s https://jsonplaceholder.typicode.com/posts | grep "qui" | wc -l',
|
|
201
|
+
explanation: '¡PERFECTO! Acabas de:\n 1. Descargar 100 posts desde una API REST\n 2. Filtrar solo los que contienen "qui"\n 3. Contar cuántos matches encontraste\n\nTodo en una sola línea, sin archivos intermedios.\n\nEsta es la esencia del trabajo con APIs en terminal: rápido, eficiente, y scripteable.'
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
{ type: 'pause' },
|
|
205
|
+
|
|
206
|
+
// --- Conclusión ---
|
|
207
|
+
{
|
|
208
|
+
type: 'text',
|
|
209
|
+
content: '🎓 Conclusión\n\nAprendiste a:\n ✅ Usar curl para fetch de APIs\n ✅ Combinar curl + grep para filtrar respuestas\n ✅ Usar curl + wc para análisis rápido\n ✅ Encadenar múltiples pipes (curl + grep + wc)\n ✅ Aplicar estos patrones a escenarios reales\n\nEstas técnicas son FUNDAMENTALES para:\n • DevOps (monitoreo, health checks)\n • Backend development (testing APIs)\n • Data engineering (ETL pipelines)\n • Automation (scripts de CI/CD)\n\nLa próxima vez que necesites consultar una API, ya no abrirás Postman.\nUsarás curl + pipes directo en tu terminal. 🚀'
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
{ type: 'pause' },
|
|
213
|
+
|
|
214
|
+
// --- Maze completion ---
|
|
215
|
+
{
|
|
216
|
+
type: 'maze-completion',
|
|
217
|
+
message: '¡Has completado: APIs con curl! 🔥',
|
|
218
|
+
unlocked: 'Dominas curl + pipes para procesar APIs en tiempo real'
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
package/lib/data/menus.js
CHANGED
|
@@ -5,6 +5,7 @@ import { TERMINAL_BASICO } from '../data/lessons/sintaxis/terminal-basico.js'
|
|
|
5
5
|
import { GIT_BASICO } from '../data/lessons/sintaxis/git-basico.js'
|
|
6
6
|
import { BASH_SCRIPTS } from '../data/lessons/sintaxis/bash-scripts.js'
|
|
7
7
|
import { PIPING_REDIRECCION } from '../data/lessons/sintaxis/piping-redireccion.js'
|
|
8
|
+
import { CURL_PIPES } from '../data/lessons/sintaxis/curl-pipes.js'
|
|
8
9
|
import { clearConsole } from '../utils/output.js'
|
|
9
10
|
import { createPromptInterface } from '../utils/input.js'
|
|
10
11
|
import { UserState } from '../utils/user-state.js'
|
|
@@ -14,7 +15,8 @@ const LESSONS_BY_ID = new Map([
|
|
|
14
15
|
['terminal-basico', TERMINAL_BASICO],
|
|
15
16
|
['git-basico', GIT_BASICO],
|
|
16
17
|
['bash-scripts', BASH_SCRIPTS],
|
|
17
|
-
['piping-redireccion', PIPING_REDIRECCION]
|
|
18
|
+
['piping-redireccion', PIPING_REDIRECCION],
|
|
19
|
+
['curl-pipes', CURL_PIPES]
|
|
18
20
|
])
|
|
19
21
|
|
|
20
22
|
function waitForEnter(message = '\nPresiona Enter para continuar...') {
|
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
# curl + Pipes - Processing APIs in Real Time
|
|
2
|
+
|
|
3
|
+
<!-- vim: set foldmethod=marker foldlevel=0: -->
|
|
4
|
+
|
|
5
|
+
## 📖 LazyVim Reading Guide {{{
|
|
6
|
+
|
|
7
|
+
**Start with:** `zM` (close all folds) → Navigate with `za` (toggle fold under cursor)
|
|
8
|
+
|
|
9
|
+
This document uses fold markers `{{{` and `}}}` for organized reading.
|
|
10
|
+
|
|
11
|
+
}}}
|
|
12
|
+
|
|
13
|
+
## 🚨 Problem: Need Quick API Insights Without Postman {{{
|
|
14
|
+
|
|
15
|
+
You're debugging an API and need to:
|
|
16
|
+
- Check if an endpoint is responding
|
|
17
|
+
- Count how many records are returned
|
|
18
|
+
- Filter JSON for specific fields
|
|
19
|
+
- Monitor for errors in real time
|
|
20
|
+
|
|
21
|
+
**Current workflow:**
|
|
22
|
+
1. Open Postman or browser
|
|
23
|
+
2. Make request
|
|
24
|
+
3. Copy response
|
|
25
|
+
4. Open text editor
|
|
26
|
+
5. Manually search/count
|
|
27
|
+
|
|
28
|
+
**Time wasted:** 2-5 minutes per query
|
|
29
|
+
|
|
30
|
+
There's a **better way** → `curl + pipes`
|
|
31
|
+
|
|
32
|
+
}}}
|
|
33
|
+
|
|
34
|
+
## ✅ Solution: curl + grep/wc/sed for Instant Insights {{{
|
|
35
|
+
|
|
36
|
+
Combine curl with pipe commands to process API responses **instantly** in your terminal.
|
|
37
|
+
|
|
38
|
+
**Core pattern:**
|
|
39
|
+
```bash
|
|
40
|
+
curl [API] | [filter] | [process] | [output]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Real example:**
|
|
44
|
+
```bash
|
|
45
|
+
# Count active users in one line
|
|
46
|
+
curl -s https://api.example.com/users | grep "active" | wc -l
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Benefits:**
|
|
50
|
+
- ⚡ **Instant feedback** - no UI switching
|
|
51
|
+
- 🔁 **Scriptable** - automate repetitive checks
|
|
52
|
+
- 🎯 **Precise** - extract exactly what you need
|
|
53
|
+
- 📊 **Pipeline-ready** - chain multiple operations
|
|
54
|
+
|
|
55
|
+
}}}
|
|
56
|
+
|
|
57
|
+
## 🎯 Essential Curl Flags {{{
|
|
58
|
+
|
|
59
|
+
### -s (Silent) {{{
|
|
60
|
+
|
|
61
|
+
**Problem:** Default curl shows progress bar, pollutes pipe output
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Without -s (messy)
|
|
65
|
+
curl https://api.github.com/users/github | grep "login"
|
|
66
|
+
# % Total % Received... (progress bar appears)
|
|
67
|
+
|
|
68
|
+
# With -s (clean)
|
|
69
|
+
curl -s https://api.github.com/users/github | grep "login"
|
|
70
|
+
# "login": "github" (just the data)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Rule:** Always use `-s` when piping curl output.
|
|
74
|
+
|
|
75
|
+
}}}
|
|
76
|
+
|
|
77
|
+
### -i (Include Headers) {{{
|
|
78
|
+
|
|
79
|
+
**Use case:** See HTTP status codes and headers
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
curl -si https://api.github.com/users/github | head -n 1
|
|
83
|
+
# HTTP/2 200 (status code visible)
|
|
84
|
+
|
|
85
|
+
curl -si https://api.github.com/users/fake123 | head -n 1
|
|
86
|
+
# HTTP/2 404 (error detected)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Pro tip:** Use `head -n 1` to extract just the status line.
|
|
90
|
+
|
|
91
|
+
}}}
|
|
92
|
+
|
|
93
|
+
### -L (Follow Redirects) {{{
|
|
94
|
+
|
|
95
|
+
**Problem:** Short URLs redirect, curl doesn't follow by default
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Without -L (stops at redirect)
|
|
99
|
+
curl -s https://bit.ly/example
|
|
100
|
+
# <redirect HTML>
|
|
101
|
+
|
|
102
|
+
# With -L (follows to final destination)
|
|
103
|
+
curl -sL https://bit.ly/example
|
|
104
|
+
# <actual content>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Use:** Essential for shortened URLs, CDN redirects, HTTPS upgrades.
|
|
108
|
+
|
|
109
|
+
}}}
|
|
110
|
+
|
|
111
|
+
### -H (Add Headers) {{{
|
|
112
|
+
|
|
113
|
+
**Use case:** Authentication, content type specification
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Add authorization header
|
|
117
|
+
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/protected
|
|
118
|
+
|
|
119
|
+
# Specify JSON content type
|
|
120
|
+
curl -s -H "Content-Type: application/json" https://api.example.com/data
|
|
121
|
+
|
|
122
|
+
# Multiple headers
|
|
123
|
+
curl -s \
|
|
124
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
125
|
+
-H "Accept: application/json" \
|
|
126
|
+
https://api.example.com/users
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
}}}
|
|
130
|
+
|
|
131
|
+
### -w (Custom Output Format) {{{
|
|
132
|
+
|
|
133
|
+
**Use case:** Extract specific HTTP metadata
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Get only HTTP status code
|
|
137
|
+
curl -s -w "%{http_code}" -o /dev/null https://api.github.com/users/github
|
|
138
|
+
# 200
|
|
139
|
+
|
|
140
|
+
# Get response time
|
|
141
|
+
curl -s -w "%{time_total}s" -o /dev/null https://api.github.com
|
|
142
|
+
# 0.452s
|
|
143
|
+
|
|
144
|
+
# Get status + time
|
|
145
|
+
curl -s -w "Status: %{http_code} | Time: %{time_total}s\n" -o /dev/null https://api.github.com
|
|
146
|
+
# Status: 200 | Time: 0.234s
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Flags:**
|
|
150
|
+
- `-w "%{format}"`: Write custom output format
|
|
151
|
+
- `-o /dev/null`: Discard response body (only show -w output)
|
|
152
|
+
|
|
153
|
+
}}}
|
|
154
|
+
|
|
155
|
+
}}}
|
|
156
|
+
|
|
157
|
+
## 🔗 Pipe Combinations {{{
|
|
158
|
+
|
|
159
|
+
### curl + grep: Filter JSON Fields {{{
|
|
160
|
+
|
|
161
|
+
**Use case:** Extract specific fields from JSON response
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Find email in user object
|
|
165
|
+
curl -s https://jsonplaceholder.typicode.com/users/1 | grep "email"
|
|
166
|
+
# "email": "Sincere@april.biz",
|
|
167
|
+
|
|
168
|
+
# Find all names in user list
|
|
169
|
+
curl -s https://jsonplaceholder.typicode.com/users | grep "name"
|
|
170
|
+
# "name": "Leanne Graham",
|
|
171
|
+
# "name": "Ervin Howell",
|
|
172
|
+
# ...
|
|
173
|
+
|
|
174
|
+
# Case-insensitive search
|
|
175
|
+
curl -s https://api.github.com/users/github | grep -i "LOGIN"
|
|
176
|
+
# "login": "github",
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Pro tip:** Use `grep -i` for case-insensitive matching.
|
|
180
|
+
|
|
181
|
+
}}}
|
|
182
|
+
|
|
183
|
+
### curl + wc: Count Records/Lines {{{
|
|
184
|
+
|
|
185
|
+
**Use case:** Quick statistics on API responses
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Count lines in response (approx. size)
|
|
189
|
+
curl -s https://jsonplaceholder.typicode.com/users | wc -l
|
|
190
|
+
# 542
|
|
191
|
+
|
|
192
|
+
# Count words (tokens)
|
|
193
|
+
curl -s https://jsonplaceholder.typicode.com/users | wc -w
|
|
194
|
+
# 1849
|
|
195
|
+
|
|
196
|
+
# Count bytes (response size)
|
|
197
|
+
curl -s https://jsonplaceholder.typicode.com/users | wc -c
|
|
198
|
+
# 5645
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Interpretation:**
|
|
202
|
+
- `wc -l`: Lines (rough measure of JSON structure)
|
|
203
|
+
- `wc -w`: Words (data density)
|
|
204
|
+
- `wc -c`: Bytes (actual response size)
|
|
205
|
+
|
|
206
|
+
}}}
|
|
207
|
+
|
|
208
|
+
### curl + grep + wc: Count Matches {{{
|
|
209
|
+
|
|
210
|
+
**Use case:** Answer questions like "How many users have X?"
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# How many users?
|
|
214
|
+
curl -s https://jsonplaceholder.typicode.com/users | grep "\"id\":" | wc -l
|
|
215
|
+
# 10
|
|
216
|
+
|
|
217
|
+
# How many posts mention "dolor"?
|
|
218
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "dolor" | wc -l
|
|
219
|
+
# 23
|
|
220
|
+
|
|
221
|
+
# How many email addresses end in ".biz"?
|
|
222
|
+
curl -s https://jsonplaceholder.typicode.com/users | grep ".biz" | wc -l
|
|
223
|
+
# 2
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Pattern:** `curl → filter → count`
|
|
227
|
+
|
|
228
|
+
}}}
|
|
229
|
+
|
|
230
|
+
### curl + head/tail: Limit Output {{{
|
|
231
|
+
|
|
232
|
+
**Use case:** See first/last N lines of response
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# First 10 lines
|
|
236
|
+
curl -s https://jsonplaceholder.typicode.com/posts | head -n 10
|
|
237
|
+
|
|
238
|
+
# Last 5 lines
|
|
239
|
+
curl -s https://jsonplaceholder.typicode.com/posts | tail -n 5
|
|
240
|
+
|
|
241
|
+
# Lines 20-30 (skip first 19, show next 10)
|
|
242
|
+
curl -s https://jsonplaceholder.typicode.com/posts | tail -n +20 | head -n 10
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Use cases:**
|
|
246
|
+
- Preview large responses
|
|
247
|
+
- Sample data without downloading everything
|
|
248
|
+
- Extract specific ranges
|
|
249
|
+
|
|
250
|
+
}}}
|
|
251
|
+
|
|
252
|
+
### curl + sed: Transform Output {{{
|
|
253
|
+
|
|
254
|
+
**Use case:** Clean up or reformat JSON fields
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Extract email addresses (remove JSON quotes)
|
|
258
|
+
curl -s https://jsonplaceholder.typicode.com/users | grep "email" | sed 's/.*"email": "\(.*\)".*/\1/'
|
|
259
|
+
|
|
260
|
+
# Remove all commas from JSON
|
|
261
|
+
curl -s https://jsonplaceholder.typicode.com/users/1 | sed 's/,//g'
|
|
262
|
+
|
|
263
|
+
# Replace text in response
|
|
264
|
+
curl -s https://jsonplaceholder.typicode.com/posts/1 | sed 's/sunt/REDACTED/g'
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Advanced:** Combine with pipes for complex transformations.
|
|
268
|
+
|
|
269
|
+
}}}
|
|
270
|
+
|
|
271
|
+
### curl + sort + uniq: Deduplicate {{{
|
|
272
|
+
|
|
273
|
+
**Use case:** Find unique values in API responses
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Get unique user IDs from posts
|
|
277
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "userId" | sort | uniq
|
|
278
|
+
|
|
279
|
+
# Count occurrences of each user
|
|
280
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "userId" | sort | uniq -c
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Pattern:** `curl → extract → sort → unique`
|
|
284
|
+
|
|
285
|
+
}}}
|
|
286
|
+
|
|
287
|
+
}}}
|
|
288
|
+
|
|
289
|
+
## 🌍 Real-World Scenarios {{{
|
|
290
|
+
|
|
291
|
+
### Scenario 1: Health Check Monitoring {{{
|
|
292
|
+
|
|
293
|
+
**Problem:** Need to check if API is responding with 200 OK
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
# Check status code
|
|
297
|
+
curl -s -w "%{http_code}" -o /dev/null https://api.myapp.com/health
|
|
298
|
+
|
|
299
|
+
# One-liner health check
|
|
300
|
+
if [ $(curl -s -w "%{http_code}" -o /dev/null https://api.myapp.com/health) -eq 200 ]; then
|
|
301
|
+
echo "✅ API is healthy"
|
|
302
|
+
else
|
|
303
|
+
echo "❌ API is down"
|
|
304
|
+
fi
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Use case:** Cron jobs, deployment scripts, CI/CD health checks.
|
|
308
|
+
|
|
309
|
+
}}}
|
|
310
|
+
|
|
311
|
+
### Scenario 2: Error Log Analysis {{{
|
|
312
|
+
|
|
313
|
+
**Problem:** Count how many errors occurred in last hour
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Count ERROR lines in logs API
|
|
317
|
+
curl -s https://logs.myapp.com/last-hour | grep "ERROR" | wc -l
|
|
318
|
+
|
|
319
|
+
# Find critical errors only
|
|
320
|
+
curl -s https://logs.myapp.com/last-hour | grep "CRITICAL" | wc -l
|
|
321
|
+
|
|
322
|
+
# Group by error type
|
|
323
|
+
curl -s https://logs.myapp.com/last-hour | grep "ERROR" | cut -d':' -f2 | sort | uniq -c
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Use case:** Incident response, debugging production issues.
|
|
327
|
+
|
|
328
|
+
}}}
|
|
329
|
+
|
|
330
|
+
### Scenario 3: API Testing in CI/CD {{{
|
|
331
|
+
|
|
332
|
+
**Problem:** Verify API returns expected data after deployment
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# Test: Does response contain expected field?
|
|
336
|
+
curl -s https://api.myapp.com/config | grep -q "version" && echo "✅ Pass" || echo "❌ Fail"
|
|
337
|
+
|
|
338
|
+
# Test: Is there at least one user?
|
|
339
|
+
USER_COUNT=$(curl -s https://api.myapp.com/users | grep "\"id\":" | wc -l)
|
|
340
|
+
if [ $USER_COUNT -gt 0 ]; then
|
|
341
|
+
echo "✅ Users endpoint working"
|
|
342
|
+
else
|
|
343
|
+
echo "❌ No users returned"
|
|
344
|
+
fi
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Use case:** Smoke tests after deployment, integration testing.
|
|
348
|
+
|
|
349
|
+
}}}
|
|
350
|
+
|
|
351
|
+
### Scenario 4: Data Pipeline Extraction {{{
|
|
352
|
+
|
|
353
|
+
**Problem:** Download API data, filter active records, save to file
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# Extract active users to file
|
|
357
|
+
curl -s https://api.myapp.com/users | grep "\"status\": \"active\"" > active-users.json
|
|
358
|
+
|
|
359
|
+
# Extract and count
|
|
360
|
+
curl -s https://api.myapp.com/orders | grep "\"status\": \"pending\"" | wc -l > pending-orders.txt
|
|
361
|
+
|
|
362
|
+
# Multi-step pipeline
|
|
363
|
+
curl -s https://api.github.com/users/github/repos \
|
|
364
|
+
| grep "\"name\":" \
|
|
365
|
+
| sed 's/.*"name": "\(.*\)".*/\1/' \
|
|
366
|
+
| sort \
|
|
367
|
+
> repo-names.txt
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Use case:** ETL jobs, data exports, scheduled reports.
|
|
371
|
+
|
|
372
|
+
}}}
|
|
373
|
+
|
|
374
|
+
### Scenario 5: Rate Limit Checking {{{
|
|
375
|
+
|
|
376
|
+
**Problem:** Check how many API calls you have left
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# GitHub rate limit (headers contain rate info)
|
|
380
|
+
curl -si https://api.github.com/users/github | grep -i "x-ratelimit"
|
|
381
|
+
# x-ratelimit-limit: 60
|
|
382
|
+
# x-ratelimit-remaining: 59
|
|
383
|
+
|
|
384
|
+
# Extract just the remaining count
|
|
385
|
+
curl -si https://api.github.com/users/github \
|
|
386
|
+
| grep -i "x-ratelimit-remaining" \
|
|
387
|
+
| awk '{print $2}'
|
|
388
|
+
# 59
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Use case:** API quota monitoring, avoiding rate limit errors.
|
|
392
|
+
|
|
393
|
+
}}}
|
|
394
|
+
|
|
395
|
+
### Scenario 6: Comparing API Versions {{{
|
|
396
|
+
|
|
397
|
+
**Problem:** Did the API response change between versions?
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# Get response from v1
|
|
401
|
+
curl -s https://api.myapp.com/v1/users > v1-response.json
|
|
402
|
+
|
|
403
|
+
# Get response from v2
|
|
404
|
+
curl -s https://api.myapp.com/v2/users > v2-response.json
|
|
405
|
+
|
|
406
|
+
# Compare
|
|
407
|
+
diff v1-response.json v2-response.json
|
|
408
|
+
|
|
409
|
+
# Or count differences
|
|
410
|
+
diff v1-response.json v2-response.json | wc -l
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Use case:** API migration validation, regression testing.
|
|
414
|
+
|
|
415
|
+
}}}
|
|
416
|
+
|
|
417
|
+
}}}
|
|
418
|
+
|
|
419
|
+
## 🧪 Practice Exercises {{{
|
|
420
|
+
|
|
421
|
+
### Exercise 1: Basic Filtering {{{
|
|
422
|
+
|
|
423
|
+
**Goal:** Extract the "title" field from a post
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# API endpoint
|
|
427
|
+
https://jsonplaceholder.typicode.com/posts/1
|
|
428
|
+
|
|
429
|
+
# Your command:
|
|
430
|
+
curl -s https://jsonplaceholder.typicode.com/posts/1 | grep "title"
|
|
431
|
+
|
|
432
|
+
# Expected output:
|
|
433
|
+
# "title": "sunt aut facere repellat provident...",
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
}}}
|
|
437
|
+
|
|
438
|
+
### Exercise 2: Counting Records {{{
|
|
439
|
+
|
|
440
|
+
**Goal:** How many comments exist in total?
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# API endpoint
|
|
444
|
+
https://jsonplaceholder.typicode.com/comments
|
|
445
|
+
|
|
446
|
+
# Your command:
|
|
447
|
+
curl -s https://jsonplaceholder.typicode.com/comments | grep "\"id\":" | wc -l
|
|
448
|
+
|
|
449
|
+
# Expected output:
|
|
450
|
+
# 500
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
}}}
|
|
454
|
+
|
|
455
|
+
### Exercise 3: Multi-Pipe Chain {{{
|
|
456
|
+
|
|
457
|
+
**Goal:** Count how many posts contain the word "qui"
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
# API endpoint
|
|
461
|
+
https://jsonplaceholder.typicode.com/posts
|
|
462
|
+
|
|
463
|
+
# Your command:
|
|
464
|
+
curl -s https://jsonplaceholder.typicode.com/posts | grep "qui" | wc -l
|
|
465
|
+
|
|
466
|
+
# Expected output:
|
|
467
|
+
# ~30-40 (number of lines containing "qui")
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
}}}
|
|
471
|
+
|
|
472
|
+
### Exercise 4: Status Code Check {{{
|
|
473
|
+
|
|
474
|
+
**Goal:** Verify GitHub API returns 200 for valid user
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
# API endpoint
|
|
478
|
+
https://api.github.com/users/github
|
|
479
|
+
|
|
480
|
+
# Your command:
|
|
481
|
+
curl -s -w "%{http_code}" -o /dev/null https://api.github.com/users/github
|
|
482
|
+
|
|
483
|
+
# Expected output:
|
|
484
|
+
# 200
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
}}}
|
|
488
|
+
|
|
489
|
+
### Exercise 5: Extract and Transform {{{
|
|
490
|
+
|
|
491
|
+
**Goal:** Get all email addresses from users, one per line (clean format)
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
# API endpoint
|
|
495
|
+
https://jsonplaceholder.typicode.com/users
|
|
496
|
+
|
|
497
|
+
# Your command:
|
|
498
|
+
curl -s https://jsonplaceholder.typicode.com/users \
|
|
499
|
+
| grep "email" \
|
|
500
|
+
| sed 's/.*"email": "\(.*\)".*/\1/' \
|
|
501
|
+
| sed 's/,$//'
|
|
502
|
+
|
|
503
|
+
# Expected output:
|
|
504
|
+
# Sincere@april.biz
|
|
505
|
+
# Shanna@melissa.tv
|
|
506
|
+
# Nathan@yesenia.net
|
|
507
|
+
# (etc...)
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
}}}
|
|
511
|
+
|
|
512
|
+
}}}
|
|
513
|
+
|
|
514
|
+
## 🛡️ Best Practices {{{
|
|
515
|
+
|
|
516
|
+
### Always Use -s Flag {{{
|
|
517
|
+
|
|
518
|
+
**Why:** Default progress bar pollutes pipe output
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
# ❌ Bad: progress bar interferes with grep
|
|
522
|
+
curl https://api.example.com | grep "name"
|
|
523
|
+
|
|
524
|
+
# ✅ Good: clean output
|
|
525
|
+
curl -s https://api.example.com | grep "name"
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
}}}
|
|
529
|
+
|
|
530
|
+
### Check Status Codes Before Processing {{{
|
|
531
|
+
|
|
532
|
+
**Why:** Don't process error responses
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
# ❌ Bad: processes error HTML as JSON
|
|
536
|
+
curl -s https://api.example.com/fake | grep "name"
|
|
537
|
+
|
|
538
|
+
# ✅ Good: check status first
|
|
539
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" https://api.example.com/users)
|
|
540
|
+
STATUS=$(echo "$RESPONSE" | tail -n 1)
|
|
541
|
+
BODY=$(echo "$RESPONSE" | head -n -1)
|
|
542
|
+
|
|
543
|
+
if [ $STATUS -eq 200 ]; then
|
|
544
|
+
echo "$BODY" | grep "name"
|
|
545
|
+
else
|
|
546
|
+
echo "Error: API returned $STATUS"
|
|
547
|
+
fi
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
}}}
|
|
551
|
+
|
|
552
|
+
### Use jq for Complex JSON Parsing {{{
|
|
553
|
+
|
|
554
|
+
**When to use pipes:** Simple filtering, counting, text search
|
|
555
|
+
**When to use jq:** Proper JSON parsing, nested fields, arrays
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
# Pipes: Quick field search
|
|
559
|
+
curl -s https://api.example.com/users | grep "email"
|
|
560
|
+
|
|
561
|
+
# jq: Proper JSON parsing
|
|
562
|
+
curl -s https://api.example.com/users | jq '.[].email'
|
|
563
|
+
|
|
564
|
+
# jq: Complex queries
|
|
565
|
+
curl -s https://api.example.com/users | jq '.[] | select(.id > 5) | .name'
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Rule of thumb:** If you need more than 2-3 greps/seds, switch to jq.
|
|
569
|
+
|
|
570
|
+
}}}
|
|
571
|
+
|
|
572
|
+
### Save Responses for Testing {{{
|
|
573
|
+
|
|
574
|
+
**Why:** Don't hammer APIs during development
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
# ❌ Bad: hits API every time you test
|
|
578
|
+
curl -s https://api.example.com/users | grep "name"
|
|
579
|
+
curl -s https://api.example.com/users | grep "email"
|
|
580
|
+
curl -s https://api.example.com/users | grep "phone"
|
|
581
|
+
|
|
582
|
+
# ✅ Good: save once, test multiple times
|
|
583
|
+
curl -s https://api.example.com/users > users.json
|
|
584
|
+
cat users.json | grep "name"
|
|
585
|
+
cat users.json | grep "email"
|
|
586
|
+
cat users.json | grep "phone"
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
}}}
|
|
590
|
+
|
|
591
|
+
### Handle Errors Gracefully {{{
|
|
592
|
+
|
|
593
|
+
**Check for empty responses:**
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# Store response
|
|
597
|
+
RESPONSE=$(curl -s https://api.example.com/users)
|
|
598
|
+
|
|
599
|
+
# Check if empty
|
|
600
|
+
if [ -z "$RESPONSE" ]; then
|
|
601
|
+
echo "Error: Empty response"
|
|
602
|
+
exit 1
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
# Process
|
|
606
|
+
echo "$RESPONSE" | grep "name"
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
}}}
|
|
610
|
+
|
|
611
|
+
### Timeout Long Requests {{{
|
|
612
|
+
|
|
613
|
+
**Prevent hanging scripts:**
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
# Default: waits forever
|
|
617
|
+
curl -s https://slow-api.example.com
|
|
618
|
+
|
|
619
|
+
# With timeout: fails after 10 seconds
|
|
620
|
+
curl -s --max-time 10 https://slow-api.example.com
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
}}}
|
|
624
|
+
|
|
625
|
+
}}}
|
|
626
|
+
|
|
627
|
+
## 📚 Quick Reference {{{
|
|
628
|
+
|
|
629
|
+
### Essential curl Flags {{{
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
-s # Silent (no progress bar)
|
|
633
|
+
-i # Include headers in output
|
|
634
|
+
-I # Headers only (no body)
|
|
635
|
+
-L # Follow redirects
|
|
636
|
+
-w "%{http_code}" # Print HTTP status code
|
|
637
|
+
-o /dev/null # Discard response body
|
|
638
|
+
-H "Key: Value" # Add custom header
|
|
639
|
+
--max-time 10 # Timeout after 10 seconds
|
|
640
|
+
-X POST # HTTP method (POST, PUT, DELETE, etc.)
|
|
641
|
+
-d "key=value" # Send data (POST body)
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
}}}
|
|
645
|
+
|
|
646
|
+
### Common Pipe Patterns {{{
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
# Filter
|
|
650
|
+
curl -s URL | grep "pattern"
|
|
651
|
+
|
|
652
|
+
# Count
|
|
653
|
+
curl -s URL | wc -l
|
|
654
|
+
|
|
655
|
+
# Count matches
|
|
656
|
+
curl -s URL | grep "pattern" | wc -l
|
|
657
|
+
|
|
658
|
+
# First N lines
|
|
659
|
+
curl -s URL | head -n 10
|
|
660
|
+
|
|
661
|
+
# Last N lines
|
|
662
|
+
curl -s URL | tail -n 10
|
|
663
|
+
|
|
664
|
+
# Transform
|
|
665
|
+
curl -s URL | sed 's/old/new/g'
|
|
666
|
+
|
|
667
|
+
# Deduplicate
|
|
668
|
+
curl -s URL | grep "field" | sort | uniq
|
|
669
|
+
|
|
670
|
+
# Save filtered results
|
|
671
|
+
curl -s URL | grep "active" > output.txt
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
}}}
|
|
675
|
+
|
|
676
|
+
### One-Liner Templates {{{
|
|
677
|
+
|
|
678
|
+
```bash
|
|
679
|
+
# Health check
|
|
680
|
+
curl -s -w "%{http_code}" -o /dev/null https://api.example.com/health
|
|
681
|
+
|
|
682
|
+
# Count records
|
|
683
|
+
curl -s https://api.example.com/users | grep "\"id\":" | wc -l
|
|
684
|
+
|
|
685
|
+
# Extract field
|
|
686
|
+
curl -s https://api.example.com/config | grep "version" | sed 's/.*"version": "\(.*\)".*/\1/'
|
|
687
|
+
|
|
688
|
+
# Monitor errors
|
|
689
|
+
curl -s https://logs.example.com/today | grep "ERROR" | wc -l
|
|
690
|
+
|
|
691
|
+
# Test endpoint
|
|
692
|
+
curl -s https://api.example.com/users | grep -q "data" && echo "OK" || echo "FAIL"
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
}}}
|
|
696
|
+
|
|
697
|
+
}}}
|
|
698
|
+
|
|
699
|
+
## 🎓 Advanced Topics {{{
|
|
700
|
+
|
|
701
|
+
### Parallel Requests with xargs {{{
|
|
702
|
+
|
|
703
|
+
**Use case:** Check multiple endpoints simultaneously
|
|
704
|
+
|
|
705
|
+
```bash
|
|
706
|
+
# Check health of multiple services
|
|
707
|
+
echo -e "api.service1.com\napi.service2.com\napi.service3.com" \
|
|
708
|
+
| xargs -I {} -P 3 curl -s -w "{} → %{http_code}\n" -o /dev/null https://{}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Explanation:**
|
|
712
|
+
- `-I {}`: Replace `{}` with each line
|
|
713
|
+
- `-P 3`: Run 3 parallel processes
|
|
714
|
+
- `-w "... %{http_code}"`: Print status code
|
|
715
|
+
|
|
716
|
+
}}}
|
|
717
|
+
|
|
718
|
+
### JSON Stream Processing {{{
|
|
719
|
+
|
|
720
|
+
**Use case:** Process large API responses line-by-line
|
|
721
|
+
|
|
722
|
+
```bash
|
|
723
|
+
# Stream large response
|
|
724
|
+
curl -s https://api.example.com/large-dataset \
|
|
725
|
+
| while IFS= read -r line; do
|
|
726
|
+
echo "$line" | grep "active" >> active-records.txt
|
|
727
|
+
done
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
**Use when:** API returns huge responses, need to filter during download.
|
|
731
|
+
|
|
732
|
+
}}}
|
|
733
|
+
|
|
734
|
+
### Authenticated API Requests {{{
|
|
735
|
+
|
|
736
|
+
**JWT token example:**
|
|
737
|
+
|
|
738
|
+
```bash
|
|
739
|
+
# Store token
|
|
740
|
+
TOKEN="your-jwt-token-here"
|
|
741
|
+
|
|
742
|
+
# Authenticated request
|
|
743
|
+
curl -s -H "Authorization: Bearer $TOKEN" \
|
|
744
|
+
https://api.example.com/protected \
|
|
745
|
+
| grep "data"
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Environment variable:**
|
|
749
|
+
|
|
750
|
+
```bash
|
|
751
|
+
# In .env or .bashrc
|
|
752
|
+
export API_TOKEN="your-token"
|
|
753
|
+
|
|
754
|
+
# In script
|
|
755
|
+
curl -s -H "Authorization: Bearer $API_TOKEN" https://api.example.com/protected
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
}}}
|
|
759
|
+
|
|
760
|
+
### POST Requests with Data {{{
|
|
761
|
+
|
|
762
|
+
**Send JSON data:**
|
|
763
|
+
|
|
764
|
+
```bash
|
|
765
|
+
# POST JSON
|
|
766
|
+
curl -s -X POST \
|
|
767
|
+
-H "Content-Type: application/json" \
|
|
768
|
+
-d '{"name": "John", "email": "john@example.com"}' \
|
|
769
|
+
https://jsonplaceholder.typicode.com/users \
|
|
770
|
+
| grep "id"
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
**POST form data:**
|
|
774
|
+
|
|
775
|
+
```bash
|
|
776
|
+
curl -s -X POST \
|
|
777
|
+
-d "name=John" \
|
|
778
|
+
-d "email=john@example.com" \
|
|
779
|
+
https://httpbin.org/post \
|
|
780
|
+
| grep "form"
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
}}}
|
|
784
|
+
|
|
785
|
+
}}}
|
|
786
|
+
|
|
787
|
+
## 💡 Pro Tips {{{
|
|
788
|
+
|
|
789
|
+
1. **Combine with watch for live monitoring:**
|
|
790
|
+
```bash
|
|
791
|
+
watch -n 5 'curl -s https://api.example.com/metrics | grep "active" | wc -l'
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
2. **Save complex one-liners as shell functions:**
|
|
795
|
+
```bash
|
|
796
|
+
# In .bashrc or .zshrc
|
|
797
|
+
api_health() {
|
|
798
|
+
curl -s -w "%{http_code}" -o /dev/null "https://api.example.com/$1"
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
# Usage
|
|
802
|
+
api_health users # Check /users endpoint
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
3. **Use aliases for frequent API checks:**
|
|
806
|
+
```bash
|
|
807
|
+
alias check-api='curl -s https://api.myapp.com/health | grep "ok"'
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
4. **Pipe to pbcopy (Mac) or xclip (Linux) to copy output:**
|
|
811
|
+
```bash
|
|
812
|
+
curl -s https://api.example.com/users | grep "email" | pbcopy
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
5. **Chain with git for API version tracking:**
|
|
816
|
+
```bash
|
|
817
|
+
curl -s https://api.example.com/schema > schema.json
|
|
818
|
+
git diff schema.json # See what changed
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
}}}
|
|
822
|
+
|
|
823
|
+
## 🚀 Next Steps {{{
|
|
824
|
+
|
|
825
|
+
**Master these progressively:**
|
|
826
|
+
|
|
827
|
+
1. ✅ **Level 1:** `curl + grep` (filtering)
|
|
828
|
+
2. ✅ **Level 2:** `curl + grep + wc` (counting)
|
|
829
|
+
3. ✅ **Level 3:** `curl + sed` (transforming)
|
|
830
|
+
4. 🎯 **Level 4:** `curl + jq` (proper JSON parsing)
|
|
831
|
+
5. 🎯 **Level 5:** Write shell scripts with error handling
|
|
832
|
+
6. 🎯 **Level 6:** Automate with cron jobs
|
|
833
|
+
|
|
834
|
+
**Related skills:**
|
|
835
|
+
- Learn `jq` for advanced JSON manipulation
|
|
836
|
+
- Study bash scripting for automation
|
|
837
|
+
- Explore `watch` for continuous monitoring
|
|
838
|
+
- Master `awk` for columnar data processing
|
|
839
|
+
|
|
840
|
+
}}}
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
**Remember:** curl + pipes turns your terminal into a powerful API client. No GUI needed! 🚀
|