Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.dev-remote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ services:
context: .
dockerfile: Dockerfile.upload-service
container_name: upload-service-dev-remote
user: "1000:1000"
user: "0:0" # Windows bind-mount to /work requires root to create workspace dirs
depends_on:
- qdrant
env_file:
Expand Down
38 changes: 28 additions & 10 deletions scripts/remote_upload_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import tempfile
import logging
import argparse
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Dict, List, Any, Optional, Tuple
from datetime import datetime
import requests
Expand Down Expand Up @@ -48,15 +48,33 @@ class RemoteUploadClient:

def _translate_to_container_path(self, host_path: str) -> str:
"""Translate host path to container path for API communication."""
# Use environment variable for path mapping if available
host_root = os.environ.get("HOST_ROOT", "/home/coder/project/Context-Engine/dev-workspace")
container_root = os.environ.get("CONTAINER_ROOT", "/work")

if host_path.startswith(host_root):
return host_path.replace(host_root, container_root)
else:
# Fallback: if path doesn't match expected pattern, use as-is
return host_path
host_root = (os.environ.get("HOST_ROOT", "") or "/home/coder/project/Context-Engine/dev-workspace").strip()
container_root = (os.environ.get("CONTAINER_ROOT", "/work") or "/work").strip()

host_path_obj = Path(host_path)
if host_root:
try:
host_root_obj = Path(host_root)
relative = host_path_obj.relative_to(host_root_obj)
container = PurePosixPath(container_root)
if relative.parts:
container = container.joinpath(*relative.parts)
return str(container)
except ValueError:
pass
except Exception:
pass

try:
container = PurePosixPath(container_root)
usable_parts = [part for part in host_path_obj.parts if part not in (host_path_obj.anchor, host_path_obj.drive)]
if usable_parts:
repo_name = usable_parts[-1]
return str(container.joinpath(repo_name))
except Exception:
pass

return host_path.replace('\\', '/').replace(':', '')

def __init__(self, upload_endpoint: str, workspace_path: str, collection_name: str,
max_retries: int = 3, timeout: int = 30, metadata_path: Optional[str] = None):
Expand Down
39 changes: 29 additions & 10 deletions scripts/standalone_upload_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import tempfile
import logging
import argparse
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Dict, List, Any, Optional, Tuple
from datetime import datetime
import requests
Expand Down Expand Up @@ -204,15 +204,34 @@ class RemoteUploadClient:

def _translate_to_container_path(self, host_path: str) -> str:
"""Translate host path to container path for API communication."""
# Use environment variable for path mapping if available
host_root = os.environ.get("HOST_ROOT", "/home/coder/project/Context-Engine/dev-workspace")
container_root = os.environ.get("CONTAINER_ROOT", "/work")

if host_path.startswith(host_root):
return host_path.replace(host_root, container_root)
else:
# Fallback: if path doesn't match expected pattern, use as-is
return host_path
host_root = (os.environ.get("HOST_ROOT", "") or "/home/coder/project/Context-Engine/dev-workspace").strip()
container_root = (os.environ.get("CONTAINER_ROOT", "/work") or "/work").strip()

host_path_obj = Path(host_path)
if host_root:
try:
host_root_obj = Path(host_root)
relative = host_path_obj.relative_to(host_root_obj)
container = PurePosixPath(container_root)
if relative.parts:
container = container.joinpath(*relative.parts)
return str(container)
except ValueError:
pass
except Exception:
pass

# Fallback: strip drive/anchor and map to /work/<repo-name>
try:
container = PurePosixPath(container_root)
usable_parts = [part for part in host_path_obj.parts if part not in (host_path_obj.anchor, host_path_obj.drive)]
if usable_parts:
repo_name = usable_parts[-1]
return str(container.joinpath(repo_name))
except Exception:
pass

return host_path.replace('\\', '/').replace(':', '')

