From 9069816db1aaf50391048d022626ea6df4080168 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Mon, 18 Aug 2025 09:51:16 +0300 Subject: [PATCH] feat: implement full heroprompt workspace management - Add create, save, get, list, and delete for workspaces - Enable adding and removing files/dirs by path or name - Integrate codewalker for recursive file discovery - Make workspaces stateful with created/updated timestamps - Update example to demonstrate new lifecycle methods --- debug.logs | 1522 +++++++++++++++++ .../develop/heroprompt/heroprompt_example.vsh | 56 +- .../heroprompt_heroscript_example.vsh | 15 - lib/develop/heroprompt/heroprompt_factory_.v | 27 +- lib/develop/heroprompt/heroprompt_model.v | 14 +- lib/develop/heroprompt/heroprompt_workspace.v | 163 +- 6 files changed, 1713 insertions(+), 84 deletions(-) create mode 100644 debug.logs delete mode 100644 examples/develop/heroprompt/heroprompt_heroscript_example.vsh diff --git a/debug.logs b/debug.logs new file mode 100644 index 00000000..d16a3e1d --- /dev/null +++ b/debug.logs @@ -0,0 +1,1522 @@ +───────────────────────────────(mahmoud@Mahmouds-Laptop:s142)─┐ +└─(08:55:05 on development_codewalker ✖ ✹)──> /Users/mahmoud/code/github/freeflowuniverse/herolib/examples/develop/heroprompt/heroprompt_example.vsh + +Using the selected files, i want you to get all print statments + + + +/Users/mahmoud/code/github/freeflowuniverse/herolib +# Selected Files: 21 | Total Content: 40967 chars | Extensions: md(2), yml(2), sh(14), dockerfile(1), gitignore(1), vsh(1) + +└── docker + ├── docker_ubuntu_install.sh * + ├── herolib + │ └── herolib + │ ├── .gitignore * + │ ├── Dockerfile * + │ ├── README.md * + │ ├── build.sh * + │ ├── debug.sh * + │ ├── docker-compose.yml * + │ ├── export.sh * + │ ├── shell.sh * + │ ├── ssh.sh * + │ ├── ssh_init.sh * + │ ├── start.sh * + │ └── scripts + │ └── scripts + │ ├── cleanup.sh * + │ ├── install_herolib.vsh * + │ ├── install_v.sh * + │ ├── install_vscode.sh * + │ └── ourinit.sh * + └── postgresql + └── postgresql + ├── docker-compose.yml * + ├── readme.md * + └── start.sh * +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh * + + + + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh +```sh +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Function to display an error message and exit +error_exit() { + echo "Error: $1" >&2 + exit 1 +} + +# Update package index and upgrade system +echo "Updating system packages..." +sudo apt update && sudo apt upgrade -y || error_exit "Failed to update system packages." + +# Install required packages for repository setup +echo "Installing prerequisites..." +sudo apt install -y ca-certificates curl gnupg || error_exit "Failed to install prerequisites." + +# Create directory for Docker GPG key +echo "Setting up GPG keyring..." +sudo mkdir -p /etc/apt/keyrings || error_exit "Failed to create keyring directory." + +# Add Docker's official GPG key +DOCKER_GPG_KEY=/etc/apt/keyrings/docker.gpg +echo "Adding Docker GPG key..." +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o $DOCKER_GPG_KEY || error_exit "Failed to add Docker GPG key." +sudo chmod a+r $DOCKER_GPG_KEY + +# Set up Docker repository +echo "Adding Docker repository..." +REPO_ENTRY="deb [arch=$(dpkg --print-architecture) signed-by=$DOCKER_GPG_KEY] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" +if ! grep -Fxq "$REPO_ENTRY" /etc/apt/sources.list.d/docker.list; then + echo "$REPO_ENTRY" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null || error_exit "Failed to add Docker repository." +fi + +# Update package index +echo "Updating package index..." +sudo apt update || error_exit "Failed to update package index." + +# Install Docker Engine, CLI, and dependencies +echo "Installing Docker Engine and dependencies..." +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin || error_exit "Failed to install Docker packages." + +# Verify Docker installation +echo "Verifying Docker installation..." +if ! docker --version; then + error_exit "Docker installation verification failed." +fi + +# Run a test container +echo "Running Docker test container..." +if ! sudo docker run --rm hello-world; then + error_exit "Docker test container failed to run." +fi + +# Add current user to Docker group (if not already added) +echo "Configuring Docker group..." +if ! groups $USER | grep -q '\bdocker\b'; then + sudo usermod -aG docker $USER || error_exit "Failed to add user to Docker group." + echo "User added to Docker group. Please log out and back in for this change to take effect." +else + echo "User is already in the Docker group." +fi + +# Enable Docker service on boot +echo "Enabling Docker service on boot..." +sudo systemctl enable docker || error_exit "Failed to enable Docker service." + +# Success message +echo "Docker installation completed successfully!" + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh +```sh +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Function to display an error message and exit +error_exit() { + echo "Error: $1" >&2 + exit 1 +} + +# Update package index and upgrade system +echo "Updating system packages..." +sudo apt update && sudo apt upgrade -y || error_exit "Failed to update system packages." + +# Install required packages for repository setup +echo "Installing prerequisites..." +sudo apt install -y ca-certificates curl gnupg || error_exit "Failed to install prerequisites." + +# Create directory for Docker GPG key +echo "Setting up GPG keyring..." +sudo mkdir -p /etc/apt/keyrings || error_exit "Failed to create keyring directory." + +# Add Docker's official GPG key +DOCKER_GPG_KEY=/etc/apt/keyrings/docker.gpg +echo "Adding Docker GPG key..." +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o $DOCKER_GPG_KEY || error_exit "Failed to add Docker GPG key." +sudo chmod a+r $DOCKER_GPG_KEY + +# Set up Docker repository +echo "Adding Docker repository..." +REPO_ENTRY="deb [arch=$(dpkg --print-architecture) signed-by=$DOCKER_GPG_KEY] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" +if ! grep -Fxq "$REPO_ENTRY" /etc/apt/sources.list.d/docker.list; then + echo "$REPO_ENTRY" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null || error_exit "Failed to add Docker repository." +fi + +# Update package index +echo "Updating package index..." +sudo apt update || error_exit "Failed to update package index." + +# Install Docker Engine, CLI, and dependencies +echo "Installing Docker Engine and dependencies..." +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin || error_exit "Failed to install Docker packages." + +# Verify Docker installation +echo "Verifying Docker installation..." +if ! docker --version; then + error_exit "Docker installation verification failed." +fi + +# Run a test container +echo "Running Docker test container..." +if ! sudo docker run --rm hello-world; then + error_exit "Docker test container failed to run." +fi + +# Add current user to Docker group (if not already added) +echo "Configuring Docker group..." +if ! groups $USER | grep -q '\bdocker\b'; then + sudo usermod -aG docker $USER || error_exit "Failed to add user to Docker group." + echo "User added to Docker group. Please log out and back in for this change to take effect." +else + echo "User is already in the Docker group." +fi + +# Enable Docker service on boot +echo "Enabling Docker service on boot..." +sudo systemctl enable docker || error_exit "Failed to enable Docker service." + +# Success message +echo "Docker installation completed successfully!" + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/Dockerfile +```dockerfile +# Use Ubuntu 24.04 as the base image +FROM ubuntu:24.04 + +# Set the working directory +WORKDIR /root + +# Copy local installation scripts into the container +COPY scripts/install_v.sh /tmp/install_v.sh +COPY scripts/install_herolib.vsh /tmp/install_herolib.vsh +COPY scripts/install_vscode.sh /tmp/install_vscode.sh +COPY scripts/ourinit.sh /usr/local/bin/ + +# Make the scripts executable +RUN chmod +x /tmp/install_v.sh /tmp/install_herolib.vsh + +RUN apt-get update && apt-get install -y \ + curl bash sudo mc wget tmux htop openssh-server + +RUN bash /tmp/install_v.sh + +RUN yes y | bash /tmp/install_v.sh --analyzer + +RUN bash /tmp/install_vscode.sh + +#SSH +RUN mkdir -p /var/run/sshd && \ + echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config && \ + echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config && \ + chown -R root:root /root/.ssh && \ + chmod -R 700 /root/.ssh/ && \ + touch /root/.ssh/authorized_keys \ + chmod 600 /root/.ssh/authorized_keys && \ + service ssh start + +RUN /tmp/install_herolib.vsh && \ + apt-get clean && \ + echo "PS1='HERO: \w \$ '" >> ~/.bashrc \ + rm -rf /var/lib/apt/lists/* + + +ENTRYPOINT ["/bin/bash"] +CMD ["/bin/bash"] + + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/README.md +```md +# HeroLib Docker Environment + +This directory contains the Docker configuration and scripts for setting up and managing the HeroLib development environment. The environment includes a containerized setup with VSCode server, SSH access, and PostgreSQL database. + +## Key Components + +### Docker Configuration Files + +- `Dockerfile`: Defines the container image based on Ubuntu 24.04, installing necessary dependencies including: + - V language and its analyzer + - VSCode server + - SSH server + - Development tools (curl, tmux, htop, etc.) + - HeroLib installation + +- `docker-compose.yml`: Orchestrates the multi-container setup with: + - PostgreSQL database service + - HeroLib development environment + - Port mappings for various services + - Volume mounting for code persistence + +## Scripts + +### Container Management + +- `shell.sh`: Interactive shell access script that: + - Checks if the container is running + - Starts the container if it's stopped + - Verifies port accessibility (default: 4000) + - Provides interactive bash session inside the container + +- `debug.sh`: Launches the container in debug mode with: + - Interactive terminal + - Volume mounts for scripts and code + - Port mappings for various services (4000-4379) + - Custom entrypoint using ourinit.sh + +- `export.sh`: Creates a compressed export of the container: + - Stops any running instances + - Launches a temporary container + - Runs cleanup script + - Exports and compresses the container to ~/Downloads/herolib.tar.gz + +### SSH Access + +- `ssh.sh`: Simple SSH connection script to access the container via port 4022 + +- `ssh_init.sh`: Configures SSH access by: + - Collecting public keys from local ~/.ssh directory + - Setting up authorized_keys in the container + - Installing and configuring SSH server + - Setting appropriate permissions + - Enabling root login with key authentication + +### Internal Scripts (in scripts/) + +- `cleanup.sh`: Comprehensive system cleanup script that: + - Removes unused packages and dependencies + - Cleans APT cache + - Removes old log files + - Clears temporary files and caches + - Performs system maintenance tasks + +- `install_herolib.vsh`: V script for HeroLib installation: + - Sets up necessary symlinks + - Configures V module structure + - Adds useful shell aliases (e.g., vtest) + +- `ourinit.sh`: Container initialization script that: + - Starts Redis server in daemon mode + - Launches VSCode server in a tmux session + - Starts SSH service + - Provides interactive bash shell + +## Port Mappings + +- 4000:3000 - Main application port +- 4022:22 - SSH access +- 4100:8100 - Additional service port +- 4101:8101 - Additional service port +- 4102:8102 - Additional service port +- 4379:6379 - Redis port + +## Usage + +1. Build and start the environment: + ```bash + docker compose up -d + ``` + +2. Access the container shell: + ```bash + ./shell.sh + ``` + +3. Connect via SSH: + ```bash + ./ssh.sh + ``` + +4. Debug mode (interactive with direct terminal): + ```bash + ./debug.sh + ``` + +5. Create container export: + ```bash + ./export.sh + ``` + +## Development + +The environment mounts your local `~/code` directory to `/root/code` inside the container, allowing for seamless development between host and container. The PostgreSQL database persists data using a named volume. + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/build.sh +```sh +#!/bin/bash -e + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +# Copy installation files +cp ../../install_v.sh ./scripts/install_v.sh +cp ../../install_herolib.vsh ./scripts/install_herolib.vsh + +# Docker image and container names +DOCKER_IMAGE_NAME="herolib" +DEBUG_CONTAINER_NAME="herolib" + +function cleanup { + if docker ps -aq -f name="$DEBUG_CONTAINER_NAME" &>/dev/null; then + echo "Cleaning up leftover debug container..." + docker rm -f "$DEBUG_CONTAINER_NAME" &>/dev/null || true + fi +} +trap cleanup EXIT + +# Attempt to build the Docker image +BUILD_LOG=$(mktemp) +set +e +docker build --name herolib --progress=plain -t "$DOCKER_IMAGE_NAME" . +BUILD_EXIT_CODE=$? +set -e + +# Handle build failure +if [ $BUILD_EXIT_CODE -ne 0 ]; then + echo -e "\\n[ERROR] Docker build failed.\n" + echo -e "remove the part which didn't build in the Dockerfile, the run again and to debug do:" + echo docker run --name herolib -it --entrypoint=/bin/bash "herolib" + exit $BUILD_EXIT_CODE +else + echo -e "\\n[INFO] Docker build completed successfully." +fi + + + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/debug.sh +```sh +#!/bin/bash -ex + +# Get the directory where the script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Remove any existing container named 'debug' (ignore errors) +docker rm -f herolib > /dev/null 2>&1 + +docker run --name herolib -it \ + --entrypoint="/usr/local/bin/ourinit.sh" \ + -v "${SCRIPT_DIR}/scripts:/scripts" \ + -v "$HOME/code:/root/code" \ + -p 4100:8100 \ + -p 4101:8101 \ + -p 4102:8102 \ + -p 4379:6379 \ + -p 4022:22 \ + -p 4000:3000 herolib + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/docker-compose.yml +```yml +services: + postgres: + image: postgres:latest + container_name: postgres_service + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: planetfirst + POSTGRES_DB: mydb + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + herolib: + build: + context: . + dockerfile: Dockerfile + image: herolib:latest + container_name: herolib + volumes: + - ~/code:/root/code + stdin_open: true + tty: true + ports: + - "4100:8100" + - "4101:8101" + - "4102:8102" + - "4379:6379" + - "4000:3000" + - "4022:22" + command: ["/usr/local/bin/ourinit.sh"] +volumes: + postgres_data: + + + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/export.sh +```sh +#!/bin/bash -ex + +# Get the directory where the script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +docker compose down + +docker rm herolib --force + +# Start the container in detached mode (-d) +docker run --name herolib \ + --entrypoint="/bin/bash" \ + -v "${SCRIPT_DIR}/scripts:/scripts" \ + -p 4022:22 \ + -d herolib -c "while true; do sleep 1; done" + +docker exec -it herolib /scripts/cleanup.sh + + +# Detect the OS +detect_os() { + if [[ "$(uname)" == "Darwin" ]]; then + echo "osx" + elif [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "$ID" == "ubuntu" ]]; then + echo "ubuntu" + fi + else + echo "unknown" + fi +} + +OS=$(detect_os) + +if [[ "$OS" == "osx" ]]; then + echo "Running on macOS..." + docker export herolib | gzip > "${HOME}/Downloads/herolib.tar.gz" + echo "Docker image exported to ${HOME}/Downloads/herolib.tar.gz" +elif [[ "$OS" == "ubuntu" ]]; then + echo "Running on Ubuntu..." + export TEMP_TAR="/tmp/herolib.tar" + + # Export the Docker container to a tar file + docker export herolib > "$TEMP_TAR" + echo "Docker container exported to $TEMP_TAR" + + # Import the tar file back as a single-layer image + docker import "$TEMP_TAR" herolib:single-layer + echo "Docker image imported as single-layer: herolib:single-layer" + + # Log in to Docker Hub and push the image + docker login --username despiegk + docker tag herolib:single-layer despiegk/herolib:single-layer + docker push despiegk/herolib:single-layer + echo "Docker image pushed to Docker Hub as despiegk/herolib:single-layer" + + # Optionally remove the tar file after importing + rm -f "$TEMP_TAR" + echo "Temporary file $TEMP_TAR removed" + +else + echo "Unsupported OS detected. Exiting." + exit 1 +fi + +docker kill herolib + + +# Test the pushed Docker image locally +echo "Testing the Docker image locally..." +TEST_CONTAINER_NAME="test_herolib_container" + +docker pull despiegk/herolib:single-layer +if [[ $? -ne 0 ]]; then + echo "Failed to pull the Docker image from Docker Hub. Exiting." + exit 1 +fi + +docker run --name "$TEST_CONTAINER_NAME" -d despiegk/herolib:single-layer +if [[ $? -ne 0 ]]; then + echo "Failed to run the Docker image as a container. Exiting." + exit 1 +fi + +docker ps | grep "$TEST_CONTAINER_NAME" +if [[ $? -eq 0 ]]; then + echo "Container $TEST_CONTAINER_NAME is running successfully." +else + echo "Container $TEST_CONTAINER_NAME is not running. Check the logs for details." +fi +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/scripts/cleanup.sh +```sh +#!/bin/bash -e + +# Log file for cleanup operations +LOG_FILE="/var/log/cleanup_script.log" +exec > >(tee -a $LOG_FILE) 2>&1 + +# Function to check and execute commands safely +safe_run() { + echo "Running: $*" + eval "$*" +} + +# Update package lists +safe_run "apt update" + +# Remove unused packages and dependencies +safe_run "apt autoremove -y" + +# Clean up APT cache +safe_run "apt clean" +safe_run "apt autoclean" + +# Remove old kernels (keeping the current and latest one) +safe_run "apt remove --purge -y $(dpkg --list | grep linux-image | awk '{print $2}' | grep -v $(uname -r | sed 's/[^-]*-[^-]*-//') | sort | head -n -1)" + +# Clear systemd journal logs, keeping only the latest 7 days +safe_run "journalctl --vacuum-time=7d" + +# Remove orphaned packages +safe_run "deborphan | xargs apt-get -y remove --purge" + +# Clear thumbnail cache +safe_run "rm -rf ~/.cache/thumbnails/*" + +# Remove old log files +safe_run "find /var/log -type f -name '*.log' -delete" + +# Clear temporary files +safe_run "rm -rf /tmp/*" +safe_run "rm -rf /var/tmp/*" + +# Remove user-specific temporary files (adjust for other users as needed) +safe_run "rm -rf ~/.cache/*" + +# Remove .pyc files +safe_run "find / -type f -name '*.pyc' -delete" + +# Remove unused snap versions +#safe_run "snap list --all | awk '/disabled/{print $1, $3}' | while read snapname revision; do snap remove "$snapname" --revision="$revision"; done" + +# Clear trash for all users +safe_run "rm -rf /home/*/.local/share/Trash/*/**" +safe_run "rm -rf /root/.local/share/Trash/*/**" + +# Free up swap space +#safe_run "swapoff -a && swapon -a" + +# Update GRUB (in case old kernels were removed) +#safe_run "update-grub" + +# # Final system update and upgrade +# safe_run "apt upgrade -y" + +# Report completion +echo "System cleanup completed successfully." + + + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/scripts/install_herolib.vsh +```vsh +#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run + +import os +import flag + +fn addtoscript(tofind string, toadd string) ! { + home_dir := os.home_dir() + mut rc_file := '${home_dir}/.zshrc' + if !os.exists(rc_file) { + rc_file = '${home_dir}/.bashrc' + if !os.exists(rc_file) { + return error('No .zshrc or .bashrc found in home directory') + } + } + + // Read current content + mut content := os.read_file(rc_file)! + + // Remove existing alias if present + lines := content.split('\n') + mut new_lines := []string{} + mut prev_is_emtpy := false + for line in lines { + if prev_is_emtpy { + if line.trim_space() == '' { + continue + } else { + prev_is_emtpy = false + } + } + if line.trim_space() == '' { + prev_is_emtpy = true + } + + if !line.contains(tofind) { + new_lines << line + } + } + new_lines << toadd + new_lines << '' + // Write back to file + new_content := new_lines.join('\n') + os.write_file(rc_file, new_content)! +} + +vroot := @VROOT +abs_dir_of_script := dir(@FILE) + +// Reset symlinks if requested +println('Resetting all symlinks...') +os.rm('${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {} + +// Create necessary directories +os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or { + panic('Failed to create directory ~/.vmodules/freeflowuniverse: ${err}') +} + +// Create new symlinks +os.symlink('${abs_dir_of_script}/lib', '${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or { + panic('Failed to create herolib symlink: ${err}') +} + +println('Herolib installation completed successfully!') + +// Add vtest alias +addtoscript('alias vtest=', "alias vtest='v -stats -enable-globals -n -w -cg -gc none -cc tcc test' ") or { + eprintln('Failed to add vtest alias: ${err}') +} + +println('Added vtest alias to shell configuration') + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/scripts/install_v.sh +```sh + +#!/bin/bash -e + +# Help function +print_help() { + echo "V & HeroLib Installer Script" + echo + echo "Usage: $0 [options]" + echo + echo "Options:" + echo " -h, --help Show this help message" + echo " --reset Force reinstallation of V" + echo " --remove Remove V installation and exit" + echo " --analyzer Install/update v-analyzer" + echo " --herolib Install our herolib" + echo + echo "Examples:" + echo " $0" + echo " $0 --reset " + echo " $0 --remove " + echo " $0 --analyzer " + echo " $0 --herolib " + echo " $0 --reset --analyzer # Fresh install of both" + echo +} + +# Parse arguments +RESET=false +REMOVE=false +INSTALL_ANALYZER=false +HEROLIB=false + +for arg in "$@"; do + case $arg in + -h|--help) + print_help + exit 0 + ;; + --reset) + RESET=true + ;; + --remove) + REMOVE=true + ;; + --herolib) + HEROLIB=true + ;; + --analyzer) + INSTALL_ANALYZER=true + ;; + *) + echo "Unknown option: $arg" + echo "Use -h or --help to see available options" + exit 1 + ;; + esac +done + +# Function to check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +export DIR_BASE="$HOME" +export DIR_BUILD="/tmp" +export DIR_CODE="$DIR_BASE/code" +export DIR_CODE_V="$DIR_BASE/_code" + +function sshknownkeysadd { + mkdir -p ~/.ssh + touch ~/.ssh/known_hosts + if ! grep github.com ~/.ssh/known_hosts > /dev/null + then + ssh-keyscan github.com >> ~/.ssh/known_hosts + fi + if ! grep git.threefold.info ~/.ssh/known_hosts > /dev/null + then + ssh-keyscan git.threefold.info >> ~/.ssh/known_hosts + fi + git config --global pull.rebase false + +} + +function package_check_install { + local command_name="$1" + if command -v "$command_name" >/dev/null 2>&1; then + echo "command '$command_name' is already installed." + else + package_install '$command_name' + fi +} + +function package_install { + local command_name="$1" + if [[ "${OSNAME}" == "ubuntu" ]]; then + apt -o Dpkg::Options::="--force-confold" -o Dpkg::Options::="--force-confdef" install $1 -q -y --allow-downgrades --allow-remove-essential + elif [[ "${OSNAME}" == "darwin"* ]]; then + brew install $command_name + elif [[ "${OSNAME}" == "alpine"* ]]; then + apk add $command_name + elif [[ "${OSNAME}" == "arch"* ]]; then + pacman --noconfirm -Su $command_name + else + echo "platform : ${OSNAME} not supported" + exit 1 + fi +} + +is_github_actions() { + [ -d "/home/runner" ] || [ -d "$HOME/runner" ] +} + + +function myplatform { + if [[ "${OSTYPE}" == "darwin"* ]]; then + export OSNAME='darwin' + elif [ -e /etc/os-release ]; then + # Read the ID field from the /etc/os-release file + export OSNAME=$(grep '^ID=' /etc/os-release | cut -d= -f2) + if [ "${os_id,,}" == "ubuntu" ]; then + export OSNAME="ubuntu" + fi + if [ "${OSNAME}" == "archarm" ]; then + export OSNAME="arch" + fi + if [ "${OSNAME}" == "debian" ]; then + export OSNAME="ubuntu" + fi + else + echo "Unable to determine the operating system." + exit 1 + fi + + + # if [ "$(uname -m)" == "x86_64" ]; then + # echo "This system is running a 64-bit processor." + # else + # echo "This system is not running a 64-bit processor." + # exit 1 + # fi + +} + +myplatform + +function os_update { + echo ' - os update' + if [[ "${OSNAME}" == "ubuntu" ]]; then + if is_github_actions; then + echo "github actions" + else + rm -f /var/lib/apt/lists/lock + rm -f /var/cache/apt/archives/lock + rm -f /var/lib/dpkg/lock* + fi + export TERM=xterm + export DEBIAN_FRONTEND=noninteractive + dpkg --configure -a + apt update -y + if is_github_actions; then + echo "** IN GITHUB ACTIONS, DON'T DO UPDATE" + else + set +e + echo "** UPDATE" + apt-mark hold grub-efi-amd64-signed + set -e + apt upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes + apt autoremove -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes + fi + #apt install apt-transport-https ca-certificates curl software-properties-common -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes + package_install "apt-transport-https ca-certificates curl wget software-properties-common tmux" + package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config" + + elif [[ "${OSNAME}" == "darwin"* ]]; then + if command -v brew >/dev/null 2>&1; then + echo ' - homebrew installed' + else + export NONINTERACTIVE=1 + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + unset NONINTERACTIVE + fi + set +e + brew install mc redis curl tmux screen htop wget rclone tcc + set -e + elif [[ "${OSNAME}" == "alpine"* ]]; then + apk update screen git htop tmux + apk add mc curl rsync htop redis bash bash-completion screen git rclone + sed -i 's#/bin/ash#/bin/bash#g' /etc/passwd + elif [[ "${OSNAME}" == "arch"* ]]; then + pacman -Syy --noconfirm + pacman -Syu --noconfirm + pacman -Su --noconfirm arch-install-scripts gcc mc git tmux curl htop redis wget screen net-tools git sudo htop ca-certificates lsb-release screen rclone + + # Check if builduser exists, create if not + if ! id -u builduser > /dev/null 2>&1; then + useradd -m builduser + echo "builduser:$(openssl rand -base64 32 | sha256sum | base64 | head -c 32)" | chpasswd + echo 'builduser ALL=(ALL) NOPASSWD: ALL' | tee /etc/sudoers.d/builduser + fi + + if [[ -n "${DEBUG}" ]]; then + execute_with_marker "paru_install" paru_install + fi + fi + echo ' - os update done' +} + + +function hero_lib_pull { + pushd $DIR_CODE/github/freeflowuniverse/herolib 2>&1 >> /dev/null + if [[ $(git status -s) ]]; then + echo "There are uncommitted changes in the Git repository herolib." + return 1 + fi + git pull + popd 2>&1 >> /dev/null +} + +function hero_lib_get { + + mkdir -p $DIR_CODE/github/freeflowuniverse + if [[ -d "$DIR_CODE/github/freeflowuniverse/herolib" ]] + then + hero_lib_pull + else + pushd $DIR_CODE/github/freeflowuniverse 2>&1 >> /dev/null + git clone --depth 1 --no-single-branch https://github.com/freeflowuniverse/herolib.git + popd 2>&1 >> /dev/null + fi +} + +function install_secp256k1 { + echo "Installing secp256k1..." + if [[ "${OSNAME}" == "darwin"* ]]; then + brew install secp256k1 + elif [[ "${OSNAME}" == "ubuntu" ]]; then + # Install build dependencies + apt-get install -y build-essential wget autoconf libtool + + # Download and extract secp256k1 + cd "${DIR_BUILD}" + wget https://github.com/bitcoin-core/secp256k1/archive/refs/tags/v0.3.2.tar.gz + tar -xvf v0.3.2.tar.gz + + # Build and install + cd secp256k1-0.3.2/ + ./autogen.sh + ./configure + make -j 5 + make install + + # Cleanup + cd .. + rm -rf secp256k1-0.3.2 v0.3.2.tar.gz + else + echo "secp256k1 installation not implemented for ${OSNAME}" + exit 1 + fi + echo "secp256k1 installation complete!" +} + + +remove_all() { + echo "Removing V installation..." + # Set reset to true to use existing reset functionality + RESET=true + # Call reset functionality + sudo rm -rf ~/code/v + sudo rm -rf ~/_code/v + sudo rm -rf ~/.config/v-analyzer + if command_exists v; then + echo "Removing V from system..." + sudo rm -f $(which v) + fi + if command_exists v-analyzer; then + echo "Removing v-analyzer from system..." + sudo rm -f $(which v-analyzer) + fi + + # Remove v-analyzer path from rc files + for RC_FILE in ~/.zshrc ~/.bashrc; do + if [ -f "$RC_FILE" ]; then + echo "Cleaning up $RC_FILE..." + # Create a temporary file + TMP_FILE=$(mktemp) + # Remove lines containing v-analyzer/bin path + sed '/v-analyzer\/bin/d' "$RC_FILE" > "$TMP_FILE" + # Remove empty lines at the end of file + sed -i.bak -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$TMP_FILE" + # Replace original file + mv "$TMP_FILE" "$RC_FILE" + echo "Cleaned up $RC_FILE" + fi + done + + echo "V removal complete" +} + + + +# Function to check if a service is running and start it if needed +check_and_start_redis() { + + # Normal service management for non-container environments + if [[ "${OSNAME}" == "ubuntu" ]] || [[ "${OSNAME}" == "debian" ]]; then + + # Check if running inside a container + if grep -q "/docker/" /proc/1/cgroup || [ ! -d "/run/systemd/system" ]; then + echo "Running inside a container. Starting redis directly." + + if pgrep redis-server > /dev/null; then + echo "redis is already running." + else + echo "redis is not running. Starting it in the background..." + redis-server --daemonize yes + if pgrep redis-server > /dev/null; then + echo "redis started successfully." + else + echo "Failed to start redis. Please check logs for details." + exit 1 + fi + fi + return + fi + + if systemctl is-active --quiet "redis"; then + echo "redis is already running." + else + echo "redis is not running. Starting it..." + sudo systemctl start "redis" + if systemctl is-active --quiet "redis"; then + echo "redis started successfully." + else + echo "Failed to start redis. Please check logs for details." + exit 1 + fi + fi + elif [[ "${OSNAME}" == "darwin"* ]]; then + if brew services list | grep -q "^redis.*started"; then + echo "redis is already running." + else + echo "redis is not running. Starting it..." + brew services start redis + fi + elif [[ "${OSNAME}" == "alpine"* ]]; then + if rc-service "redis" status | grep -q "running"; then + echo "redis is already running." + else + echo "redis is not running. Starting it..." + rc-service "redis" start + fi + elif [[ "${OSNAME}" == "arch"* ]]; then + if systemctl is-active --quiet "redis"; then + echo "redis is already running." + else + echo "redis is not running. Starting it..." + sudo systemctl start "redis" + fi + else + echo "Service management for redis is not implemented for platform: $OSNAME" + exit 1 + fi +} + + + +# Handle remove if requested +if [ "$REMOVE" = true ]; then + remove_all + exit 0 +fi + +# Handle reset if requested +if [ "$RESET" = true ]; then + remove_all + echo "Reset complete" +fi + +# Create code directory if it doesn't exist +mkdir -p ~/code + + +# Check if v needs to be installed +if [ "$RESET" = true ] || ! command_exists v; then + + os_update + + sshknownkeysadd + + # Install secp256k1 + install_secp256k1 + + # Only clone and install if directory doesn't exist + if [ ! -d ~/code/v ]; then + echo "Installing V..." + mkdir -p ~/_code + cd ~/_code + git clone --depth=1 https://github.com/vlang/v + cd v + make + sudo ./v symlink + fi + + # Verify v is in path + if ! command_exists v; then + echo "Error: V installation failed or not in PATH" + echo "Please ensure ~/code/v is in your PATH" + exit 1 + fi + echo "V installation successful!" +fi + +# Install v-analyzer if requested +if [ "$INSTALL_ANALYZER" = true ]; then + echo "Installing v-analyzer..." + v download -RD https://raw.githubusercontent.com/vlang/v-analyzer/main/install.vsh + + # Check if v-analyzer bin directory exists + if [ ! -d "$HOME/.config/v-analyzer/bin" ]; then + echo "Error: v-analyzer bin directory not found at $HOME/.config/v-analyzer/bin" + echo "Please ensure v-analyzer was installed correctly" + exit 1 + fi + + echo "v-analyzer installation successful!" +fi + +# Add v-analyzer to PATH if installed +if [ -d "$HOME/.config/v-analyzer/bin" ]; then + V_ANALYZER_PATH='export PATH="$PATH:$HOME/.config/v-analyzer/bin"' + + # Function to add path to rc file if not present + add_to_rc() { + local RC_FILE="$1" + if [ -f "$RC_FILE" ]; then + if ! grep -q "v-analyzer/bin" "$RC_FILE"; then + echo "" >> "$RC_FILE" + echo "$V_ANALYZER_PATH" >> "$RC_FILE" + echo "Added v-analyzer to $RC_FILE" + else + echo "v-analyzer path already exists in $RC_FILE" + fi + fi + } + + # Add to both .zshrc and .bashrc if they exist + add_to_rc ~/.zshrc + if [ "$(uname)" = "Darwin" ] && [ -f ~/.bashrc ]; then + add_to_rc ~/.bashrc + fi +fi + +# Final verification +if ! command_exists v; then + echo "Error: V is not accessible in PATH" + echo "Please add ~/code/v to your PATH and try again" + exit 1 +fi + +check_and_start_redis + +if [ "$HEROLIB" = true ]; then + hero_lib_get + ~/code/github/freeflowuniverse/herolib/install_herolib.vsh +fi + + +# if [ "$INSTALL_ANALYZER" = true ]; then +# echo "Run 'source ~/.bashrc' or 'source ~/.zshrc' to update PATH for v-analyzer" +# fi + + +echo "Installation complete!" + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/scripts/install_vscode.sh +```sh +#!/bin/bash -e + +# Set version and file variables +OPENVSCODE_SERVER_VERSION="1.97.0" +TMP_DIR="/tmp" +FILENAME="openvscode.tar.gz" +FILE_PATH="$TMP_DIR/$FILENAME" +INSTALL_DIR="/opt/openvscode" +BIN_PATH="/usr/local/bin/openvscode-server" +TMUX_SESSION="openvscode-server" + +# Function to detect architecture +get_architecture() { + ARCH=$(uname -m) + case "$ARCH" in + x86_64) + echo "x64" + ;; + aarch64) + echo "arm64" + ;; + *) + echo "Unsupported architecture: $ARCH" >&2 + exit 1 + ;; + esac +} + +# Check if OpenVSCode Server is already installed +if [ -d "$INSTALL_DIR" ] && [ -x "$BIN_PATH" ]; then + echo "OpenVSCode Server is already installed at $INSTALL_DIR. Skipping download and installation." +else + # Determine architecture-specific URL + ARCH=$(get_architecture) + if [ "$ARCH" == "x64" ]; then + DOWNLOAD_URL="https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-insiders-v${OPENVSCODE_SERVER_VERSION}/openvscode-server-insiders-v${OPENVSCODE_SERVER_VERSION}-linux-x64.tar.gz" + elif [ "$ARCH" == "arm64" ]; then + DOWNLOAD_URL="https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-insiders-v${OPENVSCODE_SERVER_VERSION}/openvscode-server-insiders-v${OPENVSCODE_SERVER_VERSION}-linux-arm64.tar.gz" + fi + + # Navigate to temporary directory + cd "$TMP_DIR" + + # Remove existing file if it exists + if [ -f "$FILE_PATH" ]; then + rm -f "$FILE_PATH" + fi + + # Download file using curl + curl -L "$DOWNLOAD_URL" -o "$FILE_PATH" + + # Verify file size is greater than 40 MB (40 * 1024 * 1024 bytes) + FILE_SIZE=$(stat -c%s "$FILE_PATH") + if [ "$FILE_SIZE" -le $((40 * 1024 * 1024)) ]; then + echo "Error: Downloaded file size is less than 40 MB." >&2 + exit 1 + fi + + # Extract the tar.gz file + EXTRACT_DIR="openvscode-server-insiders-v${OPENVSCODE_SERVER_VERSION}-linux-${ARCH}" + tar -xzf "$FILE_PATH" + + # Move the extracted directory to the install location + if [ -d "$INSTALL_DIR" ]; then + rm -rf "$INSTALL_DIR" + fi + mv "$EXTRACT_DIR" "$INSTALL_DIR" + + # Create a symlink for easy access + ln -sf "$INSTALL_DIR/bin/openvscode-server" "$BIN_PATH" + + # Verify installation + if ! command -v openvscode-server >/dev/null 2>&1; then + echo "Error: Failed to create symlink for openvscode-server." >&2 + exit 1 + fi + + # Install default plugins + PLUGINS=("ms-python.python" "esbenp.prettier-vscode" "saoudrizwan.claude-dev" "yzhang.markdown-all-in-one" "ms-vscode-remote.remote-ssh" "ms-vscode.remote-explorer" "charliermarsh.ruff" "qwtel.sqlite-viewer" "vosca.vscode-v-analyzer" "tomoki1207.pdf") + for PLUGIN in "${PLUGINS[@]}"; do + "$INSTALL_DIR/bin/openvscode-server" --install-extension "$PLUGIN" + done + + echo "Default plugins installed: ${PLUGINS[*]}" + + # Clean up temporary directory + if [ -d "$TMP_DIR" ]; then + find "$TMP_DIR" -maxdepth 1 -type f -name "openvscode*" -exec rm -f {} \; + fi +fi + +# Start OpenVSCode Server in a tmux session +if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then + tmux kill-session -t "$TMUX_SESSION" +fi +tmux new-session -d -s "$TMUX_SESSION" "$INSTALL_DIR/bin/openvscode-server" + +echo "OpenVSCode Server is running in a tmux session named '$TMUX_SESSION'." + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/scripts/ourinit.sh +```sh +#!/bin/bash -e +redis-server --daemonize yes + +TMUX_SESSION="vscode" +# Start OpenVSCode Server in a tmux session +if tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then + tmux kill-session -t "$TMUX_SESSION" +fi +tmux new-session -d -s "$TMUX_SESSION" "/usr/local/bin/openvscode-server --host 0.0.0.0 --without-connection-token" + +service ssh start + +exec /bin/bash + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/shell.sh +```sh +#!/bin/bash -e + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +CONTAINER_NAME="herolib" +TARGET_PORT=4000 + +# Function to check if a container is running +is_container_running() { + docker ps --filter "name=$CONTAINER_NAME" --filter "status=running" -q +} + +# Function to check if a port is accessible +is_port_accessible() { + nc -zv 127.0.0.1 "$1" &>/dev/null +} + +# Check if the container exists and is running +if ! is_container_running; then + echo "Container $CONTAINER_NAME is not running." + + # Check if the container exists but is stopped + if docker ps -a --filter "name=$CONTAINER_NAME" -q | grep -q .; then + echo "Starting existing container $CONTAINER_NAME..." + docker start "$CONTAINER_NAME" + else + echo "Container $CONTAINER_NAME does not exist. Attempting to start with start.sh..." + if [[ -f "$SCRIPT_DIR/start.sh" ]]; then + bash "$SCRIPT_DIR/start.sh" + else + echo "Error: start.sh not found in $SCRIPT_DIR." + exit 1 + fi + fi + + # Wait for the container to be fully up + sleep 5 +fi + +# Verify the container is running +if ! is_container_running; then + echo "Error: Failed to start container $CONTAINER_NAME." + exit 1 +fi +echo "Container $CONTAINER_NAME is running." + +# Check if the target port is accessible +if is_port_accessible "$TARGET_PORT"; then + echo "Port $TARGET_PORT is accessible." +else + echo "Port $TARGET_PORT is not accessible. Please check the service inside the container." +fi + +# Enter the container +echo +echo " ** WE NOW LOGIN TO THE CONTAINER ** " +echo +docker exec -it herolib bash + + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/ssh.sh +```sh +#!/bin/bash -e + +ssh root@localhost -p 4022 + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/ssh_init.sh +```sh +#!/bin/bash -e + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +# Define variables +CONTAINER_NAME="herolib" +CONTAINER_SSH_DIR="/root/.ssh" +AUTHORIZED_KEYS="authorized_keys" +TEMP_AUTH_KEYS="/tmp/authorized_keys" + +# Step 1: Create a temporary file to store public keys +> $TEMP_AUTH_KEYS # Clear the file if it exists + +# Step 2: Add public keys from ~/.ssh/ if they exist +if ls ~/.ssh/*.pub 1>/dev/null 2>&1; then + cat ~/.ssh/*.pub >> $TEMP_AUTH_KEYS +fi + +# Step 3: Check if ssh-agent is running and get public keys from it +if pgrep ssh-agent >/dev/null; then + echo "ssh-agent is running. Fetching keys..." + ssh-add -L >> $TEMP_AUTH_KEYS 2>/dev/null +else + echo "ssh-agent is not running or no keys loaded." +fi + +# Step 4: Ensure the temporary file is not empty +if [ ! -s $TEMP_AUTH_KEYS ]; then + echo "No public keys found. Exiting." + exit 1 +fi + +# Step 5: Ensure the container's SSH directory exists +docker exec -it $CONTAINER_NAME mkdir -p $CONTAINER_SSH_DIR +docker exec -it $CONTAINER_NAME chmod 700 $CONTAINER_SSH_DIR + +# Step 6: Copy the public keys into the container's authorized_keys file +docker cp $TEMP_AUTH_KEYS $CONTAINER_NAME:$CONTAINER_SSH_DIR/$AUTHORIZED_KEYS + +# Step 7: Set proper permissions for authorized_keys +docker exec -it $CONTAINER_NAME chmod 600 $CONTAINER_SSH_DIR/$AUTHORIZED_KEYS + +# Step 8: Install and start the SSH server inside the container +docker exec -it $CONTAINER_NAME bash -c " + apt-get update && + apt-get install -y openssh-server && + mkdir -p /var/run/sshd && + echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config && + echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config && + chown -R root:root /root/.ssh && + chmod -R 700 /root/.ssh/ && + chmod 600 /root/.ssh/authorized_keys && + service ssh start +" + +# Step 9: Clean up temporary file on the host +rm $TEMP_AUTH_KEYS + +echo "SSH keys added and SSH server configured. You can now SSH into the container." + +ssh root@localhost -p 4022 + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib/start.sh +```sh +#!/bin/bash -e + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +docker rm herolib --force + +docker compose up -d + +./ssh_init.sh +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/postgresql/docker-compose.yml +```yml +version: '3.9' +services: + db: + image: 'postgres:17.2-alpine3.21' + restart: always + ports: + - 5432:5432 + environment: + POSTGRES_PASSWORD: 1234 + networks: + - my_network + + adminer: + image: adminer + restart: always + ports: + - 8080:8080 + networks: + - my_network + +networks: + my_network: +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/postgresql/readme.md +```md + + +Server (Host): db (because Docker Compose creates an internal network and uses service names as hostnames) +Username: postgres (default PostgreSQL username) +Password: 1234 (as set in your POSTGRES_PASSWORD environment variable) +Database: Leave it empty or enter postgres (default database) + +``` + +/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/postgresql/start.sh +```sh +#!/bin/bash -e + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +# Stop any existing containers and remove them +docker compose down + +# Start the services in detached mode +docker compose up -d + +echo "PostgreSQL is ready" + +``` + \ No newline at end of file diff --git a/examples/develop/heroprompt/heroprompt_example.vsh b/examples/develop/heroprompt/heroprompt_example.vsh index 8c02e06a..e41842e7 100755 --- a/examples/develop/heroprompt/heroprompt_example.vsh +++ b/examples/develop/heroprompt/heroprompt_example.vsh @@ -1,38 +1,46 @@ #!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run import freeflowuniverse.herolib.develop.heroprompt -import freeflowuniverse.herolib.core.playbook import os -// heroscript_config := ' -// !!heropromptworkspace.configure name:"test workspace" path:"${os.home_dir()}/code/github/freeflowuniverse/herolib" -// ' -// mut plbook := playbook.new( -// text: heroscript_config -// )! - -// heroprompt.play(mut plbook)! - -// mut workspace1 := heroprompt.new_workspace( +// mut workspace := heroprompt.new( // path: '${os.home_dir()}/code/github/freeflowuniverse/herolib' +// name: 'workspace' // )! -mut workspace2 := heroprompt.get( - name: 'test workspace' +mut workspace := heroprompt.get( + name: 'example_ws' + path: '${os.home_dir()}/code/github/freeflowuniverse/herolib' + create: true )! -// workspace1.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker')! -// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh')! +println('workspace (initial): ${workspace}') +println('selected (initial): ${workspace.selected_children()}') -// workspace1.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib')! -// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib/.gitignore')! -// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib/build.sh')! -// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib/debug.sh')! +// Add a directory and a file +workspace.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker')! +workspace.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh')! +println('selected (after add): ${workspace.selected_children()}') -// workspace1.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/postgresql')! +// Build a prompt from current selection (should be empty now) +mut prompt := workspace.prompt( + text: 'Using the selected files, i want you to get all print statments' +) -// prompt := workspace1.prompt( -// text: 'Using the selected files, i want you to get all print statments' -// ) +println('--- PROMPT START ---') +println(prompt) +println('--- PROMPT END ---') -// println(prompt) +// Remove the file by name, then the directory by name +workspace.remove_file(name: 'docker_ubuntu_install.sh') or { println('remove_file: ${err}') } +workspace.remove_dir(name: 'docker') or { println('remove_dir: ${err}') } +println('selected (after remove): ${workspace.selected_children()}') + +// List workspaces (names only) +mut all := heroprompt.list_workspaces() or { []&heroprompt.Workspace{} } +mut names := []string{} +for w in all { names << w.name } +println('workspaces: ${names}') + +// Optionally delete the example workspace +workspace.delete_workspace() or { println('delete_workspace: ${err}') } diff --git a/examples/develop/heroprompt/heroprompt_heroscript_example.vsh b/examples/develop/heroprompt/heroprompt_heroscript_example.vsh deleted file mode 100644 index e11fdf52..00000000 --- a/examples/develop/heroprompt/heroprompt_heroscript_example.vsh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run - -import freeflowuniverse.herolib.core.playbook -import os - - -heroscript_config := ' - !!heropromptworkspace.configure name:"test workspace" path:"${os.home_dir()}/code/github/freeflowuniverse/herolib" -' -mut plbook := playbook.new( - text: heroscript_config -)! - - -heroprompt.play(mut plbook)! diff --git a/lib/develop/heroprompt/heroprompt_factory_.v b/lib/develop/heroprompt/heroprompt_factory_.v index 4f0d6a33..e5b9f4fd 100644 --- a/lib/develop/heroprompt/heroprompt_factory_.v +++ b/lib/develop/heroprompt/heroprompt_factory_.v @@ -4,6 +4,8 @@ import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console import json +import os +import time __global ( heroprompt_global map[string]&Workspace @@ -16,13 +18,32 @@ __global ( pub struct ArgsGet { pub mut: name string = 'default' + path string fromdb bool // will load from filesystem create bool // default will not create if not exist } pub fn new(args ArgsGet) !&Workspace { + // validate + if args.name.len == 0 { + return error('workspace name is required') + } + mut base_path := '' + if args.path.len > 0 { + if !os.exists(args.path) { + return error('workspace path does not exist: ${args.path}') + } + if !os.is_dir(args.path) { + return error('workspace path is not a directory: ${args.path}') + } + base_path = os.real_path(args.path) + } mut obj := Workspace{ - name: args.name + name: args.name + base_path: base_path + created: time.now() + updated: time.now() + is_saved: false } set(obj)! return get(name: args.name)! @@ -36,7 +57,7 @@ pub fn get(args ArgsGet) !&Workspace { if r.hexists('context:heroprompt', args.name)! { data := r.hget('context:heroprompt', args.name)! if data.len == 0 { - return error('Workspace with name: heroprompt does not exist, prob bug.') + return error('Workspace with name: ${args.name} does not exist, prob bug.') } mut obj := json.decode(Workspace, data)! set_in_mem(obj)! @@ -44,7 +65,7 @@ pub fn get(args ArgsGet) !&Workspace { if args.create { new(args)! } else { - return error("Workspace with name 'heroprompt' does not exist") + return error("Workspace with name '${args.name}' does not exist") } } return get(name: args.name)! // no longer from db nor create diff --git a/lib/develop/heroprompt/heroprompt_model.v b/lib/develop/heroprompt/heroprompt_model.v index c4d21fe5..e306dadd 100644 --- a/lib/develop/heroprompt/heroprompt_model.v +++ b/lib/develop/heroprompt/heroprompt_model.v @@ -1,28 +1,28 @@ module heroprompt -import freeflowuniverse.herolib.data.paramsparser import freeflowuniverse.herolib.data.encoderhero -import os +import time pub const version = '0.0.0' const singleton = false const default = true -/ // Workspace represents a workspace containing multiple directories // and their selected files for AI prompt generation @[heap] pub struct Workspace { pub mut: name string = 'default' // Workspace name - base_path string // Base path of the workspace - children []HeropromptChild // List of directories and files in this workspace + base_path string // Base path of the workspace + children []HeropromptChild // List of directories and files in this workspace + created time.Time // Time of creation + updated time.Time // Time of last update + is_saved bool } - // your checking & initialization code if needed fn obj_init(mycfg_ Workspace) !Workspace { - return mycfg + return mycfg_ } /////////////NORMALLY NO NEED TO TOUCH diff --git a/lib/develop/heroprompt/heroprompt_workspace.v b/lib/develop/heroprompt/heroprompt_workspace.v index adc1e852..504a10e2 100644 --- a/lib/develop/heroprompt/heroprompt_workspace.v +++ b/lib/develop/heroprompt/heroprompt_workspace.v @@ -4,32 +4,14 @@ import rand import time import os import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.develop.codewalker - -/ -/// Create a new workspace -/// If the name is not passed, we will generate a random one -fn (wsp Workspace) new(args_ NewWorkspaceParams) !&Workspace { - mut args := args_ - if args.name.len == 0 { - args.name = generate_random_workspace_name() - } - - // Validate and set base path - if args.path.len > 0 { - if !os.exists(args.path) { - return error('Workspace path does not exist: ${args.path}') - } - if !os.is_dir(args.path) { - return error('Workspace path is not a directory: ${args.path}') - } - } - - mut workspace := &Workspace{ - name: args.name - base_path: os.real_path(args.path) - } - return workspace +fn (wsp &Workspace) save() !&Workspace { + mut tmp := wsp + tmp.updated = time.now() + tmp.is_saved = true + set(tmp)! + return get(name: wsp.name)! } // // WorkspaceItem represents a file or directory in the workspace tree @@ -209,13 +191,22 @@ pub mut: // add a directory to the selection (no recursion stored; recursion is done on-demand) pub fn (mut wsp Workspace) add_dir(args AddDirParams) !HeropromptChild { if args.path.len == 0 { - return error('The dir path is required') + return error('the directory path is required') } + if !os.exists(args.path) || !os.is_dir(args.path) { - return error('Path is not an existing directory: ${args.path}') + return error('path is not an existing directory: ${args.path}') } + abs_path := os.real_path(args.path) name := os.base(abs_path) + + for child in wsp.children { + if child.name == name { + return error('another directory with the same name already exists: ${name}') + } + } + mut ch := HeropromptChild{ path: pathlib.Path{ path: abs_path @@ -225,6 +216,7 @@ pub fn (mut wsp Workspace) add_dir(args AddDirParams) !HeropromptChild { name: name } wsp.children << ch + wsp.save()! return ch } @@ -233,11 +225,24 @@ pub fn (mut wsp Workspace) add_file(args AddFileParams) !HeropromptChild { if args.path.len == 0 { return error('The file path is required') } + if !os.exists(args.path) || !os.is_file(args.path) { return error('Path is not an existing file: ${args.path}') } + abs_path := os.real_path(args.path) name := os.base(abs_path) + + for child in wsp.children { + if child.path.cat == .file && child.name == name { + return error('another file with the same name already exists: ${name}') + } + + if child.path.cat == .dir && child.name == name { + return error('${name}: is a directory, cannot add file with same name') + } + } + content := os.read_file(abs_path) or { '' } mut ch := HeropromptChild{ path: pathlib.Path{ @@ -248,10 +253,96 @@ pub fn (mut wsp Workspace) add_file(args AddFileParams) !HeropromptChild { name: name content: content } + wsp.children << ch + wsp.save()! return ch } +// Removal API +@[params] +pub struct RemoveParams { +pub mut: + path string + name string +} + +// Remove a directory from the selection (by absolute path or name) +pub fn (mut wsp Workspace) remove_dir(args RemoveParams) ! { + if args.path.len == 0 && args.name.len == 0 { + return error('either path or name is required to remove a directory') + } + mut idxs := []int{} + for i, ch in wsp.children { + if ch.path.cat != .dir { + continue + } + if args.path.len > 0 && os.real_path(args.path) == ch.path.path { + idxs << i + continue + } + if args.name.len > 0 && args.name == ch.name { + idxs << i + } + } + if idxs.len == 0 { + return error('no matching directory found to remove') + } + // remove from end to start to keep indices valid + idxs.sort(a > b) + for i in idxs { + wsp.children.delete(i) + } + wsp.save()! +} + +// Remove a file from the selection (by absolute path or name) +pub fn (mut wsp Workspace) remove_file(args RemoveParams) ! { + if args.path.len == 0 && args.name.len == 0 { + return error('either path or name is required to remove a file') + } + mut idxs := []int{} + for i, ch in wsp.children { + if ch.path.cat != .file { + continue + } + if args.path.len > 0 && os.real_path(args.path) == ch.path.path { + idxs << i + continue + } + if args.name.len > 0 && args.name == ch.name { + idxs << i + } + } + if idxs.len == 0 { + return error('no matching file found to remove') + } + idxs.sort(a > b) + for i in idxs { + wsp.children.delete(i) + } + wsp.save()! +} + +// Delete this workspace from the store +pub fn (wsp &Workspace) delete_workspace() ! { + delete(name: wsp.name)! +} + +// List workspaces (wrapper over factory list) +pub fn list_workspaces() ![]&Workspace { + return list(fromdb: false)! +} + +pub fn list_workspaces_fromdb() ![]&Workspace { + return list(fromdb: true)! +} + +// Get the currently selected children (copy) +pub fn (wsp Workspace) selected_children() []HeropromptChild { + return wsp.children.clone() +} + // Build utilities fn list_files_recursive(root string) []string { mut out := []string{} @@ -268,7 +359,7 @@ fn list_files_recursive(root string) []string { } // build_file_content generates formatted content for all selected files (and all files under selected dirs) -fn (wsp Workspace) build_file_content() string { +fn (wsp Workspace) build_file_content() !string { mut content := '' // files selected directly for ch in wsp.children { @@ -291,16 +382,18 @@ fn (wsp Workspace) build_file_content() string { } } } - // files under selected directories + // files under selected directories, using CodeWalker for filtered traversal for ch in wsp.children { if ch.path.cat == .dir { - for f in list_files_recursive(ch.path.path) { + mut cw := codewalker.new(codewalker.CodeWalkerArgs{})! + mut fm := cw.filemap_get(path: ch.path.path)! + for rel, fc in fm.content { if content.len > 0 { content += '\n\n' } - content += f + '\n' - ext := get_file_extension(os.base(f)) - fc := os.read_file(f) or { '' } + abs := os.join_path(ch.path.path, rel) + content += abs + '\n' + ext := get_file_extension(os.base(abs)) if fc.len == 0 { content += '(Empty file)\n' } else { @@ -339,7 +432,7 @@ fn build_file_tree_fs(roots []HeropromptChild, prefix string) string { files.sort() // files for j, f in files { - file_connector := if (j == files.len - 1 && dirs.len == 0) { + file_connector := if j == files.len - 1 && dirs.len == 0 { '└── ' } else { '├── ' @@ -455,7 +548,7 @@ pub mut: pub fn (wsp Workspace) prompt(args WorkspacePrompt) string { user_instructions := wsp.build_user_instructions(args.text) file_map := wsp.build_file_map() - file_contents := wsp.build_file_content() + file_contents := wsp.build_file_content() or { '(Error building file contents)' } prompt := HeropromptTmpPrompt{ user_instructions: user_instructions file_map: file_map