calmodovar23 commited on
Commit
a7277a1
·
verified ·
1 Parent(s): 62de6c0

Upload 11 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .venv
3
+ uv.lock
4
+ __pycache__/
5
+ *.py[cod]
6
+ .pytest_cache/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .env
10
+ .env.*
11
+ .claude/
12
+ .claude.json
13
+ .DS_Store
14
+ *.zip
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ uv.lock
3
+ __pycache__/
4
+ *.py[cod]
5
+ .env
6
+ .env.*
7
+ .claude/
8
+ .claude.json
9
+ .DS_Store
10
+ *.zip
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
Dockerfile CHANGED
@@ -6,35 +6,46 @@ ARG UV_VERSION=0.11.29
6
  FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
7
  FROM ghcr.io/coder/code-server:${CODE_SERVER_VERSION}
8
 
 
 
9
  USER root
10
 
11
- # Install a small Python build toolchain, the official Claude Code CLI,
12
  # and the official ngrok Agent CLI.
13
- RUN apt-get update \
14
- && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
 
 
15
  build-essential \
16
  ca-certificates \
17
  curl \
 
18
  jq \
19
  pkg-config \
20
  ripgrep \
21
- unzip \
22
- && install -d -m 0755 /etc/apt/keyrings \
23
- && curl -fsSL https://downloads.claude.ai/keys/claude-code.asc \
24
- -o /etc/apt/keyrings/claude-code.asc \
25
- && printf '%s\n' \
26
- 'deb [signed-by=/etc/apt/keyrings/claude-code.asc] https://downloads.claude.ai/claude-code/apt/stable stable main' \
27
- > /etc/apt/sources.list.d/claude-code.list \
28
- && curl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
29
- -o /etc/apt/keyrings/ngrok.asc \
30
- && printf '%s\n' \
31
  'deb [signed-by=/etc/apt/keyrings/ngrok.asc] https://ngrok-agent.s3.amazonaws.com buster main' \
32
- > /etc/apt/sources.list.d/ngrok.list \
33
- && apt-get update \
34
- && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
35
  claude-code \