def __init__(self, upload_endpoint: str, workspace_path: str, collection_name: str,
max_retries: int = 3, timeout: int = 30, metadata_path: Optional[str] = None):
Expand Down
30 changes: 30 additions & 0 deletions vscode-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Build output directory
out/

# VSCode extension build artifacts
*.vsix

# Node modules
node_modules/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE files
.vscode/
.idea/

# Temporary build files
**/standalone_upload_client.py
2 changes: 2 additions & 0 deletions vscode-extension/build/Build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Args:
bundle-deps # Include python dependencies in the vsix
19 changes: 19 additions & 0 deletions vscode-extension/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Docker build for VSCode extension
FROM node:18-alpine

ARG BUNDLE_DEPS=false

# Install bash/coreutils and Python for staging logic and optional dependency bundling, plus vsce for packaging
RUN apk add --no-cache bash coreutils python3 py3-pip \
&& npm install -g @vscode/vsce

# Copy entire repository into the image
WORKDIR /workspace
COPY ../.. /workspace

# Run the shared build script to produce a VSIX into /workspace/vscode-extension/out
WORKDIR /workspace/vscode-extension/build
RUN if [ "$BUNDLE_DEPS" = "true" ]; then bash build.sh --bundle-deps; else bash build.sh; fi

# Expose the output directory as the final working directory
WORKDIR /workspace/vscode-extension/out
86 changes: 86 additions & 0 deletions vscode-extension/build/build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
@echo off
setlocal EnableExtensions
pushd "%~dp0"

set "BUNDLE_DEPS=0"
set "PYTHON_BIN=%PYTHON_BIN%"
if "%PYTHON_BIN%"=="" set "PYTHON_BIN=python"
if /I "%1"=="bundle-deps" set "BUNDLE_DEPS=1"

for %%I in ("..\context-engine-uploader") do set "EXT_DIR=%%~fI"
for %%I in ("..\out") do set "OUT_DIR=%%~fI"
for %%I in ("..\..\scripts\standalone_upload_client.py") do set "SRC_SCRIPT=%%~fI"
set "CLIENT=standalone_upload_client.py"
set "STAGE_DIR=%OUT_DIR%\extension-stage"
set "BUILD_RESULT=0"

echo Building clean Context Engine Uploader extension...

if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
if errorlevel 1 (
echo Failed to create output directory.
set "BUILD_RESULT=1"
goto cleanup
)

REM Ensure the source extension directory has no lingering upload client
if exist "%EXT_DIR%\%CLIENT%" del /f /q "%EXT_DIR%\%CLIENT%"

copy /Y "%SRC_SCRIPT%" "%OUT_DIR%\%CLIENT%" >nul
if errorlevel 1 (
echo Failed to copy upload client from scripts directory.
set "BUILD_RESULT=1"
goto cleanup
)

if exist "%STAGE_DIR%" rd /s /q "%STAGE_DIR%"
mkdir "%STAGE_DIR%"
if errorlevel 1 (
echo Failed to create staging directory.
set "BUILD_RESULT=1"
goto cleanup
)

robocopy "%EXT_DIR%" "%STAGE_DIR%" /E /NFL /NDL /NJH /NJS /NP >nul
if errorlevel 8 (
echo Failed to copy extension into staging directory.
set "BUILD_RESULT=1"
goto cleanup
)

copy /Y "%OUT_DIR%\%CLIENT%" "%STAGE_DIR%\%CLIENT%" >nul
if errorlevel 1 (
echo Failed to place upload client into staging directory.
set "BUILD_RESULT=1"
goto cleanup
)

REM Optional: bundle Python dependencies into the staged extension when requested
if "%BUNDLE_DEPS%"=="1" (
echo Bundling Python dependencies into staged extension using %PYTHON_BIN%...
"%PYTHON_BIN%" -m pip install -t "%STAGE_DIR%\python_libs" requests urllib3 charset_normalizer
if errorlevel 1 (
echo Failed to install Python dependencies into staged extension.
set "BUILD_RESULT=1"
goto cleanup
)
)

