Skip to content

Commit 7d46251

Browse files
committed
feat(auth): add custom credential file support and workspace sessions
- Accept --auth-with /path/to/creds.json for flexible credential switching - Mount XDG-compliant ~/.config/deva and ~/.cache/deva for session tracking - Update Copilot API to feature/responses-api branch (GPT-5-Codex support) - Add versions-up command to upgrade images to latest npm versions - Replace bump-versions with version-report.sh for changelog-aware updates
1 parent 2d4fed8 commit 7d46251

5 files changed

Lines changed: 290 additions & 18 deletions

File tree

DEV-LOGS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
- Minimal markdown markers, no unnecessary formatting, minimal emojis.
1414
- Reference issue numbers in the format `#<issue-number>` for easy linking.
1515

16+
17+
# [2025-10-26] Dev Log: Custom credential files via --auth-with
18+
- Why: Users have multiple credential files, needed direct path support beyond predefined auth methods
19+
- What: `--auth-with /path/to/creds.json` now works, auto-backup existing credentials, workspace session tracking in `~/.config/deva/sessions/*.json`
20+
- Result: Flexible credential switching, backward compatible with predefined methods (claude/api-key/bedrock/etc)
21+
1622
# [2025-09-30] Dev Log: Persistent container reuse with tmux-style UX (#108)
1723
- Why: Per-invocation containers were slow, stateless, and clobbered each other; we wanted tmux-like persistence.
1824
- What:

Dockerfile

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
3030
openssh-client rsync \
3131
shellcheck bat fd-find silversearcher-ag \
3232
vim \
33-
procps psmisc zsh && \
34-
add-apt-repository ppa:deadsnakes/ppa && \
35-
apt-get update && \
36-
apt-get install -y --no-install-recommends \
37-
python3.12 python3.12-dev python3.12-venv python3-pip pipx && \
38-
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
39-
update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1 && \
40-
locale-gen en_US.UTF-8
33+
procps psmisc zsh
4134

42-
# Initialize Git LFS so it's usable for all users
4335
RUN git lfs install --system
4436

4537
# Install language runtimes in parallel-friendly layers
@@ -56,11 +48,11 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
5648
RUN curl -fsSL https://bun.sh/install | bash && \
5749
ln -s /root/.bun/bin/bun /usr/local/bin/bun
5850

59-
# Install Copilot API branch with GPT-5 Codex responses support (PR #119 from caozhiyuan fork)
60-
# Pinned to specific commit for reproducibility and security
51+
# Install Copilot API branch with GPT-5 Codex responses support (caozhiyuan fork)
52+
# feature/responses-api adds: GPT-5-Codex support, model reasoning, token caching, enhanced streaming
6153
ARG COPILOT_API_REPO=https://github.com/caozhiyuan/copilot-api.git
62-
ARG COPILOT_API_BRANCH=feature/gpt-5-codex
63-
ARG COPILOT_API_COMMIT=HEAD
54+
ARG COPILOT_API_BRANCH=feature/responses-api
55+
ARG COPILOT_API_COMMIT=83cdfde17d7d3be36bd2493cc7592ff13be4928d
6456

6557
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
6658
npm install -g npm@latest pnpm && \

Makefile

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,15 @@ buildx-multi-local:
103103
-t $(MAIN_IMAGE) .
104104
@echo "✅ Multi-arch build completed locally: $(MAIN_IMAGE)"
105105

106-
.PHONY: bump-versions
107-
bump-versions:
108-
@./scripts/bump-versions.sh
106+
.PHONY: versions-up
107+
versions-up:
108+
@echo "🔄 Upgrading to latest versions from npm..."
109+
@echo "Claude Code: $(CLAUDE_CODE_VERSION)"
110+
@echo "Codex: $(CODEX_VERSION)"
111+
@$(MAKE) build-main CLAUDE_CODE_VERSION=$(CLAUDE_CODE_VERSION) CODEX_VERSION=$(CODEX_VERSION)
112+
@echo "🔨 Rebuilding Rust image..."
113+
@docker build -f $(RUST_DOCKERFILE) --build-arg BASE_IMAGE=$(MAIN_IMAGE) -t $(RUST_IMAGE) .
114+
@echo "✅ All images upgraded to latest versions"
109115

110116
.PHONY: versions
111117
versions:
@@ -241,8 +247,8 @@ help:
241247
@echo " buildx Build with buildx"
242248
@echo " buildx-multi Build multi-arch and push"
243249
@echo " buildx-multi-rust Build multi-arch Rust and push"
244-
@echo " versions Show version status (current/built)"
245-
@echo " bump-versions Pin Makefile to latest npm versions"
250+
@echo " versions Compare built vs latest versions with changelogs"
251+
@echo " versions-up Upgrade both images to latest npm versions"
246252
@echo " test Test main image"
247253
@echo " test-rust Test Rust image"
248254
@echo " shell Open shell in container"