36
- ngrok \
37
- && rm -rf /var/lib/apt/lists/*
 
 
 
 
 
 
38
 
39
  COPY --from=uv /uv /uvx /usr/local/bin/
40
 
@@ -48,10 +59,12 @@ ENV HOME=/home/coder \
48
  PATH="/home/coder/workspace/.venv/bin:/home/coder/.local/bin:${PATH}" \
49
  PYTHONDONTWRITEBYTECODE=1 \
50
  PYTHONUNBUFFERED=1 \
51
- CS_DISABLE_GETTING_STARTED_OVERRIDE=true
 
 
52
 
53
- # /data is mounted only at runtime on Hugging Face Spaces. Build seed artifacts
54
- # elsewhere and copy them into the persistent disk from the startup initializer.
55
  RUN install -d -o coder -g coder \
56
  /opt/bootstrap \
57
  /opt/bootstrap/code-server-user-data \
@@ -60,25 +73,28 @@ RUN install -d -o coder -g coder \
60
  /opt/uv/python \
61
  /home/coder/workspace
62
 
63
- COPY --chown=coder:coder pyproject.toml /tmp/workspace-deps/pyproject.toml
 
 
64
 
65
  USER coder
66
 
67
- # Build a relocatable Python 3.12 project environment. It is restored under
68
- # /data/workspace and exposed through /home/coder/workspace at runtime.
 
69
  RUN set -eux; \
70
- cp /tmp/workspace-deps/pyproject.toml /home/coder/workspace/pyproject.toml; \
71
  cd /home/coder/workspace; \
72
  uv python install 3.12; \
73
  uv sync --python 3.12 --no-install-project; \
74
- cp uv.lock /opt/bootstrap/uv.lock; \
 
 
75
  tar -C /home/coder/workspace -czf /opt/bootstrap/venv.tar.gz .venv; \
76
  cd /; \
77
  uv cache clean; \
78
- rm -rf /home/coder/workspace /tmp/workspace-deps
79
 
80
  # Install and verify the requested extensions into image-side bootstrap paths.
81
- # Runtime initialization copies them to /data and repairs the registry if needed.
82
  RUN set -eux; \
83
  code-server \
84
  --user-data-dir /opt/bootstrap/code-server-user-data \
@@ -93,32 +109,22 @@ RUN set -eux; \
93
  grep -Fxi 'ms-python.python' /tmp/preinstalled-extensions.txt; \
94
  rm -f /tmp/preinstalled-extensions.txt
95
 
96
- # Seed the Space repository into the persistent workspace. Include the generated
97
- # uv.lock even when it is excluded from the repository build context.
98
- COPY --chown=coder:coder . /opt/bootstrap/workspace-src/
99
-
100
- RUN set -eux; \
101
- cp /opt/bootstrap/uv.lock /opt/bootstrap/workspace-src/uv.lock; \
102
- tar -C /opt/bootstrap/workspace-src -czf /opt/bootstrap/workspace-seed.tar.gz .; \
103
- rm -rf /opt/bootstrap/workspace-src
104
-
105
  USER root
106
 
107
  COPY --chown=root:root --chmod=0755 scripts/10-init-persistent-data.sh \
108
  /usr/local/bin/init-persistent-data
109
  COPY --chown=root:root --chmod=0755 scripts/start-code-server.sh \
110
  /usr/local/bin/start-code-server
 
 
111
 
112
- # These locations exist only after the HF persistent disk is mounted.
113
  ENV UV_CACHE_DIR=/data/cache/uv \
114
  HF_HOME=/data/cache/huggingface \
115
  CLAUDE_CONFIG_DIR=/data/claude
116
 
117
  USER coder
118
 
119
- # The workspace symlink is created at runtime, so the OCI process must begin in
120
- # an image-resident directory. The wrapper changes into the workspace before
121
- # launching code-server.
122
  WORKDIR /home/coder
123
 
124
  EXPOSE 7860
 
6
  FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
7
  FROM ghcr.io/coder/code-server:${CODE_SERVER_VERSION}
8
 
9
+ ARG CLAUDE_CODE_CHANNEL=latest
10
+
11
  USER root
12
 
13
+ # Install a small native build toolchain, the official Claude Code CLI,
14
  # and the official ngrok Agent CLI.
15
+ RUN set -eux; \
16
+ case "${CLAUDE_CODE_CHANNEL}" in latest|stable) ;; *) exit 2 ;; esac; \
17
+ apt-get update; \
18
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
19
  build-essential \
20
  ca-certificates \
21
  curl \
22
+ git \
23
  jq \
24
  pkg-config \
25
  ripgrep \
26
+ unzip; \
27
+ install -d -m 0755 /etc/apt/keyrings; \
28
+ curl -fsSL https://downloads.claude.ai/keys/claude-code.asc \
29
+ -o /etc/apt/keyrings/claude-code.asc; \
30
+ printf '%s\n' \
31
+ "deb [signed-by=/etc/apt/keyrings/claude-code.asc] https://downloads.claude.ai/claude-code/apt/${CLAUDE_CODE_CHANNEL} ${CLAUDE_CODE_CHANNEL} main" \
32
+ > /etc/apt/sources.list.d/claude-code.list; \
33
+ curl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
34
+ -o /etc/apt/keyrings/ngrok.asc; \
35
+ printf '%s\n' \
36
  'deb [signed-by=/etc/apt/keyrings/ngrok.asc] https://ngrok-agent.s3.amazonaws.com buster main' \
37
+ > /etc/apt/sources.list.d/ngrok.list; \
38
+ apt-get update; \
39
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
40
  claude-code \
41
+ ngrok; \
42
+ CLAUDE_BIN="$(command -v claude)"; \
43
+ test -x "${CLAUDE_BIN}"; \
44
+ ln -sf "${CLAUDE_BIN}" /usr/local/bin/claude-system; \
45
+ /usr/local/bin/claude-system --version; \
46
+ ngrok version; \
47
+ git --version; \
48
+ rm -rf /var/lib/apt/lists/*
49
 
50
  COPY --from=uv /uv /uvx /usr/local/bin/
51
 
 
59
  PATH="/home/coder/workspace/.venv/bin:/home/coder/.local/bin:${PATH}" \
60
  PYTHONDONTWRITEBYTECODE=1 \
61
  PYTHONUNBUFFERED=1 \
62
+ CS_DISABLE_GETTING_STARTED_OVERRIDE=true \
63
+ CLAUDE_SYSTEM_BIN=/usr/local/bin/claude-system \
64
+ CLAUDE_VSCODE_WRAPPER=/usr/local/bin/claude-vscode-wrapper
65
 
66
+ # /data is mounted at runtime. Build all reusable artifacts in the image and
67
+ # restore them into the persistent disk from the startup initializer.
68
  RUN install -d -o coder -g coder \
69
  /opt/bootstrap \
70
  /opt/bootstrap/code-server-user-data \
 
73
  /opt/uv/python \
74
  /home/coder/workspace
75
 
76
+ COPY --chown=coder:coder pyproject.toml /home/coder/workspace/pyproject.toml
77
+ COPY --chown=coder:coder workspace/README.md /opt/bootstrap/workspace-src/README.md
78
+ COPY --chown=coder:coder workspace.gitignore /opt/bootstrap/workspace.gitignore
79
 
80
  USER coder
81
 
82
+ # Resolve the dependency manifest and build the Python 3.12 environment at the
83
+ # same path it will use at runtime. Archive the environment and preserve the
84
+ # project manifest and lockfile for the candidate workspace.
85
  RUN set -eux; \
 
86
  cd /home/coder/workspace; \
87
  uv python install 3.12; \
88
  uv sync --python 3.12 --no-install-project; \
89
+ cp pyproject.toml uv.lock /opt/bootstrap/workspace-src/; \
90
+ cat pyproject.toml uv.lock | sha256sum | awk '{print $1}' \
91
+ | tee /opt/bootstrap/venv.id > .venv/.hf-bootstrap-id; \
92
  tar -C /home/coder/workspace -czf /opt/bootstrap/venv.tar.gz .venv; \
93
  cd /; \
94
  uv cache clean; \
95
+ rm -rf /home/coder/workspace
96
 
97
  # Install and verify the requested extensions into image-side bootstrap paths.
 
98
  RUN set -eux; \
99
  code-server \
100
  --user-data-dir /opt/bootstrap/code-server-user-data \
 
109
  grep -Fxi 'ms-python.python' /tmp/preinstalled-extensions.txt; \
110
  rm -f /tmp/preinstalled-extensions.txt
111
 
 
 
 
 
 
 
 
 
 
112
  USER root
113
 
114
  COPY --chown=root:root --chmod=0755 scripts/10-init-persistent-data.sh \
115
  /usr/local/bin/init-persistent-data
116
  COPY --chown=root:root --chmod=0755 scripts/start-code-server.sh \
117
  /usr/local/bin/start-code-server
118
+ COPY --chown=root:root --chmod=0755 scripts/claude-vscode-wrapper.sh \
119
+ /usr/local/bin/claude-vscode-wrapper
120
 
 
121
  ENV UV_CACHE_DIR=/data/cache/uv \
122
  HF_HOME=/data/cache/huggingface \
123
  CLAUDE_CONFIG_DIR=/data/claude
124
 
125
  USER coder
126
 
127
+ # The workspace symlink is created after the HF persistent disk is mounted.
 
 
128
  WORKDIR /home/coder
129
 
130
  EXPOSE 7860
README.md CHANGED
@@ -1,117 +1,15 @@
1
  ---
2
- title: Seb Test Space
3
- emoji: 🧪
4
- colorFrom: indigo
5
- colorTo: blue
6
  sdk: docker
7
  app_port: 7860
8
  pinned: false
9
  ---
10
 
11
- # code-server POC
12
 
13
- Open the Space through its direct `https://<space-subdomain>.hf.space` URL rather than the embedded App tab.
14
 
15
- ## Space secrets
16
-
17
- Required:
18
-
19
- - `PASSWORD` — code-server web-login password
20
-
21
- Optional:
22
-
23
- - `NGROK_AUTHTOKEN` — authenticates the ngrok agent
24
- - `OPENAI_API_KEY` — used by the OpenAI Python SDK
25
- - `ANTHROPIC_API_KEY` — used by the Anthropic Python SDK
26
-
27
- For Claude Code subscription authentication, initially omit `ANTHROPIC_API_KEY` and run:
28
-
29
- ```bash
30
- claude
31
- ```
32
-
33
- ## Runtime layout
34
-
35
- - Physical persistent workspace: `/data/workspace`
36
- - IDE-visible workspace: `/home/coder/workspace`
37
- - Python 3.12 environment: `/home/coder/workspace/.venv`
38
- - code-server user data: `/data/code-server/user-data`
39
- - code-server extensions: `/data/code-server/extensions`
40
- - Claude Code configuration: `/data/claude`
41
- - ngrok configuration: `/data/ngrok`
42
-
43
- The startup wrapper initializes these paths before code-server starts, changes into `/home/coder/workspace`, and opens that directory as the default folder.
44
-
45
- ## Expected startup log
46
-
47
- ```text
48
- [hf-code-server-init] Workspace ready: /home/coder/workspace -> /data/workspace
49
- [hf-code-server-init] Python ready: Python 3.12.x
50
- [hf-code-server-init] Extensions ready: Anthropic.claude-code ms-python.python ...
51
- ```
52
-
53
- ## Verify from the integrated terminal
54
-
55
- ```bash
56
- pwd
57
- readlink -f /home/coder/workspace
58
- python --version
59
- python -c "import anthropic, fastapi, openai; print('imports OK')"
60
-
61
- code-server \
62
- --user-data-dir /data/code-server/user-data \
63
- --extensions-dir /data/code-server/extensions \
64
- --list-extensions
65
- ```
66
-
67
- Expected essentials:
68
-
69
- ```text
70
- /home/coder/workspace
71
- /data/workspace
72
- Python 3.12.x
73
- Anthropic.claude-code
74
- ms-python.python
75
- ```
76
-
77
- Dependencies are declared in `pyproject.toml`. After editing it:
78
-
79
- ```bash
80
- uv sync
81
- ```
82
-
83
- ## FastAPI and ngrok
84
-
85
- ```bash
86
- uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
87
- ```
88
-
89
- Authenticated code-server proxy:
90
-
91
- ```text
92
- https://<space-subdomain>.hf.space/proxy/8000/
93
- ```
94
-
95
- Public ngrok tunnel:
96
-
97
- ```bash
98
- ngrok http 8000
99
- ```
100
-
101
- ## Reset stale POC state
102
-
103
- The new initializer should repair the existing disk. To reset only code-server settings and extensions while preserving project files:
104
-
105
- ```bash
106
- sudo rm -rf /data/code-server
107
- ```
108
-
109
- Then restart the Space.
110
-
111
- To discard the workspace and its virtual environment too:
112
-
113
- ```bash
114
- sudo rm -rf /data/workspace /data/code-server
115
- ```
116
-
117
- Do not run the second command when the workspace contains anything you need.
 
1
  ---
2
+ title: Coding Interview Workspace
3
+ emoji: 💻
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ # Coding Interview Workspace
12
 
13
+ Open the application using the URL and password supplied by your interviewer.
14
 
15
+ Candidate instructions are available as `README.md` in the workspace that opens in the editor.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -1,7 +1,11 @@
 
 
 
 
1
  [project]
2
- name = "hf-code-server-poc"
3
  version = "0.1.0"
4
- description = "Disposable code-server workspace for Hugging Face Spaces"
5
  requires-python = ">=3.12,<3.13"
6
  dependencies = [
7
  "anthropic",
@@ -10,6 +14,5 @@ dependencies = [
10
  "uvicorn[standard]",
11
  ]
12
 
13
- # This is an application workspace, not a package to publish.
14
  [tool.uv]
15
  package = false
 
1
+ # This project is managed with uv.
2
+ # Add a dependency with: uv add <package>
3
+ # Synchronize the environment with: uv sync
4
+
5
  [project]
6
+ name = "interview-workspace"
7
  version = "0.1.0"
8
+ description = "Python environment for the coding interview workspace"
9
  requires-python = ">=3.12,<3.13"
10
  dependencies = [
11
  "anthropic",
 
14
  "uvicorn[standard]",
15
  ]
16
 
 
17
  [tool.uv]
18
  package = false
scripts/10-init-persistent-data.sh CHANGED
@@ -9,6 +9,8 @@ DATA_ROOT="${DATA_ROOT:-/data}"
9
  CODER_HOME="${CODER_HOME:-/home/coder}"
10
  BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-/opt/bootstrap}"
11
  CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
 
 
12
 
13
  WORKSPACE_DATA="${DATA_ROOT}/workspace"
14
  WORKSPACE_LINK="${WORKSPACE:-${CODER_HOME}/workspace}"
@@ -53,30 +55,8 @@ do
53
  ensure_dir "${directory}"
54
  done
55
 
56
- # Add image-seeded files that are missing, but never overwrite work already on
57
- # the persistent disk. The marker avoids repeating this on normal restarts.
58
- seed_marker="${WORKSPACE_DATA}/.hf-code-server-seed-v4"
59
- if [ ! -f "${seed_marker}" ]; then
60
- log "Seeding persistent workspace at ${WORKSPACE_DATA}"
61
- tar --skip-old-files \
62
- -C "${WORKSPACE_DATA}" \
63
- -xzf "${BOOTSTRAP_ROOT}/workspace-seed.tar.gz"
64
- touch "${seed_marker}"
65
- fi
66
-
67
- # Restore the prebuilt Python 3.12 virtual environment when it is absent or
68
- # invalid. It was built at /home/coder/workspace/.venv, so script shebangs keep
69
- # working through the runtime workspace symlink.
70
- if ! "${WORKSPACE_DATA}/.venv/bin/python" -c \
71
- 'import sys; raise SystemExit(sys.version_info[:2] != (3, 12))' \
72
- >/dev/null 2>&1
73
- then
74
- log "Restoring Python 3.12 virtual environment"
75
- rm -rf "${WORKSPACE_DATA}/.venv"
76
- tar -C "${WORKSPACE_DATA}" -xzf "${BOOTSTRAP_ROOT}/venv.tar.gz"
77
- fi
78
-
79
- # Make the requested IDE-visible path point to persistent storage.
80
  if [ -L "${WORKSPACE_LINK}" ]; then
81
  current_target="$(readlink "${WORKSPACE_LINK}" || true)"
82
  if [ "${current_target}" != "${WORKSPACE_DATA}" ]; then
@@ -93,8 +73,80 @@ if [ ! -L "${WORKSPACE_LINK}" ]; then
93
  ln -s "${WORKSPACE_DATA}" "${WORKSPACE_LINK}"
94
  fi
95
 
96
- # Seed both the extension payload directory and any user-data metadata created
97
- # by code-server's installer. Existing persistent files are left untouched.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  if [ -d "${BOOTSTRAP_ROOT}/code-server-user-data" ] \
99
  && [ -z "$(find "${USER_DATA_DIR}" -mindepth 1 -maxdepth 1 -print -quit)" ]
100
  then
@@ -118,11 +170,8 @@ has_extension() {
118
 
119
  installed_extensions="$(list_extensions)"
120
 
121
- # Copying extension directories is usually enough, but an old persistent
122
- # extension registry can hide them. Let code-server repair its own registry if
123
- # either required extension is not visible.
124
  if ! has_extension 'Anthropic.claude-code' || ! has_extension 'ms-python.python'; then
125
- log "Repairing required extensions in ${EXTENSIONS_DIR}"
126
  "${CODE_SERVER_BIN}" \
127
  --user-data-dir "${USER_DATA_DIR}" \
128
  --extensions-dir "${EXTENSIONS_DIR}" \
@@ -142,6 +191,84 @@ if ! has_extension 'ms-python.python'; then
142
  exit 1
143
  fi
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  # Persist ngrok's default Linux configuration directory.
146
  mkdir -p "${CODER_HOME}/.config"
147
 
@@ -161,11 +288,17 @@ if [ ! -L "${CODER_HOME}/.config/ngrok" ]; then
161
  ln -s "${NGROK_DIR}" "${CODER_HOME}/.config/ngrok"
162
  fi
163
 
164
- # These checks are fatal because the startup wrapper calls this script directly.
165
  test -d "${WORKSPACE_LINK}"
166
- test -x "${WORKSPACE_LINK}/.venv/bin/python"
167
  test -f "${WORKSPACE_LINK}/pyproject.toml"
 
 
 
168
 
169
- log "Workspace ready: ${WORKSPACE_LINK} -> $(readlink -f "${WORKSPACE_LINK}")"
 
 
170
  log "Python ready: $("${WORKSPACE_LINK}/.venv/bin/python" --version 2>&1)"
 
 
171
  log "Extensions ready: $(printf '%s' "${installed_extensions}" | tr '\n' ' ')"
 
9
  CODER_HOME="${CODER_HOME:-/home/coder}"
10
  BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-/opt/bootstrap}"
11
  CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
12
+ CLAUDE_SYSTEM_BIN="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
13
+ CLAUDE_VSCODE_WRAPPER="${CLAUDE_VSCODE_WRAPPER:-/usr/local/bin/claude-vscode-wrapper}"
14
 
15
  WORKSPACE_DATA="${DATA_ROOT}/workspace"
16
  WORKSPACE_LINK="${WORKSPACE:-${CODER_HOME}/workspace}"
 
55
  ensure_dir "${directory}"
56
  done
57
 
58
+ # Make the requested editor-visible path point to the candidate's persistent
59
+ # workspace. The directory is created before code-server is launched.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  if [ -L "${WORKSPACE_LINK}" ]; then
61
  current_target="$(readlink "${WORKSPACE_LINK}" || true)"
62
  if [ "${current_target}" != "${WORKSPACE_DATA}" ]; then
 
73
  ln -s "${WORKSPACE_DATA}" "${WORKSPACE_LINK}"
74
  fi
75
 
76
+ # Seed the candidate instructions and uv project metadata on first startup.
77
+ # Existing candidate edits are never overwritten on later restarts or rebuilds.
78
+ for project_file in README.md pyproject.toml uv.lock; do
79
+ source_file="${BOOTSTRAP_ROOT}/workspace-src/${project_file}"
80
+ target_file="${WORKSPACE_DATA}/${project_file}"
81
+
82
+ if [ ! -e "${target_file}" ]; then
83
+ cp "${source_file}" "${target_file}"
84
+ fi
85
+ done
86
+
87
+ # Restore the prebuilt environment if it is absent, is not Python 3.12, or was
88
+ # built from a different dependency manifest.
89
+ expected_venv_id="$(cat "${BOOTSTRAP_ROOT}/venv.id")"
90
+ actual_venv_id="$(cat "${WORKSPACE_DATA}/.venv/.hf-bootstrap-id" 2>/dev/null || true)"
91
+
92
+ if ! "${WORKSPACE_DATA}/.venv/bin/python" -c \
93
+ 'import sys; raise SystemExit(sys.version_info[:2] != (3, 12))' \
94
+ >/dev/null 2>&1 \
95
+ || [ "${actual_venv_id}" != "${expected_venv_id}" ]
96
+ then
97
+ log "Restoring Python 3.12 virtual environment"
98
+ rm -rf "${WORKSPACE_DATA}/.venv"
99
+ tar -C "${WORKSPACE_DATA}" -xzf "${BOOTSTRAP_ROOT}/venv.tar.gz"
100
+ fi
101
+
102
+ # Each candidate Space owns one repository. Initialize it once, leave existing
103
+ # history untouched on later restarts, and keep environment artifacts ignored
104
+ # without adding a visible top-level .gitignore file.
105
+ new_repository=0
106
+ if [ ! -d "${WORKSPACE_DATA}/.git" ]; then
107
+ log "Initializing Git repository"
108
+ if ! git -C "${WORKSPACE_DATA}" init -b main >/dev/null 2>&1; then
109
+ git -C "${WORKSPACE_DATA}" init >/dev/null
110
+ git -C "${WORKSPACE_DATA}" symbolic-ref HEAD refs/heads/main
111
+ fi
112
+ new_repository=1
113
+ fi
114
+
115
+ if ! git -C "${WORKSPACE_DATA}" config --get user.name >/dev/null 2>&1; then
116
+ git -C "${WORKSPACE_DATA}" config user.name "${GIT_USER_NAME:-Candidate}"
117
+ fi
118
+
119
+ if ! git -C "${WORKSPACE_DATA}" config --get user.email >/dev/null 2>&1; then
120
+ git -C "${WORKSPACE_DATA}" config user.email "${GIT_USER_EMAIL:-candidate@localhost}"
121
+ fi
122
+
123
+ exclude_file="${WORKSPACE_DATA}/.git/info/exclude"
124
+ touch "${exclude_file}"
125
+
126
+ if [ -f "${BOOTSTRAP_ROOT}/workspace.gitignore" ]; then
127
+ while IFS= read -r pattern || [ -n "${pattern}" ]; do
128
+ case "${pattern}" in
129
+ ''|'#'*) continue ;;
130
+ esac
131
+ if ! grep -Fqx -- "${pattern}" "${exclude_file}"; then
132
+ printf '%s\n' "${pattern}" >> "${exclude_file}"
133
+ fi
134
+ done < "${BOOTSTRAP_ROOT}/workspace.gitignore"
135
+ fi
136
+
137
+ # Commit the supplied instructions and uv project definition as the clean
138
+ # baseline for a fresh repository. Existing history is never rewritten.
139
+ if [ "${new_repository}" -eq 1 ] \
140
+ || ! git -C "${WORKSPACE_DATA}" rev-parse --verify HEAD >/dev/null 2>&1
141
+ then
142
+ git -C "${WORKSPACE_DATA}" add README.md pyproject.toml uv.lock
143
+ if ! git -C "${WORKSPACE_DATA}" diff --cached --quiet; then
144
+ git -C "${WORKSPACE_DATA}" -c commit.gpgsign=false commit \
145
+ -m "Initialize Python environment" >/dev/null
146
+ fi
147
+ fi
148
+
149
+ # Restore preinstalled extensions and the installer metadata they need.
150
  if [ -d "${BOOTSTRAP_ROOT}/code-server-user-data" ] \
151
  && [ -z "$(find "${USER_DATA_DIR}" -mindepth 1 -maxdepth 1 -print -quit)" ]
152
  then
 
170
 
171
  installed_extensions="$(list_extensions)"
172
 
 
 
 
173
  if ! has_extension 'Anthropic.claude-code' || ! has_extension 'ms-python.python'; then
174
+ log "Repairing required extensions"
175
  "${CODE_SERVER_BIN}" \
176
  --user-data-dir "${USER_DATA_DIR}" \
177
  --extensions-dir "${EXTENSIONS_DIR}" \
 
191
  exit 1
192
  fi
193
 
194
+ patch_claude_extension_binaries() {
195
+ test -x "${CLAUDE_SYSTEM_BIN}"
196
+
197
+ for extension_dir in \
198
+ "${EXTENSIONS_DIR}"/anthropic.claude-code-* \
199
+ "${EXTENSIONS_DIR}"/Anthropic.claude-code-*
200
+ do
201
+ [ -d "${extension_dir}" ] || continue
202
+
203
+ for binary_name in claude claude.real; do
204
+ bundled_binary="${extension_dir}/resources/native-binary/${binary_name}"
205
+ if [ ! -e "${bundled_binary}" ] && [ ! -L "${bundled_binary}" ]; then
206
+ continue
207
+ fi
208
+
209
+ if "${bundled_binary}" --version >/dev/null 2>&1; then
210
+ continue
211
+ fi
212
+
213
+ log "Replacing incompatible Claude extension binary"
214
+ rm -f "${bundled_binary}"
215
+ ln -s "${CLAUDE_SYSTEM_BIN}" "${bundled_binary}"
216
+ "${bundled_binary}" --version >/dev/null
217
+ done
218
+ done
219
+ }
220
+
221
+ patch_claude_extension_binaries
222
+
223
+ # Keep editor configuration outside the candidate project. The Python
224
+ # environment and Git metadata stay out of Explorer and search; the visible
225
+ # baseline consists of README.md, pyproject.toml, and uv.lock.
226
+ settings_file="${USER_DATA_DIR}/User/settings.json"
227
+ settings_dir="$(dirname "${settings_file}")"
228
+ mkdir -p "${settings_dir}"
229
+
230
+ if [ ! -f "${settings_file}" ]; then
231
+ printf '{}\n' > "${settings_file}"
232
+ fi
233
+
234
+ if jq -e 'type == "object"' "${settings_file}" >/dev/null 2>&1; then
235
+ settings_tmp="${settings_file}.tmp.$$"
236
+ jq \
237
+ --arg wrapper "${CLAUDE_VSCODE_WRAPPER}" \
238
+ --arg python "${WORKSPACE_LINK}/.venv/bin/python" \
239
+ --arg workspace "${WORKSPACE_LINK}" \
240
+ '. + {
241
+ "claudeCode.claudeProcessWrapper": $wrapper,
242
+ "python.defaultInterpreterPath": $python,
243
+ "python.terminal.activateEnvironment": true,
244
+ "terminal.integrated.cwd": $workspace,
245
+ "workbench.startupEditor": "readme",
246
+ "workbench.browser.enableRemoteProxy": true,
247
+ "workbench.browser.dataStorage": "workspace",
248
+ "workbench.browser.openLocalhostLinks": true,
249
+ "workbench.browser.showInTitleBar": true,
250
+ "files.exclude": ((."files.exclude" // {}) + {
251
+ "**/.venv": true,
252
+ "**/.git": true
253
+ }),
254
+ "search.exclude": ((."search.exclude" // {}) + {
255
+ "**/.venv": true,
256
+ "**/.git": true
257
+ })
258
+ }' \
259
+ "${settings_file}" > "${settings_tmp}"
260
+ mv "${settings_tmp}" "${settings_file}"
261
+ else
262
+ log "WARNING: ${settings_file} is not plain JSON; leaving it unchanged"
263
+ fi
264
+
265
+ # Verify the Claude wrapper both with and without the extension-injected path.
266
+ test -x "${CLAUDE_VSCODE_WRAPPER}"
267
+ "${CLAUDE_VSCODE_WRAPPER}" --version >/dev/null
268
+ "${CLAUDE_VSCODE_WRAPPER}" \
269
+ "${EXTENSIONS_DIR}/test/resources/native-binary/claude" \
270
+ --version >/dev/null
271
+
272
  # Persist ngrok's default Linux configuration directory.
