codefreedom 0.0.3__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codefreedom-0.0.3/src/codefreedom.egg-info → codefreedom-0.0.4}/PKG-INFO +3 -3
- {codefreedom-0.0.3 → codefreedom-0.0.4}/pyproject.toml +3 -3
- codefreedom-0.0.4/src/codefreedom/cli/chrome.py +366 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/cli/main.py +82 -11
- codefreedom-0.0.4/src/codefreedom/examples/profiles/chrome.schema.json +55 -0
- codefreedom-0.0.3/src/codefreedom/examples/profiles/claude-code-profiles.json → codefreedom-0.0.4/src/codefreedom/examples/profiles/claude-code.json +1 -1
- codefreedom-0.0.4/src/codefreedom/examples/profiles/tools-chrome.json +22 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4/src/codefreedom.egg-info}/PKG-INFO +3 -3
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom.egg-info/SOURCES.txt +5 -2
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom.egg-info/requires.txt +2 -2
- {codefreedom-0.0.3 → codefreedom-0.0.4}/tests/test_init.py +36 -5
- {codefreedom-0.0.3 → codefreedom-0.0.4}/LICENSE +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/NOTICE +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/README.md +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/setup.cfg +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/__init__.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/__main__.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/cli/__init__.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/cli/claude.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/cli/proxy.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/env_loader.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/.env.example +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/.env.secrets.example +0 -0
- /codefreedom-0.0.3/src/codefreedom/examples/profiles/claude-code-profiles.schema.json → /codefreedom-0.0.4/src/codefreedom/examples/profiles/claude-code.schema.json +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/config.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/docker-compose.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/anthropic-compatible.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/azure-foundry.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/deepseek.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/local.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/nvidia.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/openai-compatible.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/opencode-zen.yaml +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/launcher.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/profiles.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom.egg-info/dependency_links.txt +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom.egg-info/entry_points.txt +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom.egg-info/top_level.txt +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/tests/test_env_loader.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/tests/test_profiles.py +0 -0
- {codefreedom-0.0.3 → codefreedom-0.0.4}/tests/test_proxy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codefreedom
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Single wrapper for all code agents — simple LLM routing, sandboxing, profile management, and isolation. All config in ~/.codefreedom.
|
|
5
5
|
Author-email: Nilay Parikh <nilay.parikh@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -27,14 +27,14 @@ Requires-Dist: PyYAML>=6.0
|
|
|
27
27
|
Requires-Dist: types-PyYAML
|
|
28
28
|
Provides-Extra: litellm
|
|
29
29
|
Requires-Dist: litellm[proxy]>=1.50; extra == "litellm"
|
|
30
|
-
Requires-Dist: prometheus-client>=0.
|
|
30
|
+
Requires-Dist: prometheus-client>=0.25.0; extra == "litellm"
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
33
33
|
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
34
34
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
35
35
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
36
36
|
Provides-Extra: docs
|
|
37
|
-
Requires-Dist: mkdocs-material[imaging]>=9.6; extra == "docs"
|
|
37
|
+
Requires-Dist: mkdocs-material[imaging]>=9.7.6; extra == "docs"
|
|
38
38
|
Requires-Dist: mkdocs-mermaid2-plugin>=1.2; extra == "docs"
|
|
39
39
|
Provides-Extra: all
|
|
40
40
|
Requires-Dist: codefreedom[dev,docs,litellm]; extra == "all"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codefreedom"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.4"
|
|
8
8
|
description = "Single wrapper for all code agents — simple LLM routing, sandboxing, profile management, and isolation. All config in ~/.codefreedom."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -33,9 +33,9 @@ classifiers = [
|
|
|
33
33
|
# Not needed for Docker Compose mode.
|
|
34
34
|
# dev — Development extras (type checking, linting)
|
|
35
35
|
[project.optional-dependencies]
|
|
36
|
-
litellm = ["litellm[proxy]>=1.50", "prometheus-client>=0.
|
|
36
|
+
litellm = ["litellm[proxy]>=1.50", "prometheus-client>=0.25.0"]
|
|
37
37
|
dev = ["mypy>=1.0", "ruff>=0.1", "pytest>=7.0", "types-PyYAML"]
|
|
38
|
-
docs = ["mkdocs-material[imaging]>=9.6", "mkdocs-mermaid2-plugin>=1.2"]
|
|
38
|
+
docs = ["mkdocs-material[imaging]>=9.7.6", "mkdocs-mermaid2-plugin>=1.2"]
|
|
39
39
|
all = ["codefreedom[litellm,dev,docs]"]
|
|
40
40
|
|
|
41
41
|
[project.scripts]
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Chrome browser tool — run Chrome in Docker with Xvfb for undetectable browsing.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
codefreedom tools chrome start Start Chrome container (Xvfb + Chromium)
|
|
5
|
+
codefreedom tools chrome stop Stop and remove Chrome container
|
|
6
|
+
codefreedom tools chrome status Show Chrome container status
|
|
7
|
+
codefreedom tools chrome url Show CDP debug URL
|
|
8
|
+
|
|
9
|
+
Settings are loaded from ~/.codefreedom/profiles/chrome.json (generated by
|
|
10
|
+
'codefreedom --init'). See src/codefreedom/examples/profiles/tools-chrome.json
|
|
11
|
+
for the template with all available options.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import subprocess
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from codefreedom.env_loader import eprint
|
|
22
|
+
|
|
23
|
+
# ── Defaults ──────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
_DEFAULT_IMAGE = "codefreedom:Chrome-local"
|
|
26
|
+
_DEFAULT_CONTAINER_NAME = "codefreedom-tools-chrome"
|
|
27
|
+
_DEFAULT_PORT = 9222
|
|
28
|
+
_DEFAULT_DATA_DIR = "~/.codefreedom/sandbox/tools/chrome"
|
|
29
|
+
|
|
30
|
+
_CODEFREEDOM_DIR = Path.home() / ".codefreedom"
|
|
31
|
+
_PROFILE_PATH = _CODEFREEDOM_DIR / "profiles" / "chrome.json"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ── Profile loader ────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_profile() -> dict:
|
|
38
|
+
"""Load chrome tool profile from ~/.codefreedom/profiles/chrome.json.
|
|
39
|
+
|
|
40
|
+
Returns a flat dict with keys: image, container_name, port, data_dir, env.
|
|
41
|
+
Any missing key falls back to the hardcoded default above.
|
|
42
|
+
"""
|
|
43
|
+
settings: dict = {
|
|
44
|
+
"image": _DEFAULT_IMAGE,
|
|
45
|
+
"container_name": _DEFAULT_CONTAINER_NAME,
|
|
46
|
+
"port": _DEFAULT_PORT,
|
|
47
|
+
"data_dir": _DEFAULT_DATA_DIR,
|
|
48
|
+
"env": {},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if not _PROFILE_PATH.exists():
|
|
52
|
+
return settings
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
with open(_PROFILE_PATH, encoding="utf-8") as f:
|
|
56
|
+
raw = json.load(f)
|
|
57
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
58
|
+
eprint(f"[CHROME] Warning: failed to read {_PROFILE_PATH}: {exc}")
|
|
59
|
+
return settings
|
|
60
|
+
|
|
61
|
+
chrome_cfg = raw.get("chrome", {})
|
|
62
|
+
if not isinstance(chrome_cfg, dict):
|
|
63
|
+
return settings
|
|
64
|
+
|
|
65
|
+
# Merge — profile values override defaults
|
|
66
|
+
if isinstance(chrome_cfg.get("image"), str) and chrome_cfg["image"]:
|
|
67
|
+
settings["image"] = chrome_cfg["image"]
|
|
68
|
+
if (
|
|
69
|
+
isinstance(chrome_cfg.get("container_name"), str)
|
|
70
|
+
and chrome_cfg["container_name"]
|
|
71
|
+
):
|
|
72
|
+
settings["container_name"] = chrome_cfg["container_name"]
|
|
73
|
+
if isinstance(chrome_cfg.get("port"), int) and chrome_cfg["port"] > 0:
|
|
74
|
+
settings["port"] = chrome_cfg["port"]
|
|
75
|
+
if isinstance(chrome_cfg.get("data_dir"), str) and chrome_cfg["data_dir"]:
|
|
76
|
+
settings["data_dir"] = chrome_cfg["data_dir"]
|
|
77
|
+
if isinstance(chrome_cfg.get("env"), dict):
|
|
78
|
+
settings["env"] = chrome_cfg["env"]
|
|
79
|
+
|
|
80
|
+
return settings
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _resolve_data_dir(data_dir: str) -> Path:
|
|
87
|
+
"""Resolve ~ to home directory and create the path."""
|
|
88
|
+
path = Path(data_dir).expanduser()
|
|
89
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
return path
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _container_exists(name: str) -> bool:
|
|
94
|
+
"""Check if a container exists (running or stopped)."""
|
|
95
|
+
try:
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
[
|
|
98
|
+
"docker",
|
|
99
|
+
"ps",
|
|
100
|
+
"-a",
|
|
101
|
+
"--filter",
|
|
102
|
+
f"name={name}",
|
|
103
|
+
"--format",
|
|
104
|
+
"{{.Names}}",
|
|
105
|
+
],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
timeout=10,
|
|
109
|
+
check=False,
|
|
110
|
+
)
|
|
111
|
+
return name in result.stdout.strip().split("\n")
|
|
112
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _container_is_running(name: str) -> bool:
|
|
117
|
+
"""Check if a container is currently running."""
|
|
118
|
+
try:
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
["docker", "ps", "--filter", f"name={name}", "--format", "{{.Names}}"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
text=True,
|
|
123
|
+
timeout=10,
|
|
124
|
+
check=False,
|
|
125
|
+
)
|
|
126
|
+
return name in result.stdout.strip().split("\n")
|
|
127
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _ensure_image(image: str) -> bool:
|
|
132
|
+
"""Ensure the Docker image is available locally; pull if missing. Returns True on success."""
|
|
133
|
+
_inspect = subprocess.run(
|
|
134
|
+
["docker", "image", "inspect", image],
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True,
|
|
137
|
+
timeout=10,
|
|
138
|
+
check=False,
|
|
139
|
+
)
|
|
140
|
+
if _inspect.returncode == 0:
|
|
141
|
+
eprint(f"[CHROME] Using cached image '{image}'")
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
eprint(f"[CHROME] Image '{image}' not found locally, pulling...")
|
|
145
|
+
pull = subprocess.run(
|
|
146
|
+
["docker", "pull", image],
|
|
147
|
+
capture_output=True,
|
|
148
|
+
text=True,
|
|
149
|
+
timeout=120,
|
|
150
|
+
check=False,
|
|
151
|
+
)
|
|
152
|
+
if pull.returncode == 0:
|
|
153
|
+
eprint(" [OK] Image pulled.")
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
eprint(f"[ERROR] Failed to pull image '{image}'.")
|
|
157
|
+
if pull.stderr:
|
|
158
|
+
eprint(f" {pull.stderr.strip()}")
|
|
159
|
+
eprint("")
|
|
160
|
+
eprint(" Tips:")
|
|
161
|
+
eprint(
|
|
162
|
+
" • Build locally: docker build -t codefreedom:Chrome-local -f docker/browser/Dockerfile.Chrome docker/browser/"
|
|
163
|
+
)
|
|
164
|
+
eprint(" • Set 'image' in ~/.codefreedom/profiles/chrome.json to your local tag")
|
|
165
|
+
eprint(" • Wait for CI to publish the image to ghcr.io")
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ── Actions ────────────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def start(settings: dict) -> int:
|
|
173
|
+
"""Start the Chrome browser container. Returns exit code."""
|
|
174
|
+
image = settings["image"]
|
|
175
|
+
container_name = settings["container_name"]
|
|
176
|
+
port = settings["port"]
|
|
177
|
+
data_dir = settings["data_dir"]
|
|
178
|
+
env_vars = settings.get("env", {})
|
|
179
|
+
|
|
180
|
+
if _container_is_running(container_name):
|
|
181
|
+
eprint(f"[CHROME] Container '{container_name}' is already running.")
|
|
182
|
+
return 0
|
|
183
|
+
|
|
184
|
+
# Check if Docker is available
|
|
185
|
+
if (
|
|
186
|
+
not subprocess.run(
|
|
187
|
+
["docker", "--version"], capture_output=True, timeout=5, check=False
|
|
188
|
+
).returncode
|
|
189
|
+
== 0
|
|
190
|
+
):
|
|
191
|
+
eprint("[ERROR] Docker not found. Install Docker and try again.")
|
|
192
|
+
return 1
|
|
193
|
+
|
|
194
|
+
# Resolve & create data directory
|
|
195
|
+
resolved_data = _resolve_data_dir(data_dir)
|
|
196
|
+
eprint(f"[CHROME] Using data dir: {resolved_data}")
|
|
197
|
+
|
|
198
|
+
# Remove existing container if stopped
|
|
199
|
+
if _container_exists(container_name):
|
|
200
|
+
eprint(f"[CHROME] Removing existing container '{container_name}'...")
|
|
201
|
+
subprocess.run(
|
|
202
|
+
["docker", "rm", "-f", container_name],
|
|
203
|
+
capture_output=True,
|
|
204
|
+
timeout=15,
|
|
205
|
+
check=False,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Ensure image is available
|
|
209
|
+
if not _ensure_image(image):
|
|
210
|
+
return 1
|
|
211
|
+
|
|
212
|
+
# Build environment flags
|
|
213
|
+
env_flags: list[str] = []
|
|
214
|
+
for key, val in env_vars.items():
|
|
215
|
+
if val:
|
|
216
|
+
env_flags.extend(["-e", f"{key}={val}"])
|
|
217
|
+
# Ensure DISPLAY is set
|
|
218
|
+
if "DISPLAY" not in env_vars:
|
|
219
|
+
env_flags.extend(["-e", "DISPLAY=:99"])
|
|
220
|
+
|
|
221
|
+
# Start container
|
|
222
|
+
eprint(f"[CHROME] Starting container '{container_name}'...")
|
|
223
|
+
create = subprocess.run(
|
|
224
|
+
[
|
|
225
|
+
"docker",
|
|
226
|
+
"run",
|
|
227
|
+
"-d",
|
|
228
|
+
"--name",
|
|
229
|
+
container_name,
|
|
230
|
+
"--network",
|
|
231
|
+
"host",
|
|
232
|
+
"--ipc=host",
|
|
233
|
+
"--cap-add=SYS_ADMIN",
|
|
234
|
+
"--init",
|
|
235
|
+
"--restart",
|
|
236
|
+
"unless-stopped",
|
|
237
|
+
"-v",
|
|
238
|
+
f"{resolved_data}:/data/chrome",
|
|
239
|
+
*env_flags,
|
|
240
|
+
image,
|
|
241
|
+
],
|
|
242
|
+
capture_output=True,
|
|
243
|
+
text=True,
|
|
244
|
+
timeout=60,
|
|
245
|
+
check=False,
|
|
246
|
+
)
|
|
247
|
+
if create.returncode != 0:
|
|
248
|
+
eprint("[ERROR] Failed to start Chrome container.")
|
|
249
|
+
if create.stderr:
|
|
250
|
+
eprint(f" {create.stderr.strip()}")
|
|
251
|
+
return 1
|
|
252
|
+
|
|
253
|
+
eprint(" [OK] Container started.")
|
|
254
|
+
eprint(f" CDP debug URL: http://127.0.0.1:{port}")
|
|
255
|
+
eprint(
|
|
256
|
+
f" DevTools: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:{port}"
|
|
257
|
+
)
|
|
258
|
+
return 0
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def stop(settings: dict) -> int:
|
|
262
|
+
"""Stop and remove the Chrome container. Returns exit code."""
|
|
263
|
+
container_name = settings["container_name"]
|
|
264
|
+
|
|
265
|
+
if not _container_exists(container_name):
|
|
266
|
+
eprint(f"[CHROME] Container '{container_name}' does not exist.")
|
|
267
|
+
return 0
|
|
268
|
+
|
|
269
|
+
eprint(f"[CHROME] Stopping container '{container_name}'...")
|
|
270
|
+
subprocess.run(
|
|
271
|
+
["docker", "stop", container_name],
|
|
272
|
+
capture_output=True,
|
|
273
|
+
timeout=30,
|
|
274
|
+
check=False,
|
|
275
|
+
)
|
|
276
|
+
subprocess.run(
|
|
277
|
+
["docker", "rm", container_name],
|
|
278
|
+
capture_output=True,
|
|
279
|
+
timeout=15,
|
|
280
|
+
check=False,
|
|
281
|
+
)
|
|
282
|
+
eprint(" [OK] Container stopped and removed.")
|
|
283
|
+
return 0
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def status(settings: dict) -> int:
|
|
287
|
+
"""Show Chrome container status. Returns exit code."""
|
|
288
|
+
container_name = settings["container_name"]
|
|
289
|
+
port = settings["port"]
|
|
290
|
+
|
|
291
|
+
if not _container_exists(container_name):
|
|
292
|
+
eprint("[CHROME] No Chrome container found.")
|
|
293
|
+
eprint(" Start one with: codefreedom tools chrome start")
|
|
294
|
+
return 0
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
result = subprocess.run(
|
|
298
|
+
[
|
|
299
|
+
"docker",
|
|
300
|
+
"ps",
|
|
301
|
+
"-a",
|
|
302
|
+
"--filter",
|
|
303
|
+
f"name={container_name}",
|
|
304
|
+
"--format",
|
|
305
|
+
"{{.Names}}\t{{.Status}}\t{{.CreatedAt}}",
|
|
306
|
+
],
|
|
307
|
+
capture_output=True,
|
|
308
|
+
text=True,
|
|
309
|
+
timeout=10,
|
|
310
|
+
check=False,
|
|
311
|
+
)
|
|
312
|
+
for line in result.stdout.strip().split("\n"):
|
|
313
|
+
if line:
|
|
314
|
+
name, status_line, created = line.split("\t", 2)
|
|
315
|
+
marker = "[RUNNING]" if "Up " in status_line else "[STOPPED]"
|
|
316
|
+
eprint(f"[CHROME] {marker} {name}")
|
|
317
|
+
eprint(f" Status: {status_line}")
|
|
318
|
+
eprint(f" Created: {created}")
|
|
319
|
+
|
|
320
|
+
if _container_is_running(container_name):
|
|
321
|
+
eprint(f" CDP URL: http://127.0.0.1:{port}")
|
|
322
|
+
eprint(
|
|
323
|
+
f" DevTools: devtools://devtools/bundled/inspector.html?ws=127.0.0.1:{port}"
|
|
324
|
+
)
|
|
325
|
+
except (subprocess.SubprocessError, FileNotFoundError) as e:
|
|
326
|
+
eprint(f"[ERROR] Failed to get container status: {e}")
|
|
327
|
+
return 1
|
|
328
|
+
return 0
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def url(settings: dict) -> int:
|
|
332
|
+
"""Print the CDP debug URL. Returns exit code."""
|
|
333
|
+
container_name = settings["container_name"]
|
|
334
|
+
port = settings["port"]
|
|
335
|
+
|
|
336
|
+
if not _container_is_running(container_name):
|
|
337
|
+
eprint("[CHROME] Chrome container is not running.")
|
|
338
|
+
eprint(" Start it with: codefreedom tools chrome start")
|
|
339
|
+
return 1
|
|
340
|
+
|
|
341
|
+
print(f"http://127.0.0.1:{port}")
|
|
342
|
+
return 0
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def run(args: argparse.Namespace) -> int:
|
|
346
|
+
"""Execute the chrome tool subcommand. Returns exit code."""
|
|
347
|
+
settings = _load_profile()
|
|
348
|
+
|
|
349
|
+
# CLI --port flag overrides profile and defaults
|
|
350
|
+
if hasattr(args, "port") and args.port:
|
|
351
|
+
settings["port"] = args.port
|
|
352
|
+
|
|
353
|
+
action = args.action or "status"
|
|
354
|
+
|
|
355
|
+
if action == "start":
|
|
356
|
+
return start(settings)
|
|
357
|
+
elif action == "stop":
|
|
358
|
+
return stop(settings)
|
|
359
|
+
elif action == "status":
|
|
360
|
+
return status(settings)
|
|
361
|
+
elif action == "url":
|
|
362
|
+
return url(settings)
|
|
363
|
+
else:
|
|
364
|
+
eprint(f"[ERROR] Unknown action: {action}")
|
|
365
|
+
eprint(" Valid actions: start, stop, status, url")
|
|
366
|
+
return 1
|
|
@@ -39,7 +39,7 @@ def _init_codefreedom(
|
|
|
39
39
|
|
|
40
40
|
profiles_dst_dir = cf_dir / "profiles"
|
|
41
41
|
profiles_dst = profiles_dst_dir / "claude-code.json"
|
|
42
|
-
schema_dst = profiles_dst_dir / "claude-code
|
|
42
|
+
schema_dst = profiles_dst_dir / "claude-code.schema.json"
|
|
43
43
|
proxy_dst = cf_dir / "proxy"
|
|
44
44
|
|
|
45
45
|
created_any = False
|
|
@@ -50,28 +50,56 @@ def _init_codefreedom(
|
|
|
50
50
|
print(f"[init] Profiles already exist: {profiles_dst}")
|
|
51
51
|
print(" Use --init --force to overwrite.")
|
|
52
52
|
skipped_any = True
|
|
53
|
-
elif (profiles_src / "claude-code
|
|
53
|
+
elif (profiles_src / "claude-code.json").exists():
|
|
54
54
|
profiles_dst_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
-
shutil.copy2(profiles_src / "claude-code
|
|
55
|
+
shutil.copy2(profiles_src / "claude-code.json", profiles_dst)
|
|
56
56
|
print(f"[init] [OK] Created {profiles_dst}")
|
|
57
57
|
created_any = True
|
|
58
58
|
else:
|
|
59
59
|
print("[init] [FAIL] Bundled profiles example not found")
|
|
60
60
|
print(" Reinstall the package or file a bug report.")
|
|
61
61
|
|
|
62
|
-
# ──
|
|
62
|
+
# ── Claude Code schema ─────────────────────────────────────────────────
|
|
63
63
|
if not force and schema_dst.exists():
|
|
64
64
|
print(f"[init] Schema already exists: {schema_dst}")
|
|
65
65
|
skipped_any = True
|
|
66
|
-
elif (profiles_src / "claude-code
|
|
66
|
+
elif (profiles_src / "claude-code.schema.json").exists():
|
|
67
67
|
profiles_dst_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
-
shutil.copy2(profiles_src / "claude-code
|
|
68
|
+
shutil.copy2(profiles_src / "claude-code.schema.json", schema_dst)
|
|
69
69
|
print(f"[init] [OK] Created {schema_dst}")
|
|
70
70
|
created_any = True
|
|
71
71
|
else:
|
|
72
72
|
print("[init] [FAIL] Bundled schema example not found")
|
|
73
73
|
print(" Reinstall the package or file a bug report.")
|
|
74
74
|
|
|
75
|
+
# ── Tool profiles (chrome.json, etc.) ──────────────────────────────────
|
|
76
|
+
for tool_src in sorted(profiles_src.glob("tools-*.json")):
|
|
77
|
+
tool_name = tool_src.stem.replace("tools-", "") # e.g. "chrome"
|
|
78
|
+
tool_dst = profiles_dst_dir / f"{tool_name}.json"
|
|
79
|
+
if not force and tool_dst.exists():
|
|
80
|
+
print(f"[init] Tool profile already exists: {tool_dst}")
|
|
81
|
+
skipped_any = True
|
|
82
|
+
else:
|
|
83
|
+
profiles_dst_dir.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
shutil.copy2(tool_src, tool_dst)
|
|
85
|
+
print(f"[init] [OK] Created tool profile: {tool_dst}")
|
|
86
|
+
created_any = True
|
|
87
|
+
|
|
88
|
+
# ── Tool schemas (chrome.schema.json, etc.) ────────────────────────────
|
|
89
|
+
for schema_src in sorted(profiles_src.glob("*.schema.json")):
|
|
90
|
+
schema_name = schema_src.name # e.g. "chrome.schema.json"
|
|
91
|
+
if schema_name == "claude-code.schema.json":
|
|
92
|
+
continue # already copied above
|
|
93
|
+
schema_dst_file = profiles_dst_dir / schema_name
|
|
94
|
+
if not force and schema_dst_file.exists():
|
|
95
|
+
print(f"[init] Schema already exists: {schema_dst_file}")
|
|
96
|
+
skipped_any = True
|
|
97
|
+
else:
|
|
98
|
+
profiles_dst_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
shutil.copy2(schema_src, schema_dst_file)
|
|
100
|
+
print(f"[init] [OK] Created schema: {schema_dst_file}")
|
|
101
|
+
created_any = True
|
|
102
|
+
|
|
75
103
|
# ── Proxy configs ──────────────────────────────────────────────────────
|
|
76
104
|
if not force and proxy_dst.exists():
|
|
77
105
|
print(f"[init] Proxy configs already exist: {proxy_dst}")
|
|
@@ -131,13 +159,11 @@ def _init_codefreedom(
|
|
|
131
159
|
|
|
132
160
|
# .env.secrets is optional
|
|
133
161
|
if secrets_dst.exists():
|
|
134
|
-
print(
|
|
162
|
+
print("[init] .env.secrets already exists (skipping)")
|
|
135
163
|
elif secrets_src.exists():
|
|
136
164
|
cf_dir.mkdir(parents=True, exist_ok=True)
|
|
137
165
|
shutil.copy2(secrets_src, secrets_dst)
|
|
138
|
-
print(
|
|
139
|
-
f"[init] [OK] Created {secrets_dst} (fully commented -- add your API keys)"
|
|
140
|
-
)
|
|
166
|
+
print("[init] [OK] Created .env.secrets (fully commented -- add your API keys)")
|
|
141
167
|
created_any = True
|
|
142
168
|
|
|
143
169
|
if created_any:
|
|
@@ -145,7 +171,9 @@ def _init_codefreedom(
|
|
|
145
171
|
print("[init] CodeFreedom is initialized!")
|
|
146
172
|
print(f" Profiles: {profiles_dst_dir}")
|
|
147
173
|
print(" - claude-code.json")
|
|
148
|
-
print(" - claude-code
|
|
174
|
+
print(" - claude-code.schema.json")
|
|
175
|
+
print(" - chrome.json (tool)")
|
|
176
|
+
print(" - chrome.schema.json (tool)")
|
|
149
177
|
print(f" Proxy: {proxy_dst}")
|
|
150
178
|
print(f" Env: {cf_dir}")
|
|
151
179
|
print(" - .env (fully commented)")
|
|
@@ -231,6 +259,34 @@ def main() -> None:
|
|
|
231
259
|
help="Arguments forwarded to the 'claude' CLI",
|
|
232
260
|
)
|
|
233
261
|
|
|
262
|
+
# ── tools subcommand ──────────────────────────────────────────────────
|
|
263
|
+
tools_parser = subparsers.add_parser(
|
|
264
|
+
"tools",
|
|
265
|
+
help="Manage auxiliary tools (chrome browser, etc.)",
|
|
266
|
+
description="Manage auxiliary tools used by coding agents (Chrome browser with Xvfb, etc.).",
|
|
267
|
+
)
|
|
268
|
+
tools_subparsers = tools_parser.add_subparsers(dest="tool", title="tools")
|
|
269
|
+
|
|
270
|
+
# ── chrome tool ─────────────────────────────────────────────────────
|
|
271
|
+
chrome_parser = tools_subparsers.add_parser(
|
|
272
|
+
"chrome",
|
|
273
|
+
help="Chrome browser with Xvfb for undetectable headed browsing",
|
|
274
|
+
description="Start/stop/manage a Chrome browser container with virtual display (Xvfb) for undetectable web browsing. Coding agents connect via Chrome DevTools Protocol (CDP) at port 9222.",
|
|
275
|
+
)
|
|
276
|
+
chrome_parser.add_argument(
|
|
277
|
+
"action",
|
|
278
|
+
nargs="?",
|
|
279
|
+
default="status",
|
|
280
|
+
choices=["start", "stop", "status", "url"],
|
|
281
|
+
help="Action to perform (default: status)",
|
|
282
|
+
)
|
|
283
|
+
chrome_parser.add_argument(
|
|
284
|
+
"--port",
|
|
285
|
+
type=int,
|
|
286
|
+
default=9222,
|
|
287
|
+
help="CDP debug port (default: 9222)",
|
|
288
|
+
)
|
|
289
|
+
|
|
234
290
|
# ── proxy subcommand ───────────────────────────────────────────────────
|
|
235
291
|
proxy_parser = subparsers.add_parser(
|
|
236
292
|
"proxy",
|
|
@@ -324,6 +380,21 @@ def main() -> None:
|
|
|
324
380
|
from codefreedom.cli.proxy import run as proxy_run
|
|
325
381
|
|
|
326
382
|
sys.exit(proxy_run(args))
|
|
383
|
+
elif args.command == "tools":
|
|
384
|
+
if args.tool == "chrome":
|
|
385
|
+
if unknown:
|
|
386
|
+
eprint(f"[ERROR] Unrecognized arguments: {' '.join(unknown)}")
|
|
387
|
+
sys.exit(2)
|
|
388
|
+
from codefreedom.cli.chrome import run as chrome_run
|
|
389
|
+
|
|
390
|
+
sys.exit(chrome_run(args))
|
|
391
|
+
elif args.tool is None:
|
|
392
|
+
tools_parser.print_help()
|
|
393
|
+
sys.exit(0)
|
|
394
|
+
else:
|
|
395
|
+
eprint(f"[ERROR] Unknown tool: {args.tool}")
|
|
396
|
+
eprint(" Available tools: chrome")
|
|
397
|
+
sys.exit(1)
|
|
327
398
|
else:
|
|
328
399
|
parser.print_help()
|
|
329
400
|
sys.exit(0)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "chrome.schema.json",
|
|
4
|
+
"title": "Chrome Tool Profile",
|
|
5
|
+
"description": "Schema for chrome.json — configures the Chrome browser container image, ports, data directory, and environment.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"description": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Description of the profile"
|
|
11
|
+
},
|
|
12
|
+
"notes": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"description": "Usage notes"
|
|
18
|
+
},
|
|
19
|
+
"chrome": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "Chrome browser container settings",
|
|
22
|
+
"properties": {
|
|
23
|
+
"image": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Docker image tag (e.g. codefreedom:Chrome-local or ghcr.io/nilayparikh/codefreedom:Chrome-latest)"
|
|
26
|
+
},
|
|
27
|
+
"container_name": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Docker container name"
|
|
30
|
+
},
|
|
31
|
+
"port": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"description": "CDP debug port",
|
|
34
|
+
"minimum": 1024,
|
|
35
|
+
"maximum": 65535
|
|
36
|
+
},
|
|
37
|
+
"data_dir": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Host path for persistent Chrome profile data (~ is expanded to $HOME)"
|
|
40
|
+
},
|
|
41
|
+
"env": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Environment variables forwarded to the container",
|
|
44
|
+
"additionalProperties": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": [
|
|
53
|
+
"chrome"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "claude-code
|
|
2
|
+
"$schema": "claude-code.schema.json",
|
|
3
3
|
"description": "Claude Code profiles for codefreedom — each profile sets model selection, API endpoint, and auth explicitly.",
|
|
4
4
|
"notes": [
|
|
5
5
|
"In sandbox mode (--sandbox), each profile gets its own isolated ~/.codefreedom/{profile}/.claude directory.",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "chrome.schema.json",
|
|
3
|
+
"description": "Chrome browser tool profile for codefreedom — configures container image, ports, data directory, and environment.",
|
|
4
|
+
"notes": [
|
|
5
|
+
"The 'chrome' block defines all settings for 'codefreedom tools chrome'.",
|
|
6
|
+
"'image' is the Docker image to use — change to 'ghcr.io/nilayparikh/codefreedom:Chrome-latest' for published builds.",
|
|
7
|
+
"'container_name' is the Docker container name (persistent, restart unless-stopped).",
|
|
8
|
+
"'port' is the CDP debug port exposed to the host.",
|
|
9
|
+
"'data_dir' is the host path mounted to /data/chrome inside the container.",
|
|
10
|
+
"'env' are environment variables forwarded to the container."
|
|
11
|
+
],
|
|
12
|
+
"chrome": {
|
|
13
|
+
"image": "codefreedom:Chrome-local",
|
|
14
|
+
"container_name": "codefreedom-chrome",
|
|
15
|
+
"port": 9222,
|
|
16
|
+
"data_dir": "~/.codefreedom/sandbox/tools/chrome",
|
|
17
|
+
"env": {
|
|
18
|
+
"DISPLAY": ":99",
|
|
19
|
+
"CHROME_DEBUG_PORT": "9222"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codefreedom
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Single wrapper for all code agents — simple LLM routing, sandboxing, profile management, and isolation. All config in ~/.codefreedom.
|
|
5
5
|
Author-email: Nilay Parikh <nilay.parikh@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -27,14 +27,14 @@ Requires-Dist: PyYAML>=6.0
|
|
|
27
27
|
Requires-Dist: types-PyYAML
|
|
28
28
|
Provides-Extra: litellm
|
|
29
29
|
Requires-Dist: litellm[proxy]>=1.50; extra == "litellm"
|
|
30
|
-
Requires-Dist: prometheus-client>=0.
|
|
30
|
+
Requires-Dist: prometheus-client>=0.25.0; extra == "litellm"
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
33
33
|
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
34
34
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
35
35
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
36
36
|
Provides-Extra: docs
|
|
37
|
-
Requires-Dist: mkdocs-material[imaging]>=9.6; extra == "docs"
|
|
37
|
+
Requires-Dist: mkdocs-material[imaging]>=9.7.6; extra == "docs"
|
|
38
38
|
Requires-Dist: mkdocs-mermaid2-plugin>=1.2; extra == "docs"
|
|
39
39
|
Provides-Extra: all
|
|
40
40
|
Requires-Dist: codefreedom[dev,docs,litellm]; extra == "all"
|
|
@@ -14,13 +14,16 @@ src/codefreedom.egg-info/entry_points.txt
|
|
|
14
14
|
src/codefreedom.egg-info/requires.txt
|
|
15
15
|
src/codefreedom.egg-info/top_level.txt
|
|
16
16
|
src/codefreedom/cli/__init__.py
|
|
17
|
+
src/codefreedom/cli/chrome.py
|
|
17
18
|
src/codefreedom/cli/claude.py
|
|
18
19
|
src/codefreedom/cli/main.py
|
|
19
20
|
src/codefreedom/cli/proxy.py
|
|
20
21
|
src/codefreedom/examples/.env.example
|
|
21
22
|
src/codefreedom/examples/.env.secrets.example
|
|
22
|
-
src/codefreedom/examples/profiles/
|
|
23
|
-
src/codefreedom/examples/profiles/claude-code
|
|
23
|
+
src/codefreedom/examples/profiles/chrome.schema.json
|
|
24
|
+
src/codefreedom/examples/profiles/claude-code.json
|
|
25
|
+
src/codefreedom/examples/profiles/claude-code.schema.json
|
|
26
|
+
src/codefreedom/examples/profiles/tools-chrome.json
|
|
24
27
|
src/codefreedom/examples/proxy/config.yaml
|
|
25
28
|
src/codefreedom/examples/proxy/docker-compose.yaml
|
|
26
29
|
src/codefreedom/examples/proxy/providers/anthropic-compatible.yaml
|
|
@@ -14,13 +14,34 @@ def _setup_bundled_examples(root: Path) -> Path:
|
|
|
14
14
|
# Profiles
|
|
15
15
|
profiles_dir = examples / "profiles"
|
|
16
16
|
profiles_dir.mkdir(parents=True)
|
|
17
|
-
(profiles_dir / "claude-code
|
|
17
|
+
(profiles_dir / "claude-code.json").write_text(
|
|
18
18
|
json.dumps({"profiles": {"default": {"description": "test", "env": {}}}})
|
|
19
19
|
)
|
|
20
|
-
(profiles_dir / "claude-code
|
|
20
|
+
(profiles_dir / "claude-code.schema.json").write_text(
|
|
21
21
|
json.dumps({"$schema": "http://json-schema.org/draft-07/schema#"})
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
+
# Tool profiles + schemas (tools-*.json, * .schema.json)
|
|
25
|
+
(profiles_dir / "tools-chrome.json").write_text(
|
|
26
|
+
json.dumps(
|
|
27
|
+
{
|
|
28
|
+
"chrome": {
|
|
29
|
+
"image": "codefreedom:Chrome-local",
|
|
30
|
+
"container_name": "codefreedom-chrome",
|
|
31
|
+
"port": 9222,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
(profiles_dir / "chrome.schema.json").write_text(
|
|
37
|
+
json.dumps(
|
|
38
|
+
{
|
|
39
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
40
|
+
"title": "Chrome Tool Profile",
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
24
45
|
# Proxy
|
|
25
46
|
proxy_dir = examples / "proxy"
|
|
26
47
|
proxy_dir.mkdir(parents=True)
|
|
@@ -64,11 +85,21 @@ class TestInitCodefreedom:
|
|
|
64
85
|
assert content["profiles"]["default"]["description"] == "test"
|
|
65
86
|
|
|
66
87
|
# Verify schema was created
|
|
67
|
-
schema_dst = cf_dir / "profiles" / "claude-code
|
|
88
|
+
schema_dst = cf_dir / "profiles" / "claude-code.schema.json"
|
|
68
89
|
assert schema_dst.exists()
|
|
69
90
|
schema_content = json.loads(schema_dst.read_text())
|
|
70
91
|
assert "$schema" in schema_content
|
|
71
92
|
|
|
93
|
+
# Verify tool profile (chrome.json from tools-chrome.json)
|
|
94
|
+
tool_dst = cf_dir / "profiles" / "chrome.json"
|
|
95
|
+
assert tool_dst.exists()
|
|
96
|
+
tool_content = json.loads(tool_dst.read_text())
|
|
97
|
+
assert "chrome" in tool_content
|
|
98
|
+
|
|
99
|
+
# Verify tool schema (chrome.schema.json)
|
|
100
|
+
tool_schema_dst = cf_dir / "profiles" / "chrome.schema.json"
|
|
101
|
+
assert tool_schema_dst.exists()
|
|
102
|
+
|
|
72
103
|
# Verify proxy was created with correct nested structure
|
|
73
104
|
proxy_dst = cf_dir / "proxy"
|
|
74
105
|
assert proxy_dst.exists()
|
|
@@ -107,7 +138,7 @@ class TestInitCodefreedom:
|
|
|
107
138
|
# Setup source examples with different content
|
|
108
139
|
examples = _setup_bundled_examples(tmp_path)
|
|
109
140
|
# Override with newer content so we can verify it was NOT copied
|
|
110
|
-
(examples / "profiles" / "claude-code
|
|
141
|
+
(examples / "profiles" / "claude-code.json").write_text(
|
|
111
142
|
json.dumps({"profiles": {"default": {"env": {}}}})
|
|
112
143
|
)
|
|
113
144
|
(examples / "proxy" / "config.yaml").write_text("new content")
|
|
@@ -146,7 +177,7 @@ class TestInitCodefreedom:
|
|
|
146
177
|
new_content = json.dumps(
|
|
147
178
|
{"profiles": {"test": {"description": "new", "env": {"KEY": "val"}}}}
|
|
148
179
|
)
|
|
149
|
-
(examples / "profiles" / "claude-code
|
|
180
|
+
(examples / "profiles" / "claude-code.json").write_text(new_content)
|
|
150
181
|
(examples / "proxy" / "config.yaml").write_text("new proxy")
|
|
151
182
|
(examples / "proxy" / "docker-compose.yaml").write_text("new compose")
|
|
152
183
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/azure-foundry.yaml
RENAMED
|
File without changes
|
{codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/deepseek.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/nvidia.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{codefreedom-0.0.3 → codefreedom-0.0.4}/src/codefreedom/examples/proxy/providers/opencode-zen.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|