deva.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,6 +2057,18 @@ if [ -n "${AUTH_METHOD:-}" ]; then
20572057
fi
20582058
fi
20592059
2060+
# Set statusline log paths via env vars (XDG-compliant)
2061+
DOCKER_ARGS+=("-e" "CLAUDE_DATA_DIR=/home/deva/.config/deva/claude")
2062+
DOCKER_ARGS+=("-e" "CLAUDE_CACHE_DIR=/home/deva/.cache/deva/claude/sessions")
2063+
2064+
# Mount deva config and cache directories for statusline usage tracking
2065+
if [ -d "$HOME/.config/deva" ]; then
2066+
DOCKER_ARGS+=("-v" "$HOME/.config/deva:/home/deva/.config/deva")
2067+
fi
2068+
if [ -d "$HOME/.cache/deva" ]; then
2069+
DOCKER_ARGS+=("-v" "$HOME/.cache/deva:/home/deva/.cache/deva")
2070+
fi
2071+
20602072
# Mount project-local .claude directory if exists
20612073
append_user_envs
20622074
if [ -d "$(pwd)/.claude" ]; then

scripts/version-report.sh

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
CLAUDE_CHANGELOG_URL=${CLAUDE_CHANGELOG_URL:-https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md}
5+
CODEX_RELEASES_API=${CODEX_RELEASES_API:-https://api.github.com/repos/openai/codex/releases/latest}
6+
7+
section() {
8+
local title=$1
9+
echo "$title"
10+
printf '%*s\n' "${#title}" '' | tr ' ' '-'
11+
}
12+
13+
indent() { sed 's/^/ /'; }
14+
15+
normalize_version() {
16+
local v=$1
17+
v=${v#v}
18+
echo "$v"
19+
}
20+
21+
get_latest_npm_version() {
22+
local pkg=$1
23+
npm view "$pkg" version 2>/dev/null || echo ""
24+
}
25+
26+
get_image_version() {
27+
local image=$1 label=$2
28+
docker inspect "$image" 2>/dev/null | \
29+
jq -r --arg k "$label" '.[0].Config.Labels[$k] // ""' 2>/dev/null || echo ""
30+
}
31+
32+
compare_versions() {
33+
local current=$1 latest=$2 name=$3
34+
current=$(normalize_version "$current")
35+
latest=$(normalize_version "$latest")
36+
37+
if [[ -z $current ]]; then
38+
echo " $name: - -> v$latest (not built)"
39+
return 1
40+
elif [[ $current == "$latest" ]]; then
41+
echo " $name: v$current (up-to-date)"
42+
return 0
43+
else
44+
echo " $name: v$current -> v$latest (upgrade available)"
45+
return 1
46+
fi
47+
}
48+
49+
fetch_changelog_between() {
50+
local current=$1 latest=$2
51+
current=$(normalize_version "$current")
52+
latest=$(normalize_version "$latest")
53+
54+
if [[ -z $current ]] || [[ $current == "$latest" ]]; then
55+
return
56+
fi
57+
58+
local data
59+
if ! data=$(curl -fsSL --max-time 10 --retry 2 "$CLAUDE_CHANGELOG_URL" 2>/dev/null); then
60+
return
61+
fi
62+
63+
python3 -c 'import re, sys
64+
65+
def parse_version(v):
66+
"""Parse version string to tuple of ints for comparison"""
67+
parts = re.findall(r"\d+", v)
68+
return tuple(int(p) for p in parts) if parts else (0,)
69+
70+
current = sys.argv[1]
71+
latest = sys.argv[2]
72+
text = sys.stdin.read()
73+
74+
try:
75+
cur_v = parse_version(current)
76+
lat_v = parse_version(latest)
77+
except:
78+
sys.exit(0)
79+
80+
# Match entire sections: ## heading followed by content until next ## or end
81+
sections = re.split(r"(?=^## )", text, flags=re.M)
82+
changes = []
83+
84+
for section in sections:
85+
if not section.strip():
86+
continue
87+
88+
# Extract version from heading
89+
match = re.match(r"^##\s+.*?(\d+\.\d+\.\d+)", section, re.M)
90+
if not match:
91+
continue
92+
93+
try:
94+
v = parse_version(match.group(1))
95+
if cur_v < v <= lat_v:
96+
# Clean up section: remove heading marker, limit lines
97+
lines = section.strip().split("\n")
98+
if lines:
99+
# Keep version heading and first 10 content lines
100+
output = [lines[0].replace("## ", "")]
101+
content_lines = [l for l in lines[1:] if l.strip()][:10]
102+
output.extend(content_lines)
103+
changes.append("\n".join(output))
104+
except:
105+
continue
106+
107+
if changes:
108+
for change in reversed(changes[-3:]):
109+
print(change)
110+
print()
111+
' "$current" "$latest" <<< "$data" 2>/dev/null || true
112+
}
113+
114+
fetch_github_releases_between() {
115+
local current=$1 latest=$2
116+
current=$(normalize_version "$current")
117+
latest=$(normalize_version "$latest")
118+
119+
if [[ -z $current ]] || [[ $current == "$latest" ]]; then
120+
return
121+
fi
122+
123+
local json
124+
if ! json=$(curl -fsSL --max-time 10 --retry 2 \
125+
"https://api.github.com/repos/openai/codex/releases" 2>/dev/null); then
126+
return
127+
fi
128+
129+
python3 -c 'import json, sys, re
130+
131+
def parse_version(v):
132+
"""Parse version string to tuple of ints for comparison"""
133+
# Extract pure version numbers, ignore prefixes and suffixes
134+
match = re.search(r"(\d+)\.(\d+)\.(\d+)", v)
135+
if not match:
136+
return (0, 0, 0)
137+
return tuple(int(x) for x in match.groups())
138+
139+
current = sys.argv[1]
140+
latest = sys.argv[2]
141+
releases = json.load(sys.stdin)
142+
143+
try:
144+
cur_v = parse_version(current)
145+
lat_v = parse_version(latest)
146+
except:
147+
sys.exit(0)
148+
149+
changes = []
150+
seen_versions = set()
151+
152+
for rel in releases:
153+
# Skip prereleases (alpha, beta, rc)
154+
if rel.get("prerelease", False):
155+
continue
156+
157+
tag = rel.get("tag_name") or ""
158+
if not tag:
159+
continue
160+
161+
try:
162+
v = parse_version(tag)
163+
164+
# Skip duplicates and check version range
165+
if v in seen_versions or not (cur_v < v <= lat_v):
166+
continue
167+
168+
seen_versions.add(v)
169+
170+
# Extract clean version string
171+
ver_match = re.search(r"(\d+\.\d+\.\d+)", tag)
172+
ver = ver_match.group(1) if ver_match else tag
173+
174+
name = rel.get("name") or ver
175+
body = (rel.get("body") or "").strip()
176+
# Convert \r\n to \n
177+
body = body.replace("\r\n", "\n")
178+
179+
changes.append((v, ver, name, body))
180+
except Exception:
181+
continue
182+
183+
# Sort by version tuple (newest first) and show up to 3
184+
for v, ver, name, body in sorted(changes, key=lambda x: x[0], reverse=True)[:3]:
185+
print(f"{ver}")
186+
if body:
187+
lines = [l.rstrip() for l in body.split("\n") if l.strip()][:15]
188+
for line in lines:
189+
print(f" {line}")
190+
print()
191+
' "$current" "$latest" <<< "$json" 2>/dev/null || true
192+
}
193+
194+
main() {
195+
local image=${MAIN_IMAGE:-ghcr.io/thevibeworks/deva:latest}
196+
197+
section "Version Status"
198+
echo "Image: $image"
199+
200+
# Get current versions from built image
201+
local cur_claude cur_codex image_exists=true
202+
if docker inspect "$image" >/dev/null 2>&1; then
203+
cur_claude=$(get_image_version "$image" "org.opencontainers.image.claude_code_version")
204+
cur_codex=$(get_image_version "$image" "org.opencontainers.image.codex_version")
205+
else
206+
echo " (image not built locally)"
207+
cur_claude=""
208+
cur_codex=""
209+
image_exists=false
210+
fi
211+
212+
# Get latest versions
213+
local lat_claude=${CLAUDE_CODE_VERSION:-$(get_latest_npm_version "@anthropic-ai/claude-code")}
214+
local lat_codex=${CODEX_VERSION:-$(get_latest_npm_version "@openai/codex")}
215+
216+
local needs_update=0
217+
compare_versions "$cur_claude" "$lat_claude" "Claude Code" || needs_update=1
218+
compare_versions "$cur_codex" "$lat_codex" "Codex" || needs_update=1
219+
echo
220+
221+
if [[ $needs_update -eq 1 ]]; then
222+
if [[ -n $cur_claude ]] && [[ $(normalize_version "$cur_claude") != $(normalize_version "$lat_claude") ]]; then
223+
section "Claude Code Changes"
224+
local changes
225+
changes=$(fetch_changelog_between "$cur_claude" "$lat_claude")
226+
if [[ -n $changes ]]; then
227+
echo "$changes" | indent
228+
else
229+
echo " (changelog unavailable)"
230+
fi
231+
echo
232+
fi
233+
234+
if [[ -n $cur_codex ]] && [[ $(normalize_version "$cur_codex") != $(normalize_version "$lat_codex") ]]; then
235+
section "Codex Changes"
236+
local changes
237+
changes=$(fetch_github_releases_between "$cur_codex" "$lat_codex")
238+
if [[ -n $changes ]]; then
239+
echo "$changes" | indent
240+
else
241+
echo " (release notes unavailable)"
242+
fi
243+
echo
244+
fi
245+
246+
if [[ $image_exists == true ]]; then
247+
echo "Run 'make versions-up' to upgrade"
248+
else
249+
echo "Run 'make build' to build images"
250+
fi
251+
else
252+
echo "All versions up-to-date"
253+
fi
254+
}
255+
256+
main

0 commit comments

Comments
 (0)