pushd "%STAGE_DIR%"
echo Packaging extension...
npx @vscode/vsce package --no-dependencies --out "%OUT_DIR%"
if errorlevel 1 (
echo Packaging failed.
set "BUILD_RESULT=1"
popd
goto cleanup
)
popd

echo Build complete! Check the /out directory for .vsix files.
dir "%OUT_DIR%\*.vsix"

:cleanup
if exist "%STAGE_DIR%" rd /s /q "%STAGE_DIR%"
popd
endlocal & exit /b %BUILD_RESULT%
49 changes: 49 additions & 0 deletions vscode-extension/build/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXT_DIR="$SCRIPT_DIR/../context-engine-uploader"
OUT_DIR="$SCRIPT_DIR/../out"
SRC_SCRIPT="$SCRIPT_DIR/../../scripts/standalone_upload_client.py"
CLIENT="standalone_upload_client.py"
STAGE_DIR="$OUT_DIR/extension-stage"
BUNDLE_DEPS="${1:-}"
PYTHON_BIN="${PYTHON_BIN:-python3}"

cleanup() {
rm -rf "$STAGE_DIR"
}
trap cleanup EXIT

echo "Building clean Context Engine Uploader extension..."

mkdir -p "$OUT_DIR"

# Ensure extension directory is clean
rm -f "$EXT_DIR/$CLIENT"

# Copy upload client to the distributable out directory
cp "$SRC_SCRIPT" "$OUT_DIR/$CLIENT"

# Prepare staging directory
rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR"
cp -a "$EXT_DIR/." "$STAGE_DIR/"

# Inject the upload client into the staged extension for packaging
cp "$OUT_DIR/$CLIENT" "$STAGE_DIR/$CLIENT"
chmod +x "$STAGE_DIR/$CLIENT"

# Optional: bundle Python deps into the staged extension when requested
if [[ "$BUNDLE_DEPS" == "--bundle-deps" ]]; then
echo "Bundling Python dependencies into staged extension using $PYTHON_BIN..."
"$PYTHON_BIN" -m pip install -t "$STAGE_DIR/python_libs" requests urllib3 charset_normalizer
fi

pushd "$STAGE_DIR" >/dev/null
echo "Packaging extension..."
npx @vscode/vsce package --no-dependencies --out "$OUT_DIR"
popd >/dev/null

echo "Build complete! Check the /out directory for .vsix and .py files."
ls -la "$OUT_DIR"
7 changes: 7 additions & 0 deletions vscode-extension/context-engine-uploader/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Temporary build files
standalone_upload_client.py
*.vsix

# OS generated files
.DS_Store
Thumbs.db
10 changes: 10 additions & 0 deletions vscode-extension/context-engine-uploader/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# VSCode extension - exclude files from packaging
.vscode/**
build/**
out/**
*.vsix
.gitignore
build.sh
build.bat
Dockerfile
README.md
2 changes: 2 additions & 0 deletions vscode-extension/context-engine-uploader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Configuration
- `Run On Startup` auto-triggers force sync + watch after VS Code finishes loading.
- `Python Path`, `Endpoint`, `Extra Force Args`, `Extra Watch Args`, and `Interval Seconds` can be tuned via standard VS Code settings.
- `Target Path` is auto-filled from the workspace but can be overridden if you need to upload a different folder.
- **Python dependencies:** the extension runs the standalone upload client via your configured `pythonPath`. Ensure the interpreter has `requests`, `urllib3`, and `charset_normalizer` installed. Run `python3 -m pip install requests urllib3 charset_normalizer` (or replace `python3` with your configured path) before starting the uploader.
- **Path mapping:** `Host Root` + `Container Root` control how local paths are rewritten before reaching the remote service. By default the host root mirrors your `Target Path` and the container root is `/work`, which keeps Windows paths working without extra config.

Commands
--------
Expand Down
Binary file not shown.
Loading
Loading