273
  mkdir -p "${CODER_HOME}/.config"
274
 
 
288
  ln -s "${NGROK_DIR}" "${CODER_HOME}/.config/ngrok"
289
  fi
290
 
 
291
  test -d "${WORKSPACE_LINK}"
292
+ test -f "${WORKSPACE_LINK}/README.md"
293
  test -f "${WORKSPACE_LINK}/pyproject.toml"
294
+ test -f "${WORKSPACE_LINK}/uv.lock"
295
+ test -x "${WORKSPACE_LINK}/.venv/bin/python"
296
+ git -C "${WORKSPACE_DATA}" rev-parse --is-inside-work-tree >/dev/null
297
 
298
+ log "Workspace ready: ${WORKSPACE_LINK}"
299
+ log "Candidate instructions ready: ${WORKSPACE_LINK}/README.md"
300
+ log "uv project ready: ${WORKSPACE_LINK}/pyproject.toml"
301
  log "Python ready: $("${WORKSPACE_LINK}/.venv/bin/python" --version 2>&1)"
302
+ log "Git ready: $(git -C "${WORKSPACE_DATA}" branch --show-current 2>/dev/null || true)"
303
+ log "Claude CLI ready: $("${CLAUDE_SYSTEM_BIN}" --version 2>&1)"
304
  log "Extensions ready: $(printf '%s' "${installed_extensions}" | tr '\n' ' ')"
