FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour OpenClaw concepts on HIX
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
OpenClaw concepts on HIX
Posted: Sun Feb 15, 2026 08:42 PM

HIX Cron/Task Testing - Walkthrough

🎯 Objetivo

Crear y probar ejemplos completos de la nueva funcionalidad de gestión de tareas (cron) de HIX, que permite ejecutar threads de Harbour en segundo plano.

📦 Archivos Creados

He creado 6 archivos de prueba en c:\hix para demostrar diferentes capacidades de la funcionalidad de tareas:

1. [test_init.prg](file:///c:/hix/test_init.prg)

Propósito: Ejemplo básico - Reloj que muestra la hora cada segundo

URL: http://localhost/task/test_init.prg

Código:

Code (harbour): Select all Collapse
function main()
	? 'Init Thread Task: ', HIX_AddTask( 'TIME', hb_threadStart( @zTime() ) )	
retu nil 

function zTime()
	while .t. 
		_d( time() )
		hb_idleSleep(1)
	end 
retu nil

Qué hace:

  • Inicia una tarea llamada 'TIME'
  • Muestra la hora actual cada segundo
  • Perfecto para verificar que la funcionalidad básica funciona

---

2. [test_counter.prg](file:///c:/hix/test_counter.prg) 🔢

Propósito: Contador incremental que demuestra persistencia de variables

URL: http://localhost/task/test_counter.prg

Características:

  • Contador que incrementa cada 2 segundos
  • Muestra cómo mantener estado en un thread
  • Útil para verificar que las variables persisten

---

3. [test_logger.prg](file:///c:/hix/test_logger.prg) 📝

Propósito: Sistema de logging con timestamps

URL: http://localhost/task/test_logger.prg

Características:

  • Escribe logs cada 5 segundos
  • Archivo de salida: .log\task_logger.txt
  • Incluye fecha y hora en cada entrada
  • Demuestra I/O de archivos desde threads

---

4. [test_multiple.prg](file:///c:/hix/test_multiple.prg) 🔀

Propósito: Múltiples tareas concurrentes

URL: http://localhost/task/test_multiple.prg

Tareas que inicia:

  1. RELOJ: Hora cada segundo
  2. STATS: Estadísticas del sistema cada 10 segundos
  3. HEARTBEAT: Señal de vida cada 3 segundos

Código relevante:

Code (harbour): Select all Collapse
HIX_AddTask( 'RELOJ', hb_threadStart( @zClock() ) )
HIX_AddTask( 'STATS', hb_threadStart( @zStats() ) )
HIX_AddTask( 'HEARTBEAT', hb_threadStart( @zHeartBeat() ) )

Qué demuestra:

  • Ejecución paralela de múltiples threads
  • Diferentes intervalos de ejecución
  • Monitoreo concurrente

---

5. [test_status.prg](file:///c:/hix/test_status.prg) 📊

Propósito: Monitor de estado de tareas (placeholder)

URL: http://localhost/task/test_status.prg

Nota: Este archivo es un esqueleto para futuras implementaciones que requieren funciones adicionales como:

  • HIX_GetTasks() - Para listar tareas activas
  • HIX_GetTaskStatus() - Para obtener estado de una tarea
  • HIX_ListTasks() - Para enumerar todas las tareas

---

6. [test_scheduled.prg](file:///c:/hix/test_scheduled.prg) ⏲️

Propósito: Tareas programadas estilo cron

URL: http://localhost/task/test_scheduled.prg

Características:

  • Tarea horaria automática (cada vez que cambia la hora)
  • Tareas específicas en horarios definidos:
  • 09:00 - Inicio del día
  • 12:00 - Mediodía
  • 18:00 - Fin del día
  • 00:00 - Mantenimiento nocturno
  • Archivo de salida: .log\scheduled_tasks.txt
  • Verificación cada 30 segundos
  • Uso ideal:

    • Backups programados
    • Reportes periódicos
    • Limpieza automática
    • Sincronización de datos

    ---

    🧪 Cómo Probar

    Método 1: Navegador (Recomendado)

    Simplemente navega en tu navegador a cualquiera de estas URLs mientras HIX.exe está corriendo:

    • http://localhost/task/test_init.prg
    • http://localhost/task/test_counter.prg
    • http://localhost/task/test_logger.prg
    • http://localhost/task/test_multiple.prg
    • http://localhost/task/test_status.prg
    • http://localhost/task/test_scheduled.prg

    Método 2: Curl (Línea de comandos)

    Code (cmd): Select all Collapse
    curl http://localhost/task/test_init.prg
    curl http://localhost/task/test_counter.prg
    curl http://localhost/task/test_logger.prg

    Método 3: PowerShell

    Code (powershell): Select all Collapse
    Invoke-WebRequest -Uri "http://localhost/task/test_init.prg"

    🔍 Verificación

    Después de ejecutar las pruebas, verifica:

    1. Salida en consola/logs de HIX:

      • Busca mensajes como [RELOJ], [STATS], [HEARTBEAT]
      • Verifica timestamps actualizándose
  • Archivos de log creados:

  • c:\hix\.log\task_logger.txt - Del test_logger
  • c:\hix\.log\scheduled_tasks.txt - Del test_scheduled
  • Comportamiento esperado:

    • Las tareas deben seguir ejecutándose después de la inicialización
    • Los contadores deben incrementar
    • Los logs deben actualizarse periódicamente
  • 💡 Sugerencias de Casos de Uso Reales

    Basándome en estos ejemplos, aquí hay casos de uso prácticos:

    1. Backup Automático de Bases de Datos

    Code (harbour): Select all Collapse
    function zBackupDB()
        while .t.
            if Time() >= '02:00:00' .and. Time() < '02:05:00'
                // Ejecutar backup a las 2 AM
                BackupDatabase()
            endif
            hb_idleSleep(300) // Check cada 5 minutos
        end
    retu nil

    2. Monitor de Integridad de DBF

    Code (harbour): Select all Collapse
    function zDbIntegrityCheck()
        while .t.
            CheckAllDatabases()
            ReportIssues()
            hb_idleSleep(3600) // Cada hora
        end
    retu nil

    3. Limpieza de Archivos Temporales

    Code (harbour): Select all Collapse
    function zCleanupTemp()
        while .t.
            CleanOldFiles('.tmp', 7) // Archivos > 7 días
            hb_idleSleep(86400) // Una vez al día
        end
    retu nil

    4. Sincronización con API Externa

    Code (harbour): Select all Collapse
    function zApiSync()
        while .t.
            SyncWithExternalAPI()
            LogSyncStatus()
            hb_idleSleep(600) // Cada 10 minutos
        end
    retu nil

    5. Generación de Reportes Programados

    Code (harbour): Select all Collapse
    function zDailyReports()
        while .t.
            if Time() >= '17:00:00' .and. Time() < '17:01:00'
                GenerateDailyReport()
                EmailReport()
            endif
            hb_idleSleep(60)
        end
    retu nil

    🔧 Funciones Útiles de Harbour

    Estas funciones son particularmente útiles en el contexto de tareas:

    FunciónDescripción
    hb_threadStart(@func)Inicia un thread con la función especificada
    hb_idleSleep(nSec)Pausa el thread N segundos
    Time()Hora actual (HH:MM:SS)
    Date()Fecha actual
    DToC(date)Convierte fecha a string
    Memory(0)Memoria en uso (bytes)
    MemoRead(file)Lee archivo de texto completo
    MemoWrit(file, content)Escribe archivo de texto
    hb_valToStr(val)Convierte cualquier valor a string

    Preguntas para Investigar

    1. Gestión de tareas:

      • ¿Cómo detener una tarea? (HIX_RemoveTask(), HIX_StopTask()?)
      • ¿Cómo listar tareas activas? (HIX_ListTasks()?)
      • ¿Cómo obtener estado de una tarea?
  • Límites:

  • ¿Cuántas tareas concurrentes soporta HIX?
  • ¿Hay límite de memoria por tarea?
  • Persistencia:

    • ¿Las tareas sobreviven un reinicio de HIX?
    • ¿Se pueden auto-iniciar tareas al arrancar el servidor?
  • Comunicación:

    • ¿Pueden las tareas compartir datos entre sí?
    • ¿Hay mecanismos de sincronización disponibles?
  • Monitoreo:

    • ¿Existe un dashboard para ver tareas activas?
    • ¿Se puede ver el output de las tareas en tiempo real?
  • 📋 Checklist de Pruebas

    • [ ] Ejecutar test_init.prg y verificar logs
    • [ ] Ejecutar test_counter.prg y ver incremento
    • [ ] Ejecutar test_logger.prg y revisar archivo .log\task_logger.txt
    • [ ] Ejecutar test_multiple.prg y verificar 3 tareas concurrentes
    • [ ] Ejecutar test_scheduled.prg y esperar al cambio de hora
    • [ ] Verificar que las tareas siguen corriendo después de minutos
    • [ ] Intentar detener tareas (si existe la funcionalidad)
    • [ ] Intentar listar tareas activas
    • [ ] Monitorear uso de CPU/memoria con múltiples tareas
    • [ ] Probar qué pasa si se reinicia HIX.exe

    🎓 Conclusión

    He creado una suite completa de ejemplos que cubren:

    Inicialización básica de tareas
    Persistencia de estado con variables
    I/O de archivos desde threads
    Múltiples tareas concurrentes
    Tareas programadas estilo cron
    Casos de uso prácticos documentados

    Los archivos están listos para probar. Simplemente navega a las URLs correspondientes mientras HIX.exe está corriendo en localhost.


    ---

    📁 Resumen de Archivos

    ArchivoLíneasPropósito
    [test_init.prg](file:///c:/hix/test_init.prg)15Reloj básico
    [test_counter.prg](file:///c:/hix/test_counter.prg)17Contador incremental
    [test_logger.prg](file:///c:/hix/test_logger.prg)22Sistema de logging
    [test_multiple.prg](file:///c:/hix/test_multiple.prg)47Tareas concurrentes
    [test_status.prg](file:///c:/hix/test_status.prg)16Monitor de estado
    [test_scheduled.prg](file:///c:/hix/test_scheduled.prg)68Tareas programadas
    TOTAL1856 ejemplos completos

    ¡Listo para probar! 🚀

    regards, saludos

    Antonio Linares
    www.fivetechsoft.com
    Posts: 44158
    Joined: Thu Oct 06, 2005 05:47 PM
    Re: OpenClaw concepts on HIX
    Posted: Sun Feb 15, 2026 08:43 PM

    Guía de Pruebas - Funcionalidad Cron/Tasks de HIX

    📋 Resumen

    Esta guía documenta los ejemplos creados para probar la nueva funcionalidad de gestión de tareas (cron) en HIX. La función HIX_AddTask() permite iniciar threads de Harbour que se ejecutan en segundo plano de forma continua.

    🚀 Archivos de Prueba Creados

    1. test_init.prg - Reloj Básico

    • URL: http://localhost/task/test_init.prg
    • Descripción: Muestra la hora actual cada segundo
    • Uso: Ejemplo básico de inicialización de tareas
    • Código clave:
      Code (harbour): Select all Collapse
        HIX_AddTask( 'TIME', hb_threadStart( @zTime() ) )

    2. test_counter.prg - Contador Incremental

    • URL: http://localhost/task/test_counter.prg
    • Descripción: Contador que incrementa cada 2 segundos
    • Uso: Demuestra variables persistentes en threads
    • Características:
    • Contador numérico incremental
    • Intervalo: 2 segundos
    • Muestra el progreso en tiempo real

    3. test_logger.prg - Sistema de Logging

    • URL: http://localhost/task/test_logger.prg
    • Descripción: Escribe logs con timestamp cada 5 segundos
    • Uso: Demuestra persistencia de datos y escritura de archivos
    • Archivo de salida: .log\task_logger.txt
    • Características:
    • Registro con fecha y hora
    • Escritura en archivo de texto
    • Ideal para monitoreo de procesos largos

    4. test_multiple.prg - Tareas Concurrentes

    • URL: http://localhost/task/test_multiple.prg
    • Descripción: Inicia 3 tareas simultáneas
    • Tareas:
      1. RELOJ: Muestra hora cada segundo
      2. STATS: Estadísticas del sistema cada 10 segundos
      3. HEARTBEAT: Señal de vida cada 3 segundos
    • Uso: Demuestra múltiples threads corriendo en paralelo

    5. test_status.prg - Monitor de Estado

    • URL: http://localhost/task/test_status.prg
    • Descripción: Placeholder para monitoreo de tareas activas
    • Uso: Base para implementar dashboard de tareas
    • Nota: Requiere funciones adicionales como HIX_GetTasks() o HIX_ListTasks()

    6. test_scheduled.prg - Tareas Programadas (Cron-like)

    • URL: http://localhost/task/test_scheduled.prg
    • Descripción: Ejecuta tareas en horarios específicos
    • Características:
    • Tarea horaria automática
    • Tareas específicas a las 09:00, 12:00, 18:00, 00:00
    • Sistema de verificación cada 30 segundos
  • Archivo de salida: .log\scheduled_tasks.txt
  • Uso: Perfecto para backups, reportes, mantenimiento
  • 🧪 Plan de Pruebas Sugerido

    Prueba 1: Inicialización Básica

    1. Asegurarse que HIX.exe está corriendo
    2. Navegar a: http://localhost/task/test_init.prg
    3. Verificar que se muestra el mensaje de inicialización
    4. Revisar logs del servidor para ver la salida del tiempo

    Prueba 2: Contador Persistente

    1. Ejecutar: http://localhost/task/test_counter.prg
    2. Monitorear los logs para ver el contador incrementando
    3. Verificar que el contador persiste y no se reinicia

    Prueba 3: Sistema de Logging

    1. Ejecutar: http://localhost/task/test_logger.prg
    2. Esperar al menos 30 segundos
    3. Revisar el archivo: c:\hix\.log\task_logger.txt
    4. Verificar que contiene múltiples entradas con timestamps

    Prueba 4: Tareas Concurrentes

    1. Ejecutar: http://localhost/task/test_multiple.prg
    2. Monitorear los logs del servidor
    3. Verificar que se ven mensajes de las 3 tareas intercalados:
      • [RELOJ] cada 1 segundo
      • [HEARTBEAT] cada 3 segundos
      • [STATS] cada 10 segundos

    Prueba 5: Tareas Programadas

    1. Ejecutar: http://localhost/task/test_scheduled.prg
    2. Esperar al cambio de hora
    3. Revisar: c:\hix\.log\scheduled_tasks.txt
    4. Verificar que se ejecutan las tareas horarias
    5. Si es posible, probar en las horas especiales (09:00, 12:00, 18:00, 00:00)

    💡 Ideas Adicionales para Probar

    1. Sistema de Backup Automático

    Code (harbour): Select all Collapse
    function zBackupTask()
        while .t.
            // Ejecutar backup cada 6 horas
            DoBackup()
            hb_idleSleep(21600) // 6 horas en segundos
        end
    retu nil

    2. Monitor de Base de Datos

    Code (harbour): Select all Collapse
    function zDbMonitor()
        while .t.
            // Verificar integridad de DBF cada 15 minutos
            CheckDatabaseIntegrity()
            hb_idleSleep(900)
        end
    retu nil

    3. Limpieza de Archivos Temporales

    Code (harbour): Select all Collapse
    function zCleanupTask()
        while .t.
            // Limpiar archivos temporales cada día
            CleanTempFiles()
            hb_idleSleep(86400) // 24 horas
        end
    retu nil

    4. Envío de Reportes por Email

    Code (harbour): Select all Collapse
    function zEmailReports()
        while .t.
            if Time() >= '17:00:00' .and. Time() < '17:01:00'
                SendDailyReport()
            endif
            hb_idleSleep(60)
        end
    retu nil

    5. Monitor de API Externa

    Code (harbour): Select all Collapse
    function zApiMonitor()
        while .t.
            // Verificar disponibilidad de API cada 5 minutos
            CheckApiStatus()
            hb_idleSleep(300)
        end
    retu nil

    📊 Preguntas para Investigar

    1. ¿Cómo detener una tarea?

      • ¿Existe HIX_RemoveTask('TASKNAME')?
      • ¿Hay un HIX_StopTask()?
  • ¿Cómo listar tareas activas?

  • ¿Existe HIX_ListTasks() o HIX_GetTasks()?
  • ¿Devuelve un array con información de las tareas?
  • ¿Límite de tareas concurrentes?

    • ¿Cuántas tareas puede manejar HIX simultáneamente?
  • ¿Persistencia de tareas?

    • ¿Las tareas sobreviven un reinicio de HIX?
    • ¿Se pueden guardar/cargar configuraciones de tareas?
  • ¿Comunicación entre tareas?

    • ¿Pueden las tareas compartir datos?
    • ¿Hay mecanismos de sincronización?
  • 🔧 Funciones Útiles de Harbour para Tasks

    • hb_threadStart(@function) - Inicia un thread
    • hb_idleSleep(nSeconds) - Pausa el thread
    • _d(cMessage) - Debug output (probablemente)
    • Time() - Hora actual
    • Date() - Fecha actual
    • DToC(dDate) - Convierte fecha a string
    • Memory(0) - Memoria en uso
    • MemoRead(cFile) / MemoWrit(cFile, cContent) - I/O de archivos

    📝 Notas Importantes

    • Todas las tareas corren en un loop infinito (while .t.)
    • Es importante usar hb_idleSleep() para no saturar el CPU
    • Los logs se pueden acumular, considerar rotación de logs
    • Manejar errores apropiadamente en tareas de producción
    • Las tareas son ideales para:
    • Monitoreo continuo
    • Procesamiento en background
    • Tareas programadas (cron-like)
    • Sincronización de datos
    • Limpieza automática
    • Backups programados

    🎯 Próximos Pasos

    1. Probar todos los ejemplos en localhost
    2. Documentar el comportamiento observado
    3. Identificar funciones adicionales de la API de HIX_AddTask
    4. Crear ejemplos de casos de uso reales
    5. Implementar manejo de errores robusto
    6. Considerar sistema de notificaciones para tareas críticas
    regards, saludos

    Antonio Linares
    www.fivetechsoft.com
    Posts: 44158
    Joined: Thu Oct 06, 2005 05:47 PM
    Re: OpenClaw concepts on HIX
    Posted: Sun Feb 15, 2026 09:13 PM

    test_counter.prg

    // test_counter.prg
    // Ejemplo de contador incremental
    // Ejecutar desde: http://localhost/task/test_counter.prg
    
    function main()
    	
    ? 'Init Counter Task: ', HIX_AddTask( 'COUNTER', hb_threadStart( @zCounter() ) )	
    
    retu nil 
    
    // Función contador que incrementa cada 2 segundos
    function zCounter()
        local nCount := 0
    
    while .t. 
    nCount++
    _d( 'Counter: ' + str(nCount) )
    hb_idleSleep(2)
    end 
    
    retu nil

    test_logger.prg

    // test_logger.prg
    // Ejemplo de logger con fecha y hora
    // Ejecutar desde: http://localhost/task/test_logger.prg
    
    function main()
    	
    ? 'Init Logger Task: ', HIX_AddTask( 'LOGGER', hb_threadStart( @zLogger() ) )	
    
    retu nil 
    
    // Función logger que registra fecha/hora cada 5 segundos
    function zLogger()
        local cLogFile := '.log\task_logger.txt'
    
    while .t. 
    // Escribir al log
    MemoWrit( cLogFile, ;
        MemoRead(cLogFile) + ;
        DToC(Date()) + ' ' + Time() + ' - Log Entry' + Chr(13) + Chr(10) )
    	
    _d( 'Log written: ' + DToC(Date()) + ' ' + Time() )
    hb_idleSleep(5)
    end 
    
    retu nil

    test_multiple.prg

    // test_multiple.prg
    // Ejemplo de múltiples tareas concurrentes
    // Ejecutar desde: http://localhost/task/test_multiple.prg
    
    function main()
        local cHtml := ''
    	
    cHtml += '<h2>Inicializando múltiples tareas...</h2>'
    cHtml += '<ul>'
    cHtml += '<li>Tarea RELOJ: ' + hb_valToStr( HIX_AddTask( 'RELOJ', hb_threadStart( @zClock() ) ) ) + '</li>'
    cHtml += '<li>Tarea STATS: ' + hb_valToStr( HIX_AddTask( 'STATS', hb_threadStart( @zStats() ) ) ) + '</li>'
    cHtml += '<li>Tarea HEARTBEAT: ' + hb_valToStr( HIX_AddTask( 'HEARTBEAT', hb_threadStart( @zHeartBeat() ) ) ) + '</li>'
    cHtml += '</ul>'
    cHtml += '<p>Tareas iniciadas correctamente</p>'
    
    ? cHtml
    
    retu nil 
    
    // Tarea 1: Reloj que muestra la hora cada segundo
    function zClock()
        while .t. 
        _d( '[RELOJ] ' + Time() )
        hb_idleSleep(1)
        end 
    retu nil
    
    // Tarea 2: Estadísticas del sistema cada 10 segundos
    function zStats()
        local nCount := 0
        while .t. 
        nCount++
        _d( '[STATS] Iteración #' + str(nCount) + ' - Memoria: ' + str(Memory(0)) + ' bytes' )
        hb_idleSleep(10)
        end 
    retu nil
    
    // Tarea 3: HeartBeat cada 3 segundos
    function zHeartBeat()
        while .t. 
        _d( '[HEARTBEAT] ♥ Sistema activo' )
        hb_idleSleep(3)
        end 
    retu nil

    test_scheduled.prg

    // test_scheduled.prg
    // Ejemplo de tareas programadas (cron-like)
    // Ejecutar desde: http://localhost/task/test_scheduled.prg
    
    function main()
    	
    ? 'Init Scheduled Task: ', HIX_AddTask( 'SCHEDULED', hb_threadStart( @zScheduledTask() ) )	
    
    retu nil 
    
    // Tarea que se ejecuta en momentos específicos
    function zScheduledTask()
        local cLastHour := ''
        local cCurrentHour := ''
        local cLogFile := '.log\scheduled_tasks.txt'
    
    while .t. 
    cCurrentHour := SubStr(Time(), 1, 2)  // Obtener hora actual
    	
    // Ejecutar tarea cada hora (cuando cambia la hora)
    if cCurrentHour != cLastHour
    cLastHour := cCurrentHour
    		
    // Tarea programada por hora
    DoHourlyTask(cLogFile)
    		
    // Tareas específicas según la hora
    do case
    case cCurrentHour == '09'
    _d( '[SCHEDULED] Tarea de inicio del día ejecutada' )
    MemoWrit( cLogFile, MemoRead(cLogFile) + ;
        DToC(Date()) + ' 09:00 - Inicio del día' + Chr(13) + Chr(10) )
    				
    case cCurrentHour == '12'
    _d( '[SCHEDULED] Tarea del mediodía ejecutada' )
    MemoWrit( cLogFile, MemoRead(cLogFile) + ;
        DToC(Date()) + ' 12:00 - Mediodía' + Chr(13) + Chr(10) )
    				
    case cCurrentHour == '18'
    _d( '[SCHEDULED] Tarea de fin del día ejecutada' )
    MemoWrit( cLogFile, MemoRead(cLogFile) + ;
        DToC(Date()) + ' 18:00 - Fin del día' + Chr(13) + Chr(10) )
    				
    case cCurrentHour == '00'
    _d( '[SCHEDULED] Tarea de mantenimiento nocturno ejecutada' )
    MemoWrit( cLogFile, MemoRead(cLogFile) + ;
        DToC(Date()) + ' 00:00 - Mantenimiento nocturno' + Chr(13) + Chr(10) )
    endcase
    endif
    	
    // Dormir 30 segundos antes de verificar de nuevo
    hb_idleSleep(30)
    end 
    
    retu nil
    
    // Tarea que se ejecuta cada hora
    function DoHourlyTask(cLogFile)
        _d( '[SCHEDULED] Tarea horaria ejecutada: ' + Time() )
        MemoWrit( cLogFile, MemoRead(cLogFile) + ;
            DToC(Date()) + ' ' + Time() + ' - Tarea horaria' + Chr(13) + Chr(10) )
    retu nil
    regards, saludos

    Antonio Linares
    www.fivetechsoft.com
    Posts: 18
    Joined: Wed Feb 12, 2020 04:55 PM
    Re: OpenClaw concepts on HIX
    Posted: Sun Feb 15, 2026 09:25 PM

    Muy bueno Antonio. Gracias. Nos abre mas la mente para ser creativos. Cordial Saludo.

    Posts: 44158
    Joined: Thu Oct 06, 2005 05:47 PM
    Re: OpenClaw concepts on HIX
    Posted: Sun Feb 15, 2026 09:37 PM

    El concepto de "Skills" en HIX:

    skills_loader.prg

    // skill_loader.prg - Con auto-generación de skills de ejemplo
    #include "hix.ch"
    
    function main()
        local cHtml
        local aDirs, cDir, cSkillPath, oSkill
        local nTotal := 0
        
    
    // Auto-generar skills de ejemplo si no existen
    AutoGenerateDemoSkills()
    
    cHtml := '<!DOCTYPE html><html><head>'
    cHtml += '<meta charset="UTF-8">'
    cHtml += '<title>HIX Skills System</title>'
    cHtml += '<style>'
    cHtml += 'body { font-family: Arial, sans-serif; max-width: 900px; margin: 20px auto; padding: 20px; }'
    cHtml += 'h1 { color: #333; border-bottom: 3px solid #007bff; padding-bottom: 10px; }'
    cHtml += '.skill-card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 8px; background: #f9f9f9; }'
    cHtml += '.skill-name { font-size: 20px; font-weight: bold; color: #333; }'
    cHtml += '.skill-version { color: #666; font-size: 14px; }'
    cHtml += '.skill-desc { margin: 10px 0; color: #555; }'
    cHtml += '.badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; margin-right: 5px; display: inline-block; }'
    cHtml += '.badge-danger { background: #dc3545; color: white; }'
    cHtml += '.badge-warning { background: #ffc107; color: black; }'
    cHtml += '.badge-info { background: #17a2b8; color: white; }'
    cHtml += '.summary { background: #e7f3ff; padding: 15px; border-radius: 8px; margin: 20px 0; }'
    cHtml += '</style></head><body>'
    
    aDirs := Directory('skills\*.*', 'D')
    
    
    cHtml += '<h1>📦 HIX Skills System</h1>'
    
    
    for each cDir in aDirs
        if cDir[5] == 'D' .and. !(cDir[1] $ '.,') .and. cDir[1] != '..'
            cSkillPath := 'skills\' + cDir[1]
            oSkill := ParseSkill(cSkillPath)
            
            if oSkill != NIL
                nTotal++
                cHtml += '<div class="skill-card">'
                cHtml += '<div class="skill-name">' + oSkill['name'] + ' '
                cHtml += '<span class="skill-version">v' + oSkill['version'] + '</span>'
                cHtml += '</div>'
                
                cHtml += '<div class="skill-desc">' + oSkill['description'] + '</div>'
                
                cHtml += '<div>'
                cHtml += '<span class="badge badge-info">📂 ' + hb_HGetDef(oSkill, 'category', 'general') + '</span> '
                
                if hb_HGetDef(oSkill, 'requires_permission', .t.)
                    cHtml += '<span class="badge badge-warning">🔐 Requiere Permiso</span> '
                endif
                
                if oSkill['dangerous']
                    cHtml += '<span class="badge badge-danger">⚠️ PELIGROSA</span>'
                endif
    
                
                cHtml += '</div>'
                cHtml += '</div>'
            endif
        endif
    next
    
    cHtml += '<div class="summary">'
    cHtml += '<strong>✅ Total:</strong> ' + AllTrim(Str(nTotal)) + ' skills cargadas correctamente'
    cHtml += '</div>'
    
    
    cHtml += '</body></html>'
    
    UWrite(cHtml)
    retu ""
    
    //------------------------------------------------------------------------------
    // Auto-genera las 3 skills de demostración si no existen
    //------------------------------------------------------------------------------
    function AutoGenerateDemoSkills()
        
    
    if !File('skills\gmail\SKILL.md')
        CreateGmailSkill()
    endif
    
    if !File('skills\whatsapp\SKILL.md')
        CreateWhatsAppSkill()
    endif
    
    if !File('skills\filesystem\SKILL.md')
        CreateFileSystemSkill()
    endif
    
    retu NIL
    
    function CreateGmailSkill()
        local cContent := ''
        
    
    MkDir('skills')
    MkDir('skills\gmail')
    
    cContent += '---' + Chr(13) + Chr(10)
    cContent += 'name: "gmail"' + Chr(13) + Chr(10)
    cContent += 'version: "1.0.0"' + Chr(13) + Chr(10)
    cContent += 'description: "Gestiona correos electrónicos mediante Gmail API"' + Chr(13) + Chr(10)
    cContent += 'category: "communication"' + Chr(13) + Chr(10)
    cContent += 'requires_permission: true' + Chr(13) + Chr(10)
    cContent += 'dangerous: false' + Chr(13) + Chr(10)
    cContent += '---' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += '# Gmail Skill' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += 'Permite enviar, leer y gestionar correos electrónicos mediante Gmail API.' + Chr(13) + Chr(10)
    
    MemoWrit('skills\gmail\SKILL.md', cContent)
    retu NIL
    
    function CreateWhatsAppSkill()
        local cContent := ''
        
    
    MkDir('skills')
    MkDir('skills\whatsapp')
    
    cContent += '---' + Chr(13) + Chr(10)
    cContent += 'name: "whatsapp"' + Chr(13) + Chr(10)
    cContent += 'version: "1.0.0"' + Chr(13) + Chr(10)
    cContent += 'description: "Envía y recibe mensajes de WhatsApp mediante WhatsApp Business API"' + Chr(13) + Chr(10)
    cContent += 'category: "communication"' + Chr(13) + Chr(10)
    cContent += 'requires_permission: true' + Chr(13) + Chr(10)
    cContent += 'dangerous: false' + Chr(13) + Chr(10)
    cContent += '---' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += '# WhatsApp Skill' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += 'Permite enviar mensajes, imágenes y archivos por WhatsApp.' + Chr(13) + Chr(10)
    
    MemoWrit('skills\whatsapp\SKILL.md', cContent)
    retu NIL
    
    function CreateFileSystemSkill()
        local cContent := ''
        
    
    MkDir('skills')
    MkDir('skills\filesystem')
    
    cContent += '---' + Chr(13) + Chr(10)
    cContent += 'name: "filesystem"' + Chr(13) + Chr(10)
    cContent += 'version: "1.0.0"' + Chr(13) + Chr(10)
    cContent += 'description: "Gestiona archivos y directorios del sistema de archivos"' + Chr(13) + Chr(10)
    cContent += 'category: "filesystem"' + Chr(13) + Chr(10)
    cContent += 'requires_permission: true' + Chr(13) + Chr(10)
    cContent += 'dangerous: true' + Chr(13) + Chr(10)
    cContent += '---' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += '# FileSystem Skill' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += 'Permite listar, buscar, leer, escribir, mover y eliminar archivos.' + Chr(13) + Chr(10)
    cContent += Chr(13) + Chr(10)
    cContent += '⚠️ **ADVERTENCIA**: Esta skill puede modificar/eliminar archivos del sistema.' + Chr(13) + Chr(10)
    
    MemoWrit('skills\filesystem\SKILL.md', cContent)
    retu NIL
    
    function ParseSkill(cPath)
        local cFile := cPath + '\SKILL.md'
        local cContent, aLines, cLine
        local oSkill := {=>}
        local lInFront := .f.
        local aParts, cKey, cValue
        
    
    if !File(cFile)
        retu NIL
    endif
    
    cContent := MemoRead(cFile)
    if Empty(cContent)
        retu NIL
    endif
    
    aLines := hb_ATokens(cContent, Chr(13) + Chr(10))
    
    for each cLine in aLines
        cLine := AllTrim(cLine)
        
        if cLine == '---'
            lInFront := !lInFront
            loop
        endif
        
        if lInFront .and. ':' $ cLine
            aParts := hb_ATokens(cLine, ':')
            if Len(aParts) >= 2
                cKey := AllTrim(aParts[1])
                cValue := AllTrim(SubStr(cLine, At(':', cLine) + 1))
                cValue := StrTran(cValue, '"', '')
                cValue := StrTran(cValue, "'", "")
                
                do case
                    case Lower(cValue) == 'true'
                        oSkill[cKey] := .t.
                    case Lower(cValue) == 'false'
                        oSkill[cKey] := .f.
                        otherwise
                        oSkill[cKey] := cValue
                endcase
            endif
        endif
        
        if !lInFront .and. !Empty(oSkill)
            exit
        endif
    next
    
    // Defaults
    if !hb_HHasKey(oSkill, 'version')
        oSkill['version'] := '1.0.0'
    endif
    if !hb_HHasKey(oSkill, 'dangerous')
        oSkill['dangerous'] := .f.
    endif
    if !hb_HHasKey(oSkill, 'requires_permission')
        oSkill['requires_permission'] := .t.
    endif
    
    if Empty(hb_HGetDef(oSkill, 'name', ''))
        retu NIL
    endif
    
    retu oSkill
    regards, saludos

    Antonio Linares
    www.fivetechsoft.com
    Posts: 44158
    Joined: Thu Oct 06, 2005 05:47 PM
    Re: OpenClaw concepts on HIX
    Posted: Mon Feb 16, 2026 01:59 PM

    HIX + Gemini (chat memory support)

    gemini_chat.prg

    function main()
        local cHtml := ""
        
    
    cHtml += "<!DOCTYPE html>"
    cHtml += "<html lang='es'>"
    cHtml += "<head>"
    cHtml += "    <meta charset='UTF-8'>"
    cHtml += "    <meta name='viewport' content='width=device-width, initial-scale=1.0'>"
    cHtml += "    <title>HIX AIOS v13 - Chat Premium</title>"
    cHtml += "    <link href='https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=JetBrains+Mono&display=swap' rel='stylesheet'>"
    cHtml += "    <style>"
    cHtml += "        :root {"
    cHtml += "            --bg: #0a0b10;"
    cHtml += "            --glass: rgba(255, 255, 255, 0.05);"
    cHtml += "            --glass-border: rgba(255, 255, 255, 0.1);"
    cHtml += "            --accent: linear-gradient(135deg, #6366f1, #a855f7);"
    cHtml += "            --text: #e2e8f0;"
    cHtml += "            --bot-bubble: rgba(30, 41, 59, 0.7);"
    cHtml += "            --user-bubble: linear-gradient(135deg, #4f46e5, #7c3aed);"
    cHtml += "        }"
    cHtml += "        * { margin:0; padding:0; box-sizing: border-box; }"
    cHtml += "        body {"
    cHtml += "            font-family: 'Outfit', sans-serif;"
    cHtml += "            background: var(--bg);"
    cHtml += "            background-image: radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),"
    cHtml += "                              radial-gradient(circle at 80% 80%, rgba(168, 85, 247, 0.15) 0%, transparent 40%);"
    cHtml += "            color: var(--text);"
    cHtml += "            height: 100vh;"
    cHtml += "            display: flex;"
    cHtml += "            flex-direction: column;"
    cHtml += "            overflow: hidden;"
    cHtml += "        }"
    cHtml += "        header {"
    cHtml += "            padding: 1.5rem 2rem;"
    cHtml += "            background: var(--glass);"
    cHtml += "            backdrop-filter: blur(10px);"
    cHtml += "            border-bottom: 1px solid var(--glass-border);"
    cHtml += "            display: flex;"
    cHtml += "            justify-content: space-between;"
    cHtml += "            align-items: center;"
    cHtml += "            z-index: 10;"
    cHtml += "        }"
    cHtml += "        header h1 { font-size: 1.5rem; font-weight: 600; letter-spacing: -0.5px; }"
    cHtml += "        .version-badge { background: var(--accent); padding: 0.2rem 0.6rem; border-radius: 20px; font-size: 0.75rem; font-weight: bold; }"
    cHtml += "        #chat-container {"
    cHtml += "            flex: 1;"
    cHtml += "            overflow-y: auto;"
    cHtml += "            padding: 2rem;"
    cHtml += "            display: flex;"
    cHtml += "            flex-direction: column;"
    cHtml += "            gap: 1.5rem;"
    cHtml += "        }"
    cHtml += "        .message {"
    cHtml += "            max-width: 85%;"
    cHtml += "            padding: 1rem 1.25rem;"
    cHtml += "            border-radius: 1.2rem;"
    cHtml += "            line-height: 1.6;"
    cHtml += "            animation: fadeIn 0.3s ease-out;"
    cHtml += "        }"
    cHtml += "        @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }"
    cHtml += "        .user { align-self: flex-end; background: var(--user-bubble); color: white; border-bottom-right-radius: 0.2rem; }"
    cHtml += "        .bot { align-self: flex-start; background: var(--bot-bubble); backdrop-filter: blur(5px); border: 1px solid var(--glass-border); border-bottom-left-radius: 0.2rem; }"
    cHtml += "        .error { align-self: center; background: rgba(255,0,0,0.2); color: #ff8888; border: 1px solid #ff0000; padding: 0.5rem 1rem; border-radius: 10px; }"
    cHtml += "        pre { background: #1e1e1e; padding: 1rem; border-radius: 8px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; }"
    cHtml += "        .input-wrapper { padding: 2rem; background: linear-gradient(to top, var(--bg), transparent); }"
    cHtml += "        .input-box {"
    cHtml += "            max-width: 900px; margin: 0 auto; background: var(--glass); backdrop-filter: blur(20px); border: 1px solid var(--glass-border);"
    cHtml += "            border-radius: 1.5rem; padding: 0.5rem; display: flex; align-items: center; gap: 0.5rem;"
    cHtml += "        }"
    cHtml += "        input { flex: 1; background: transparent; border: none; color: white; padding: 0.75rem 1rem; font-size: 1rem; outline: none; }"
    cHtml += "        button { background: var(--accent); border: none; color: white; padding: 0.75rem 1.5rem; border-radius: 1.1rem; font-weight: 600; cursor: pointer; }"
    cHtml += "        button:disabled { background: #444; }"
    cHtml += "        .typing { display: flex; gap: 4px; padding: 10px; }"
    cHtml += "        .dot { width: 8px; height: 8px; background: #6366f1; border-radius: 50%; animation: bounce 1.4s infinite ease-in-out; }"
    cHtml += "        @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }"
    cHtml += "    </style>"
    cHtml += "</head>"
    cHtml += "<body>"
    cHtml += "<header>"
    cHtml += "    <h1>HIX AIOS <span class='version-badge'>v13</span></h1>"
    cHtml += "    <button onclick='clearChat()'>Limpiar</button>"
    cHtml += "</header>"
    cHtml += "<div id='chat-container'></div>"
    cHtml += "<div class='input-wrapper'>"
    cHtml += "    <div class='input-box'>"
    cHtml += "        <input type='text' id='input' placeholder='Pregunta algo...' onkeydown='if(event.keyCode===13) sendMessage()'>"
    cHtml += "        <button id='send-btn' onclick='sendMessage()'>Enviar</button>"
    cHtml += "    </div>"
    cHtml += "</div>"
    cHtml += "<script>"
    cHtml += "    window.onerror = function(m, u, l) { alert('Error: ' + m + ' en ' + l); };"
    cHtml += "    var history_data = [];"
    cHtml += "    function addMessage(role, text) {"
    cHtml += "        var container = document.getElementById('chat-container');"
    cHtml += "        var div = document.createElement('div');"
    cHtml += "        div.className = 'message ' + role;"
    cHtml += "        div.innerHTML = text.split('\\n').join('<br>');"
    cHtml += "        container.appendChild(div);"
    cHtml += "        setTimeout(function() { container.scrollTop = container.scrollHeight; }, 50);"
    cHtml += "    }"
    cHtml += "    function addTyping() {"
    cHtml += "        var container = document.getElementById('chat-container');"
    cHtml += "        var div = document.createElement('div');"
    cHtml += "        div.id = 'typing-indicator';"
    cHtml += "        div.className = 'message bot typing';"
    cHtml += "        div.innerHTML = 'ÔÜÖ´©Å Pensando...';"
    cHtml += "        container.appendChild(div);"
    cHtml += "        container.scrollTop = container.scrollHeight;"
    cHtml += "    }"
    cHtml += "    function removeTyping() {"
    cHtml += "        var indicator = document.getElementById('typing-indicator');"
    cHtml += "        if(indicator) indicator.parentNode.removeChild(indicator);"
    cHtml += "    }"
    cHtml += "    function sendMessage() {"
    cHtml += "        var input = document.getElementById('input');"
    cHtml += "        var text = input.value.trim();"
    cHtml += "        var btn = document.getElementById('send-btn');"
    cHtml += "        if (!text) return;"
    cHtml += "        addMessage('user', text);"
    cHtml += "        input.value = '';"
    cHtml += "        addTyping();"
    cHtml += "        btn.disabled = true;"
    cHtml += "        var params = 'query=' + encodeURIComponent(text);"
    cHtml += "        if (history_data.length > 0) params += '&history=' + encodeURIComponent(JSON.stringify(history_data));"
    cHtml += "        console.log('SENDING:', params);"
    cHtml += "        var xhr = new XMLHttpRequest();"
    cHtml += "        xhr.open('POST', 'gemini_aios_v13.prg', true);"
    cHtml += "        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');"
    cHtml += "        xhr.onreadystatechange = function() {"
    cHtml += "            if (xhr.status !== 200 && xhr.status !== 0) { addMessage('error', 'Status: ' + xhr.status); }"
    cHtml += "            if (xhr.readyState == 4) {"
    cHtml += "                removeTyping(); btn.disabled = false;"
    cHtml += "                if (xhr.status == 200) {"
    cHtml += "                    try {"
    cHtml += "                        var data = JSON.parse(xhr.responseText);"
    cHtml += "                        if (data.success) {"
    cHtml += "                            addMessage('bot', data.text);"
    cHtml += "                            history_data.push({role:'user', parts:[{text:text}]});"
    cHtml += "                            history_data.push({role:'model', parts:[{text:data.text}]});"
    cHtml += "                        } else { addMessage('error', 'Error: ' + data.error); }"
    cHtml += "                    } catch(e) { addMessage('error', 'JSON Error: ' + xhr.responseText.substring(0,100)); }"
    cHtml += "                } else if(xhr.status !== 0) { addMessage('error', 'Server Error: ' + xhr.status); }"
    cHtml += "            }"
    cHtml += "        };"
    cHtml += "        xhr.send(params);"
    cHtml += "    }"
    cHtml += "    function clearChat() {"
    cHtml += "        document.getElementById('chat-container').innerHTML = '';"
    cHtml += "        history_data = [];"
    cHtml += "    }"
    cHtml += "    window.onload = function() {"
    cHtml += "        addMessage('bot', 'HIX AIOS v13 listo. Modo ultra-estable activado.');"
    cHtml += "    };"
    cHtml += "</script>"
    cHtml += "</body></html>"
    
    UWrite(cHtml)
    return ""

    gemini_aios_v13.prg

    // gemini_aios_v13.prg - Memory Fix Version
    #include "hbcurl.ch"
    
    function main()
        local cQuery, cModel, cHistory, cLogDoc := ""
        local hResult
        
    
    cQuery   := UGet("query")
    cModel   := UGet("model")
    cHistory := UGet("history")
    
    if Empty(cQuery)   ; cQuery   := UPost("query")   ; endif
    if Empty(cModel)   ; cModel   := UPost("model")   ; endif
    if Empty(cHistory) ; cHistory := UPost("history") ; endif
    
    // Auto-decode if raw
    if !Empty(cHistory) .and. "%" $ hb_ValToStr(cHistory)
    cHistory := Hix_UrlDecode(hb_ValToStr(cHistory))
    endif
    if !Empty(cQuery) .and. "%" $ hb_ValToStr(cQuery)
    cQuery := Hix_UrlDecode(hb_ValToStr(cQuery))
    endif
    
    cLogDoc := hb_eol() + ">>> [" + Time() + "] NEW REQ" + hb_eol()
    cLogDoc += "QUERY: " + hb_ValToStr(cQuery) + hb_eol()
    
    if cQuery == "ping"
    UWrite('{"success":true, "text":"pong"}')
    return ""
    endif
    
    if Empty(cQuery)
    UWrite('{"success":false, "error":"No query provided"}')
    return ""
    endif
    
    if Empty(cModel) ; cModel := "gemini-2.0-flash" ; endif
    
    hResult := ExecuteReasoningLoop(cQuery, cModel, hb_ValToStr(cHistory), @cLogDoc)
    
    cLogDoc += "<<< [" + Time() + "] RESP SENT" + hb_eol()
    MemoWrit("aios_debug.log", MemoRead("aios_debug.log") + cLogDoc)
    
    UWrite(hb_jsonEncode(hResult))
    return ""
    
    //------------------------------------------------------------------------------
    function ExecuteReasoningLoop(cQuery, cModel, cHistory, cTrace)
        local aFunctions := BuildAiosFunctions()
        local aMessages := {}, nMaxSteps := 5, nStep := 0
        local hResult := {=>}, aStepsArr := {}, hMsg, hGeminiResult, hSkillResult
        local nErr
        
    
    if !Empty(cHistory)
    nErr := hb_jsonDecode(cHistory, @aMessages)
    // In HIX, hb_jsonDecode returns the POS where it stopped. 
    // If it reaches the end, it's successful.
    if ValType(aMessages) != "A" .or. Len(aMessages) == 0
    if nErr == 0 .or. nErr < Len(AllTrim(cHistory))
    cTrace += "!! History Decode Failed. Pos: " + hb_ValToStr(nErr) + hb_eol()
    aMessages := {}
    endif
    endif
    endif
    
    if ValType(aMessages) != "A" ; aMessages := {} ; endif
    cTrace += "HIST_TURNS: " + hb_ValToStr(Len(aMessages)) + hb_eol()
    
    aAdd(aMessages, { "role" => "user", "parts" => { { "text" => cQuery } } })
    
    do while nStep < nMaxSteps
    nStep++
    hGeminiResult := GeminiCallFC(aMessages, aFunctions, cModel, @cTrace)
        
    if !hGeminiResult['success']
    hResult['success'] := .f. ; hResult['error'] := hGeminiResult['error'] ; retu hResult
    endif
        
    hMsg := { "role" => "model", "parts" => hGeminiResult["raw_parts"] }
    aAdd(aMessages, hMsg)
        
    if hGeminiResult['type'] == 'function_call'
    aAdd(aStepsArr, { "type" => "call", "name" => hGeminiResult['function_name'], "args" => hGeminiResult['arguments'] })
    hSkillResult := ExecuteAiosSkill(hGeminiResult['function_name'], hGeminiResult['arguments'])
            
    aAdd(aMessages, { "role" => "function", "parts" => { { "functionResponse" => { "name" => hGeminiResult['function_name'], "response" => { "content" => hb_jsonEncode(hSkillResult) } } } } })
    aAdd(aStepsArr, { "type" => "result", "success" => hSkillResult['success'], "result" => hSkillResult })
    else
    hResult['success'] := .t. ; hResult['text'] := hGeminiResult['text'] ; hResult['steps'] := aStepsArr ; hResult['model_used'] := cModel
    retu hResult
    endif
    enddo
    
    hResult['success'] := .f. ; hResult['error'] := "Max steps"
    retu hResult
    
    //------------------------------------------------------------------------------
    function GeminiCallFC(aMessages, aFunctions, cModel, cTrace)
        local cApiKey := GetAiosApiKey(), cUrl, cJson, hCurl, nError, cResponse := "", hResult := {=>}
        
    
    cUrl := "https://generativelanguage.googleapis.com/v1beta/models/" + cModel + ":generateContent?key=" + cApiKey
    cJson := hb_jsonEncode({ "contents" => aMessages, "tools" => { { "function_declarations" => aFunctions } } })
    
    hCurl := curl_easy_init()
    if !Empty(hCurl)
    curl_easy_setopt(hCurl, HB_CURLOPT_POST, .T.)
    curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl)
    curl_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" })
    curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.)
    curl_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cJson)
    curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP)
        
    nError := curl_easy_perform(hCurl)
    if nError == HB_CURLE_OK
    cResponse := curl_easy_dl_buff_get(hCurl)
    hResult := ParseAiosFCResponse(cResponse, @cTrace)
    else
    hResult['success'] := .f. ; hResult['error'] := "Curl error: " + hb_ValToStr(nError)
    cTrace += "CURL_ERR: " + hb_ValToStr(nError) + hb_eol()
    endif
    curl_easy_cleanup(hCurl)
    else ; hResult['success'] := .f. ; hResult['error'] := "Curl init failed" ; endif
    retu hResult
    
    //------------------------------------------------------------------------------
    function ParseAiosFCResponse(cJSON, cTrace)
        local hResult := { "success" => .f. }, hResponse := {=>}, aParts, nErr := 0
        local nStart, nEnd, cWorking
        
    
    cJSON := StrTran(hb_ValToStr(cJSON), Chr(0), "")
    cWorking := AllTrim(cJSON)
    
    nStart := At("{", cWorking)
    nEnd   := RAt("}", cWorking)
    if nStart > 0 .and. nEnd > nStart
    cWorking := SubStr(cWorking, nStart, nEnd - nStart + 1)
    endif
    
    nErr := hb_jsonDecode(cWorking, @hResponse)
    
    do while nErr > 0 .and. Len(cWorking) > 10
    cWorking := Left(cWorking, Len(cWorking) - 1)
    if Right(cWorking, 1) $ '}"0123456789'
    nErr := hb_jsonDecode(cWorking, @hResponse)
    endif
    enddo
    
    if (nErr == 0 .or. nErr == Len(cWorking)) .and. hb_HHasKey(hResponse, "candidates") .and. Len(hResponse["candidates"]) > 0
    aParts := hResponse["candidates"][1]["content"]["parts"]
    hResult['success'] := .t. ; hResult['raw_parts'] := aParts
        
    if hb_HHasKey(aParts[1], "functionCall")
    hResult['type'] := 'function_call'
    hResult['function_name'] := aParts[1]["functionCall"]["name"]
    hResult['arguments'] := aParts[1]["functionCall"]["args"]
    else
    hResult['type'] := 'text'
    hResult['text'] := hb_HGetDef(aParts[1], "text", "")
    endif
    else
    hResult['error'] := "API JSON malformed (" + hb_ValToStr(nErr) + ")"
    cTrace += "!! Decoder failed on: " + Left(cWorking, 100) + hb_eol()
    endif
    retu hResult
    
    //------------------------------------------------------------------------------
    function BuildAiosFunctions()
        local aFuncs := {}
        aAdd(aFuncs, { "name" => "filesystem_search", "description" => "Search files. pattern, path.", "parameters" => { "type" => "object", "properties" => { "pattern" => { "type" => "string" }, "path" => { "type" => "string" } }, "required" => {"pattern", "path"} } })
        aAdd(aFuncs, { "name" => "filesystem_read", "description" => "Read file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" } }, "required" => {"file"} } })
        aAdd(aFuncs, { "name" => "filesystem_write", "description" => "Write file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" }, "content" => { "type" => "string" } }, "required" => {"file", "content"} } })
        aAdd(aFuncs, { "name" => "shell_execute", "description" => "Execute shell command.", "parameters" => { "type" => "object", "properties" => { "command" => { "type" => "string" } }, "required" => {"command"} } })
        aAdd(aFuncs, { "name" => "identity_get_context", "description" => "Returns the identity and user soul information. Use this to know who you are and who the user is.", "parameters" => { "type" => "object", "properties" => {=>}, "required" => {} } })
    retu aFuncs
    
    //------------------------------------------------------------------------------
    function ExecuteAiosSkill(cName, hArgs)
        local hRes := { "success" => .f., "error" => "" }
        do case
        case cName == 'filesystem_search' .or. cName == 'filesystem_read' .or. cName == 'filesystem_write'
        hRes := Aios_FileSystem(cName, hArgs)
        case cName == 'shell_execute'
        hRes := Aios_Shell(hArgs)
        case cName == 'identity_get_context'
        hRes := Aios_Identity()
        endcase
    retu hRes
    
    //------------------------------------------------------------------------------
    function Aios_Identity()
        local hR := { "success" => .t. }, cId := "", cSoul := ""
        if File("persona\IDENTITY.md") ; cId := MemoRead("persona\IDENTITY.md") ; endif
        if File("persona\SOUL.md")     ; cSoul := MemoRead("persona\SOUL.md")     ; endif
        hR['identity'] := cId
        hR['soul'] := cSoul
        if Empty(cId) .and. Empty(cSoul) ; hR['success'] := .f. ; hR['error'] := "Identity files not found" ; endif
    retu hR
    
    //------------------------------------------------------------------------------
    function Aios_FileSystem(cAct, hP)
        local hR := { "success" => .t. }, cPat, cPath, cFile, aRes := {}
        if cAct == 'filesystem_search'
        cPat := hb_HGetDef(hP, 'pattern', '*.*') ; cPath := hb_HGetDef(hP, 'path', 'c:\hix')
        for each cFile in Directory(cPath + '\' + cPat)
        if !(cFile[1] $ '.,') ; aAdd(aRes, { "name" => cFile[1], "size" => cFile[2], "date" => DToC(cFile[3]), "time" => cFile[4] }) ; endif
        next
        hR['files'] := aRes ; hR['count'] := Len(aRes)
        elseif cAct == 'filesystem_read'
        cFile := hb_HGetDef(hP, 'file', '')
        if File(cFile) ; hR['content'] := Left(MemoRead(cFile), 5000) ; else ; hR['success'] := .f. ; hR['error'] := "File not found" ; endif
        elseif cAct == 'filesystem_write'
        if MemoWrit(hb_HGetDef(hP, 'file', ''), hb_HGetDef(hP, 'content', '')) ; hR['msg'] := "Stored"
        else ; hR['success'] := .f. ; hR['error'] := "Writ error" ; endif
        endif
    retu hR
    
    //------------------------------------------------------------------------------
    function Aios_Shell(hP)
        local hR := { "success" => .t. }, cCmd := hb_HGetDef(hP, 'command', '')
        if Empty(cCmd) ; hR['success'] := .f. ; return hR ; endif
        hb_Run(cCmd + " > shell_out.tmp 2>&1")
        hR['output'] := MemoRead("shell_out.tmp") ; FErase("shell_out.tmp")
    retu hR
    
    //------------------------------------------------------------------------------
    function GetAiosApiKey()
        local cKey := GetEnv("GEMINI_API_KEY")
        if Empty(cKey) .and. File("gemini_config.json")
        cKey := hb_HGetDef(hb_jsonDecode(MemoRead("gemini_config.json")), "api_key", "")
        endif
    retu cKey
    
    //------------------------------------------------------------------------------
    function Hix_UrlDecode( cT )
        local cR := "", n := 1, cX
        if Empty(cT) ; return "" ; endif
        cT := StrTran( cT, "+", " " )
        do while n <= Len( cT )
        if SubStr( cT, n, 1 ) == "%"
        cX := SubStr( cT, n + 1, 2 ) ; cR += Chr( Hix_HexToNum( cX ) ) ; n += 3
        else ; cR += SubStr( cT, n, 1 ) ; n++ ; endif
        enddo
    retu cR
    
    //------------------------------------------------------------------------------
    function Hix_HexToNum( cX )
        local n := 0, i, cC, nV
        cX := Upper( cX )
        for i := 1 to Len( cX )
        cC := SubStr( cX, i, 1 ) ; nV := At( cC, "0123456789ABCDEF" ) - 1
        if nV >= 0 ; n := n * 16 + nV ; endif
        next
    retu n
    regards, saludos

    Antonio Linares
    www.fivetechsoft.com
    Posts: 44158
    Joined: Thu Oct 06, 2005 05:47 PM
    Re: OpenClaw concepts on HIX
    Posted: Mon Feb 16, 2026 02:33 PM

    Persistent support for "PERSONA" files: IDENTITY.md and SOUL.md (like OpenClaw)

    gemini_aios_v13.prg

    // gemini_aios_v13.prg - Memory Fix Version
    #include "hbcurl.ch"
    
    function main()
        local cQuery, cModel, cHistory, cLogDoc := "", hIdContext, cSystemPrompt := ""
        local hResult
        
    
    cQuery   := UGet("query")
    cModel   := UGet("model")
    cHistory := UGet("history")
    
    if Empty(cQuery)   ; cQuery   := UPost("query")   ; endif
    if Empty(cModel)   ; cModel   := UPost("model")   ; endif
    if Empty(cHistory) ; cHistory := UPost("history") ; endif
    
    // Auto-decode if raw
    if !Empty(cHistory) .and. "%" $ hb_ValToStr(cHistory)
    cHistory := Hix_UrlDecode(hb_ValToStr(cHistory))
    endif
    if !Empty(cQuery) .and. "%" $ hb_ValToStr(cQuery)
    cQuery := Hix_UrlDecode(hb_ValToStr(cQuery))
    endif
    
    cLogDoc := hb_eol() + ">>> [" + Time() + "] NEW REQ" + hb_eol()
    cLogDoc += "QUERY: " + hb_ValToStr(cQuery) + hb_eol()
    
    if cQuery == "ping"
    UWrite('{"success":true, "text":"pong"}')
    return ""
    endif
    
    if Empty(cQuery)
    UWrite('{"success":false, "error":"No query provided"}')
    return ""
    endif
    
    if Empty(cModel) ; cModel := "gemini-2.0-flash" ; endif
    
    // Pre-load identity context as system instructions
    hIdContext := Aios_Identity()
    cSystemPrompt := "Eres HIX AIOS (Harbourix AI Operating System)." + hb_eol()
    if hIdContext['success']
    cSystemPrompt += "Tu IDENTIDAD actual:" + hb_eol() + hIdContext['identity'] + hb_eol()
    cSystemPrompt += "Tu ALMA (User Data):" + hb_eol() + hIdContext['soul'] + hb_eol()
    cSystemPrompt += "IMPORTANTE: Puedes actualizar estos ficheros usando filesystem_write en las rutas: " + hb_eol()
    cSystemPrompt += "- persona\IDENTITY.md" + hb_eol()
    cSystemPrompt += "- persona\SOUL.md" + hb_eol()
    endif
    
    hResult := ExecuteReasoningLoop(cQuery, cModel, hb_ValToStr(cHistory), @cLogDoc, cSystemPrompt)
    
    cLogDoc += "<<< [" + Time() + "] RESP SENT" + hb_eol()
    MemoWrit("aios_debug.log", MemoRead("aios_debug.log") + cLogDoc)
    
    UWrite(hb_jsonEncode(hResult))
    return ""
    
    //------------------------------------------------------------------------------
    function ExecuteReasoningLoop(cQuery, cModel, cHistory, cTrace, cSystemPrompt)
        local aFunctions := BuildAiosFunctions()
        local aMessages := {}, nMaxSteps := 5, nStep := 0
        local hResult := {=>}, aStepsArr := {}, hMsg, hGeminiResult, hSkillResult
        local nErr
        
    
    if !Empty(cHistory)
    nErr := hb_jsonDecode(cHistory, @aMessages)
    // In HIX, hb_jsonDecode returns the POS where it stopped. 
    // If it reaches the end, it's successful.
    if ValType(aMessages) != "A" .or. Len(aMessages) == 0
    if nErr == 0 .or. nErr < Len(AllTrim(cHistory))
    cTrace += "!! History Decode Failed. Pos: " + hb_ValToStr(nErr) + hb_eol()
    aMessages := {}
    endif
    endif
    endif
    
    if ValType(aMessages) != "A" ; aMessages := {} ; endif
    cTrace += "HIST_TURNS: " + hb_ValToStr(Len(aMessages)) + hb_eol()
    
    aAdd(aMessages, { "role" => "user", "parts" => { { "text" => cQuery } } })
    
    do while nStep < nMaxSteps
    nStep++
    hGeminiResult := GeminiCallFC(aMessages, aFunctions, cModel, @cTrace, cSystemPrompt)
        
    if !hGeminiResult['success']
    hResult['success'] := .f. ; hResult['error'] := hGeminiResult['error'] ; retu hResult
    endif
        
    hMsg := { "role" => "model", "parts" => hGeminiResult["raw_parts"] }
    aAdd(aMessages, hMsg)
        
    if hGeminiResult['type'] == 'function_call'
    aAdd(aStepsArr, { "type" => "call", "name" => hGeminiResult['function_name'], "args" => hGeminiResult['arguments'] })
    hSkillResult := ExecuteAiosSkill(hGeminiResult['function_name'], hGeminiResult['arguments'])
            
    aAdd(aMessages, { "role" => "function", "parts" => { { "functionResponse" => { "name" => hGeminiResult['function_name'], "response" => { "content" => hb_jsonEncode(hSkillResult) } } } } })
    aAdd(aStepsArr, { "type" => "result", "success" => hSkillResult['success'], "result" => hSkillResult })
    else
    hResult['success'] := .t. ; hResult['text'] := hGeminiResult['text'] ; hResult['steps'] := aStepsArr ; hResult['model_used'] := cModel
    retu hResult
    endif
    enddo
    
    hResult['success'] := .f. ; hResult['error'] := "Max steps"
    retu hResult
    
    //------------------------------------------------------------------------------
    function GeminiCallFC(aMessages, aFunctions, cModel, cTrace, cSystemPrompt)
        local cApiKey := GetAiosApiKey(), cUrl, cJson, hCurl, nError, cResponse := "", hResult := {=>}
        local hPayload := { "contents" => aMessages, "tools" => { { "function_declarations" => aFunctions } } }
        
    
    if !Empty(cSystemPrompt)
    hPayload["system_instruction"] := { "parts" => { { "text" => cSystemPrompt } } }
    endif
    
    cUrl := "https://generativelanguage.googleapis.com/v1beta/models/" + cModel + ":generateContent?key=" + cApiKey
    cJson := hb_jsonEncode(hPayload)
    
    hCurl := curl_easy_init()
    if !Empty(hCurl)
    curl_easy_setopt(hCurl, HB_CURLOPT_POST, .T.)
    curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl)
    curl_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" })
    curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.)
    curl_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cJson)
    curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP)
        
    nError := curl_easy_perform(hCurl)
    if nError == HB_CURLE_OK
    cResponse := curl_easy_dl_buff_get(hCurl)
    hResult := ParseAiosFCResponse(cResponse, @cTrace)
    else
    hResult['success'] := .f. ; hResult['error'] := "Curl error: " + hb_ValToStr(nError)
    cTrace += "CURL_ERR: " + hb_ValToStr(nError) + hb_eol()
    endif
    curl_easy_cleanup(hCurl)
    else ; hResult['success'] := .f. ; hResult['error'] := "Curl init failed" ; endif
    retu hResult
    
    //------------------------------------------------------------------------------
    function ParseAiosFCResponse(cJSON, cTrace)
        local hResult := { "success" => .f. }, hResponse := {=>}, aParts, nErr := 0
        local nStart, nEnd, cWorking
        
    
    cJSON := StrTran(hb_ValToStr(cJSON), Chr(0), "")
    cWorking := AllTrim(cJSON)
    
    nStart := At("{", cWorking)
    nEnd   := RAt("}", cWorking)
    if nStart > 0 .and. nEnd > nStart
    cWorking := SubStr(cWorking, nStart, nEnd - nStart + 1)
    endif
    
    nErr := hb_jsonDecode(cWorking, @hResponse)
    
    do while nErr > 0 .and. Len(cWorking) > 10
    cWorking := Left(cWorking, Len(cWorking) - 1)
    if Right(cWorking, 1) $ '}"0123456789'
    nErr := hb_jsonDecode(cWorking, @hResponse)
    endif
    enddo
    
    if (nErr == 0 .or. nErr == Len(cWorking)) .and. hb_HHasKey(hResponse, "candidates") .and. Len(hResponse["candidates"]) > 0
    aParts := hResponse["candidates"][1]["content"]["parts"]
    hResult['success'] := .t. ; hResult['raw_parts'] := aParts
        
    if hb_HHasKey(aParts[1], "functionCall")
    hResult['type'] := 'function_call'
    hResult['function_name'] := aParts[1]["functionCall"]["name"]
    hResult['arguments'] := aParts[1]["functionCall"]["args"]
    else
    hResult['type'] := 'text'
    hResult['text'] := hb_HGetDef(aParts[1], "text", "")
    endif
    else
    hResult['error'] := "API JSON malformed (" + hb_ValToStr(nErr) + ")"
    cTrace += "!! Decoder failed on: " + Left(cWorking, 100) + hb_eol()
    endif
    retu hResult
    
    //------------------------------------------------------------------------------
    function BuildAiosFunctions()
        local aFuncs := {}
        aAdd(aFuncs, { "name" => "filesystem_search", "description" => "Search files. pattern, path.", "parameters" => { "type" => "object", "properties" => { "pattern" => { "type" => "string" }, "path" => { "type" => "string" } }, "required" => {"pattern", "path"} } })
        aAdd(aFuncs, { "name" => "filesystem_read", "description" => "Read file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" } }, "required" => {"file"} } })
        aAdd(aFuncs, { "name" => "filesystem_write", "description" => "Write file content.", "parameters" => { "type" => "object", "properties" => { "file" => { "type" => "string" }, "content" => { "type" => "string" } }, "required" => {"file", "content"} } })
        aAdd(aFuncs, { "name" => "shell_execute", "description" => "Execute shell command.", "parameters" => { "type" => "object", "properties" => { "command" => { "type" => "string" } }, "required" => {"command"} } })
        aAdd(aFuncs, { "name" => "identity_get_context", "description" => "Returns the identity and user soul information. Use this to know who you are and who the user is.", "parameters" => { "type" => "object", "properties" => {=>}, "required" => {} } })
    retu aFuncs
    
    //------------------------------------------------------------------------------
    function ExecuteAiosSkill(cName, hArgs)
        local hRes := { "success" => .f., "error" => "" }
        do case
        case cName == 'filesystem_search' .or. cName == 'filesystem_read' .or. cName == 'filesystem_write'
        hRes := Aios_FileSystem(cName, hArgs)
        case cName == 'shell_execute'
        hRes := Aios_Shell(hArgs)
        case cName == 'identity_get_context'
        hRes := Aios_Identity()
        endcase
    retu hRes
    
    //------------------------------------------------------------------------------
    function Aios_Identity()
        local hR := { "success" => .t. }, cId := "", cSoul := ""
        if File("persona\IDENTITY.md") ; cId := MemoRead("persona\IDENTITY.md") ; endif
        if File("persona\SOUL.md")     ; cSoul := MemoRead("persona\SOUL.md")     ; endif
        hR['identity'] := cId
        hR['soul'] := cSoul
        if Empty(cId) .and. Empty(cSoul) ; hR['success'] := .f. ; hR['error'] := "Identity files not found" ; endif
    retu hR
    
    //------------------------------------------------------------------------------
    function Aios_FileSystem(cAct, hP)
        local hR := { "success" => .t. }, cPat, cPath, cFile, aRes := {}
        if cAct == 'filesystem_search'
        cPat := hb_HGetDef(hP, 'pattern', '*.*') ; cPath := hb_HGetDef(hP, 'path', 'c:\hix')
        for each cFile in Directory(cPath + '\' + cPat)
        if !(cFile[1] $ '.,') ; aAdd(aRes, { "name" => cFile[1], "size" => cFile[2], "date" => DToC(cFile[3]), "time" => cFile[4] }) ; endif
        next
        hR['files'] := aRes ; hR['count'] := Len(aRes)
        elseif cAct == 'filesystem_read'
        cFile := hb_HGetDef(hP, 'file', '')
        if File(cFile) ; hR['content'] := Left(MemoRead(cFile), 5000) ; else ; hR['success'] := .f. ; hR['error'] := "File not found" ; endif
        elseif cAct == 'filesystem_write'
        if MemoWrit(hb_HGetDef(hP, 'file', ''), hb_HGetDef(hP, 'content', '')) ; hR['msg'] := "Stored"
        else ; hR['success'] := .f. ; hR['error'] := "Writ error" ; endif
        endif
    retu hR
    
    //------------------------------------------------------------------------------
    function Aios_Shell(hP)
        local hR := { "success" => .t. }, cCmd := hb_HGetDef(hP, 'command', '')
        if Empty(cCmd) ; hR['success'] := .f. ; return hR ; endif
        hb_Run(cCmd + " > shell_out.tmp 2>&1")
        hR['output'] := MemoRead("shell_out.tmp") ; FErase("shell_out.tmp")
    retu hR
    
    //------------------------------------------------------------------------------
    function GetAiosApiKey()
        local cKey := GetEnv("GEMINI_API_KEY")
        if Empty(cKey) .and. File("gemini_config.json")
        cKey := hb_HGetDef(hb_jsonDecode(MemoRead("gemini_config.json")), "api_key", "")
        endif
    retu cKey
    
    //------------------------------------------------------------------------------
    function Hix_UrlDecode( cT )
        local cR := "", n := 1, cX
        if Empty(cT) ; return "" ; endif
        cT := StrTran( cT, "+", " " )
        do while n <= Len( cT )
        if SubStr( cT, n, 1 ) == "%"
        cX := SubStr( cT, n + 1, 2 ) ; cR += Chr( Hix_HexToNum( cX ) ) ; n += 3
        else ; cR += SubStr( cT, n, 1 ) ; n++ ; endif
        enddo
    retu cR
    
    //------------------------------------------------------------------------------
    function Hix_HexToNum( cX )
        local n := 0, i, cC, nV
        cX := Upper( cX )
        for i := 1 to Len( cX )
        cC := SubStr( cX, i, 1 ) ; nV := At( cC, "0123456789ABCDEF" ) - 1
        if nV >= 0 ; n := n * 16 + nV ; endif
        next
    retu n
    regards, saludos

    Antonio Linares
    www.fivetechsoft.com
    Posts: 44158
    Joined: Thu Oct 06, 2005 05:47 PM
    Re: OpenClaw concepts on HIX
    Posted: Wed Feb 18, 2026 10:18 PM

    Telegram Skill for HIX:

    telegram.prg

    // telegram.prg - Telegram Bot API Integration for HIX AIOS
    #include "hbcurl.ch"
    
    function Aios_Telegram(cAct, hP)
        local hR := { "success" => .t. }
        
    
    do case
    case cAct == 'telegram_send_message'
    hR := Telegram_SendMessage(hb_HGetDef(hP, "chat_id", ""), hb_HGetDef(hP, "text", ""))
    case cAct == 'telegram_get_updates'
    hR := Telegram_GetUpdates()
    otherwise
    hR := { "success" => .f., "error" => "Telegram action not supported: " + cAct }
    endcase
    retu hR
    
    //------------------------------------------------------------------------------
    function Telegram_SendMessage(cChatId, cText)
        local hR := { "success" => .f. }, cToken := GetTelegramToken()
        local cUrl, cPayload, hCurl, nError, cResponse := ""
        
    
    if Empty(cToken) ; hR['error'] := "Telegram Token not found" ; retu hR ; endif
    if Empty(cChatId) ; hR['error'] := "Chat ID required" ; retu hR ; endif
    
    cUrl := "https://api.telegram.org/bot" + cToken + "/sendMessage"
    cPayload := hb_jsonEncode({ "chat_id" => cChatId, "text" => cText })
    
    hCurl := curl_easy_init()
    if !Empty(hCurl)
    curl_easy_setopt(hCurl, HB_CURLOPT_POST, .T.)
    curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl)
    curl_easy_setopt(hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" })
    curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.)
    curl_easy_setopt(hCurl, HB_CURLOPT_POSTFIELDS, cPayload)
    curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP)
        
    nError := curl_easy_perform(hCurl)
    if nError == HB_CURLE_OK
    cResponse := curl_easy_dl_buff_get(hCurl)
    hR['success'] := .t.
    hR['raw_response'] := cResponse
    else
    hR['error'] := "Curl error: " + hb_ValToStr(nError)
    endif
    curl_easy_cleanup(hCurl)
    else
    hR['error'] := "Curl init failed"
    endif
    retu hR
    
    //------------------------------------------------------------------------------
    function Telegram_GetUpdates()
        local hR := { "success" => .f. }, cToken := GetTelegramToken()
        local cUrl, hCurl, nError, cResponse := ""
        
    
    if Empty(cToken) ; hR['error'] := "Telegram Token not found" ; retu hR ; endif
    
    cUrl := "https://api.telegram.org/bot" + cToken + "/getUpdates"
    
    hCurl := curl_easy_init()
    if !Empty(hCurl)
    curl_easy_setopt(hCurl, HB_CURLOPT_URL, cUrl)
    curl_easy_setopt(hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F.)
    curl_easy_setopt(hCurl, HB_CURLOPT_DL_BUFF_SETUP)
        
    nError := curl_easy_perform(hCurl)
    if nError == HB_CURLE_OK
    cResponse := curl_easy_dl_buff_get(hCurl)
    hR['success'] := .t.
    hR['updates'] := cResponse
    else
    hR['error'] := "Curl error: " + hb_ValToStr(nError)
    endif
    curl_easy_cleanup(hCurl)
    else
    hR['error'] := "Curl init failed"
    endif
    retu hR
    
    //------------------------------------------------------------------------------
    function GetTelegramToken()
        local cKey := GetEnv("TELEGRAM_BOT_TOKEN")
        local hConfig
        if Empty(cKey) .and. File("telegram_config.json")
        hConfig := {=>}
        hb_jsonDecode(MemoRead("telegram_config.json"), @hConfig)
        cKey := hb_HGetDef(hConfig, "bot_token", "")
        endif
    retu cKey
    regards, saludos

    Antonio Linares
    www.fivetechsoft.com

    Continue the discussion