scripts/claude-vscode-wrapper.sh ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ # Claude Code's VS Code extension invokes a configured process wrapper as:
5
+ # wrapper <bundled-claude-path> <claude-arguments...>
6
+ #
7
+ # The Open VSX linux-x64 package can occasionally contain a native binary that
8
+ # is not runnable on the code-server base image. Drop the extension-injected
9
+ # binary path and launch the separately installed Anthropic CLI instead.
10
+ REAL_CLAUDE="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
11
+
12
+ if [ "$#" -gt 0 ]; then
13
+ case "$1" in
14
+ */resources/native-binary/claude|*/resources/native-binary/claude.real)
15
+ shift
16
+ ;;
17
+ esac
18
+ fi
19
+
20
+ exec "${REAL_CLAUDE}" "$@"
scripts/start-code-server.sh CHANGED
@@ -6,10 +6,10 @@ INIT_BIN="${INIT_BIN:-/usr/local/bin/init-persistent-data}"
6
  DUMB_INIT_BIN="${DUMB_INIT_BIN:-/usr/bin/dumb-init}"
7
  CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
8
 
9
- # Keep the UID-repair behavior used by the official image.
10
  eval "$("${FIXUID_BIN}" -q)"
11
 
12
- # Keep the official image's optional user-renaming behavior.
13
  if [ "${DOCKER_USER-}" ]; then
14
  USER="${DOCKER_USER}"
15
  export USER
@@ -35,15 +35,15 @@ export CODE_SERVER_EXTENSIONS="${CODE_SERVER_EXTENSIONS:-/data/code-server/exten
35
  export UV_CACHE_DIR="${UV_CACHE_DIR:-/data/cache/uv}"
36
  export HF_HOME="${HF_HOME:-/data/cache/huggingface}"
37
  export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-/data/claude}"
 
 
38
 
39
- # Run initialization directly. A broken workspace, virtual environment, or
40
- # extension install now prevents code-server from starting in a partial state.
41
  "${INIT_BIN}"
42
 
43
- # This makes the code-server process and integrated terminals start from the
44
- # requested workspace, in addition to passing it as the folder to open.
45
  cd "${WORKSPACE}"
46
 
 
 
47
  exec "${DUMB_INIT_BIN}" "${CODE_SERVER_BIN}" \
48
  --bind-addr "0.0.0.0:${PORT}" \
49
  --auth password \
@@ -52,4 +52,5 @@ exec "${DUMB_INIT_BIN}" "${CODE_SERVER_BIN}" \
52
  --extensions-dir "${CODE_SERVER_EXTENSIONS}" \
53
  --disable-telemetry \
54
  --disable-update-check \
 
55
  "${WORKSPACE}"
 
6
  DUMB_INIT_BIN="${DUMB_INIT_BIN:-/usr/bin/dumb-init}"
7
  CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
8
 
9
+ # Preserve the UID-repair behavior from the official image.
10
  eval "$("${FIXUID_BIN}" -q)"
11
 
12
+ # Preserve the official image's optional user-renaming behavior.
13
  if [ "${DOCKER_USER-}" ]; then
14
  USER="${DOCKER_USER}"
15
  export USER
 
35
  export UV_CACHE_DIR="${UV_CACHE_DIR:-/data/cache/uv}"
36
  export HF_HOME="${HF_HOME:-/data/cache/huggingface}"
37
  export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-/data/claude}"
38
+ export CLAUDE_SYSTEM_BIN="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
39
+ export CLAUDE_VSCODE_WRAPPER="${CLAUDE_VSCODE_WRAPPER:-/usr/local/bin/claude-vscode-wrapper}"
40
 
 
 
41
  "${INIT_BIN}"
42
 
 
 
43
  cd "${WORKSPACE}"
44
 
45
+ # --ignore-last-opened makes the command-line folder authoritative instead of
46
+ # restoring an older /home/coder session from persistent editor state.
47
  exec "${DUMB_INIT_BIN}" "${CODE_SERVER_BIN}" \
48
  --bind-addr "0.0.0.0:${PORT}" \
49
  --auth password \
 
52
  --extensions-dir "${CODE_SERVER_EXTENSIONS}" \
53
  --disable-telemetry \
54
  --disable-update-check \
55
+ --ignore-last-opened \
56
  "${WORKSPACE}"
workspace.gitignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .coverage
8
+ htmlcov/
9
+ .env
10
+ .env.*
11
+ .claude/
12
+ .claude.json
13
+ .DS_Store
workspace/README.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Coding Interview
2
+
3
+ Use the password supplied by your interviewer. Keep your work in the repository already open in the editor:
4
+
5
+ ```text
6
+ /home/coder/workspace
7
+ ```
8
+
9
+ ## Python environment
10
+
11
+ Python 3.12 is ready, and this project uses **uv** for dependency and environment management:
12
+
13
+ - `pyproject.toml` lists the direct dependencies available for the exercise.
14
+ - `uv.lock` records the exact resolved dependency versions.
15
+ - `.venv` contains the active virtual environment.
16
+
17
+ Useful checks:
18
+
19
+ ```bash
20
+ pwd
21
+ python --version
22
+ uv --version
23
+ uv tree
24
+ git status
25
+ ```
26
+
27
+ Add a dependency with:
28
+
29
+ ```bash
30
+ uv add <package>
31
+ ```
32
+
33
+ After editing `pyproject.toml` manually, synchronize the environment with:
34
+
35
+ ```bash
36
+ uv sync
37
+ ```
38
+
39
+ ## Run and preview a FastAPI app
40
+
41
+ Assuming the FastAPI application is exposed as `app` in `main.py`, start it in a terminal:
42
+
43
+ ```bash
44
+ uv run uvicorn main:app --host 127.0.0.1 --port 8000 --reload
45
+ ```
46
+
47
+ The first choice is to view Swagger UI inside the editor:
48
+
49
+ 1. Open the Command Palette with **Ctrl/Cmd+Shift+P**.
50
+ 2. Run **Browser: Open Integrated Browser**. In some editor versions, the command may be named **Simple Browser: Show**.
51
+ 3. Open:
52
+
53
+ ```text
54
+ http://127.0.0.1:8000/docs
55
+ ```
56
+
57
+ ReDoc is available at:
58
+
59
+ ```text
60
+ http://127.0.0.1:8000/redoc
61
+ ```
62
+
63
+ If the integrated browser does not open the local address, open the current code-server URL in a normal browser tab and append:
64
+
65
+ ```text
66
+ /proxy/8000/docs
67
+ ```
68
+
69
+ ## Optional: expose the API with ngrok
70
+
71
+ Use ngrok only when a service outside the editor must reach your API, such as a webhook provider or an external API client.
72
+
73
+ Keep Uvicorn running, open a second terminal, and run:
74
+
75
+ ```bash
76
+ ngrok http 8000
77
+ ```
78
+
79
+ ngrok prints a public HTTPS URL. Append `/docs` to that URL to open Swagger UI, for example:
80
+
81
+ ```text
82
+ https://<generated-address>.ngrok.app/docs
83
+ ```
84
+
85
+ If ngrok asks for authentication and an authtoken was supplied for the interview, configure it once and retry:
86
+
87
+ ```bash
88
+ ngrok config add-authtoken <token>
89
+ ngrok http 8000
90
+ ```
91
+
92
+ The generated URL is publicly reachable. Do not expose credentials, environment variables, or sensitive data, and stop the tunnel with **Ctrl+C** when it is no longer needed.
93
+
94
+ ## Source control and tools
95
+
96
+ The workspace is already a Git repository on the `main` branch. Make commits at useful milestones and do not commit credentials or access tokens.
97
+
98
+ Claude Code is available when the interview task calls for it.