aicage 0.5.12__tar.gz → 0.7.0__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.
- {aicage-0.5.12 → aicage-0.7.0}/PKG-INFO +4 -3
- {aicage-0.5.12 → aicage-0.7.0}/README.md +3 -2
- {aicage-0.5.12 → aicage-0.7.0}/config/agent-build/Dockerfile +3 -2
- {aicage-0.5.12 → aicage-0.7.0}/config/agent-build/agents/claude/agent.yaml +2 -1
- aicage-0.7.0/config/agent-build/agents/claude/install.sh +18 -0
- aicage-0.7.0/config/agent-build/agents/claude/version.sh +18 -0
- aicage-0.7.0/config/agent-build/agents/droid/agent.yaml +5 -0
- {aicage-0.5.12 → aicage-0.7.0}/config/config.yaml +1 -0
- aicage-0.7.0/config/extension-build/Dockerfile +12 -0
- {aicage-0.5.12 → aicage-0.7.0}/config/images-metadata.yaml +26 -24
- aicage-0.7.0/config/validation/agent.schema.json +38 -0
- {aicage-0.5.12 → aicage-0.7.0}/pyproject.toml +6 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/_version.py +2 -2
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/cli/_parse.py +11 -5
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/cli/entrypoint.py +8 -4
- aicage-0.7.0/src/aicage/config/__init__.py +4 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/config/config_store.py +3 -2
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/config/context.py +2 -4
- aicage-0.7.0/src/aicage/config/global_config.py +67 -0
- aicage-0.7.0/src/aicage/config/project_config.py +116 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/config/runtime_config.py +13 -18
- aicage-0.7.0/src/aicage/paths.py +25 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_agent_discovery.py +2 -1
- aicage-0.7.0/src/aicage/registry/_extended_images.py +128 -0
- aicage-0.7.0/src/aicage/registry/_extensions.py +115 -0
- aicage-0.7.0/src/aicage/registry/_hashing.py +16 -0
- aicage-0.7.0/src/aicage/registry/_image_refs.py +6 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_pull_decision.py +5 -5
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_remote_api.py +1 -1
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_remote_query.py +12 -4
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/agent_version/__init__.py +1 -1
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/agent_version/checker.py +1 -1
- aicage-0.5.12/src/aicage/registry/agent_version/_store.py → aicage-0.7.0/src/aicage/registry/agent_version/store.py +9 -6
- aicage-0.7.0/src/aicage/registry/custom_agent/_validation.py +107 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/custom_agent/loader.py +36 -31
- aicage-0.7.0/src/aicage/registry/image_selection/__init__.py +2 -0
- aicage-0.7.0/src/aicage/registry/image_selection/extensions/context.py +16 -0
- aicage-0.7.0/src/aicage/registry/image_selection/extensions/extended_images.py +65 -0
- aicage-0.7.0/src/aicage/registry/image_selection/extensions/handler.py +74 -0
- aicage-0.7.0/src/aicage/registry/image_selection/extensions/missing_extensions.py +70 -0
- aicage-0.7.0/src/aicage/registry/image_selection/extensions/refs.py +14 -0
- aicage-0.7.0/src/aicage/registry/image_selection/models.py +9 -0
- aicage-0.7.0/src/aicage/registry/image_selection/selection.py +149 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/images_metadata/loader.py +4 -3
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/images_metadata/models.py +96 -49
- aicage-0.7.0/src/aicage/registry/local_build/__init__.py +0 -0
- aicage-0.7.0/src/aicage/registry/local_build/_extended_plan.py +45 -0
- aicage-0.7.0/src/aicage/registry/local_build/_extended_runner.py +90 -0
- aicage-0.7.0/src/aicage/registry/local_build/_extended_store.py +76 -0
- aicage-0.7.0/src/aicage/registry/local_build/_layers.py +24 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/local_build/_logs.py +5 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/local_build/_plan.py +22 -4
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/local_build/_runner.py +15 -2
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/local_build/_store.py +22 -20
- aicage-0.7.0/src/aicage/registry/local_build/ensure_extended_image.py +94 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/local_build/ensure_local_image.py +4 -7
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_docker_socket.py +2 -2
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_entrypoint.py +2 -2
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_git_config.py +2 -4
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_gpg.py +2 -4
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_ssh_keys.py +2 -4
- aicage-0.7.0/src/aicage/runtime/prompts/__init__.py +20 -0
- aicage-0.7.0/src/aicage/runtime/prompts/_tty.py +8 -0
- aicage-0.5.12/src/aicage/runtime/prompts.py → aicage-0.7.0/src/aicage/runtime/prompts/base.py +12 -28
- aicage-0.7.0/src/aicage/runtime/prompts/confirm.py +48 -0
- aicage-0.7.0/src/aicage/runtime/prompts/extensions.py +46 -0
- aicage-0.7.0/src/aicage/runtime/prompts/image_choice.py +115 -0
- aicage-0.7.0/src/aicage/runtime/prompts/image_ref.py +11 -0
- aicage-0.7.0/src/aicage/runtime/prompts/missing_extensions.py +24 -0
- aicage-0.5.12/config/agent-build/agents/claude/install.sh +0 -4
- aicage-0.5.12/config/agent-build/agents/claude/version.sh +0 -4
- aicage-0.5.12/config/agent-build/agents/droid/agent.yaml +0 -4
- aicage-0.5.12/src/aicage/config/__init__.py +0 -8
- aicage-0.5.12/src/aicage/config/global_config.py +0 -53
- aicage-0.5.12/src/aicage/config/project_config.py +0 -87
- aicage-0.5.12/src/aicage/registry/_agent_definition.py +0 -28
- aicage-0.5.12/src/aicage/registry/custom_agent/_validation.py +0 -44
- aicage-0.5.12/src/aicage/registry/image_selection.py +0 -66
- {aicage-0.5.12 → aicage-0.7.0}/.gitignore +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/LICENSE +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/config/agent-build/agents/droid/install.sh +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/config/agent-build/agents/droid/version.sh +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/__main__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/_logging.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/cli/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/cli/_print_config.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/cli_types.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/config/_file_locking.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/config/errors.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/config/resources.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/docker_client.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/errors.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_local_query.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_logs.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/_pull_runner.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/custom_agent/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/image_pull.py +0 -0
- {aicage-0.5.12/src/aicage/registry/images_metadata → aicage-0.7.0/src/aicage/registry/image_selection/extensions}/__init__.py +0 -0
- {aicage-0.5.12/src/aicage/registry/local_build → aicage-0.7.0/src/aicage/registry/images_metadata}/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/registry/local_build/_digest.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/_env_vars.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/agent_config.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/__init__.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_exec.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/_signing.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/mounts/resolver.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/run_args.py +0 -0
- {aicage-0.5.12 → aicage-0.7.0}/src/aicage/runtime/run_plan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aicage
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Runs agentic coding assistants in docker containers
|
|
5
5
|
Author: Stefan Kuhn
|
|
6
6
|
License: Apache License
|
|
@@ -278,11 +278,12 @@ preferences and credentials.
|
|
|
278
278
|
## aicage options
|
|
279
279
|
|
|
280
280
|
- `--dry-run` prints the composed `docker run` command without executing it.
|
|
281
|
-
- `--entrypoint PATH` mounts a custom entrypoint script to `/usr/local/bin/entrypoint.sh`.
|
|
281
|
+
- `--aicage-entrypoint PATH` mounts a custom entrypoint script to `/usr/local/bin/entrypoint.sh`.
|
|
282
282
|
- `--docker` mounts `/run/docker.sock` into the container to enable Docker-in-Docker workflows.
|
|
283
283
|
- `--config print` prints the project config path and its contents.
|
|
284
284
|
|
|
285
|
-
Configuration file formats are documented in [CONFIG.md](CONFIG.md).
|
|
285
|
+
Configuration file formats are documented in [CONFIG.md](CONFIG.md). Extension authoring is documented in
|
|
286
|
+
[doc/extensions.md](doc/extensions.md).
|
|
286
287
|
|
|
287
288
|
## Why cage agents?
|
|
288
289
|
|
|
@@ -65,11 +65,12 @@ preferences and credentials.
|
|
|
65
65
|
## aicage options
|
|
66
66
|
|
|
67
67
|
- `--dry-run` prints the composed `docker run` command without executing it.
|
|
68
|
-
- `--entrypoint PATH` mounts a custom entrypoint script to `/usr/local/bin/entrypoint.sh`.
|
|
68
|
+
- `--aicage-entrypoint PATH` mounts a custom entrypoint script to `/usr/local/bin/entrypoint.sh`.
|
|
69
69
|
- `--docker` mounts `/run/docker.sock` into the container to enable Docker-in-Docker workflows.
|
|
70
70
|
- `--config print` prints the project config path and its contents.
|
|
71
71
|
|
|
72
|
-
Configuration file formats are documented in [CONFIG.md](CONFIG.md).
|
|
72
|
+
Configuration file formats are documented in [CONFIG.md](CONFIG.md). Extension authoring is documented in
|
|
73
|
+
[doc/extensions.md](doc/extensions.md).
|
|
73
74
|
|
|
74
75
|
## Why cage agents?
|
|
75
76
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# syntax=docker/dockerfile:1.7-labs
|
|
2
1
|
ARG BASE_IMAGE=base
|
|
3
2
|
ARG AGENT=codex
|
|
4
3
|
|
|
@@ -18,4 +17,6 @@ RUN --mount=type=bind,source=agents/,target=/tmp/agents,readonly \
|
|
|
18
17
|
/tmp/agents/${AGENT}/install.sh
|
|
19
18
|
|
|
20
19
|
ENV AGENT=${AGENT}
|
|
21
|
-
|
|
20
|
+
|
|
21
|
+
# entrypoint.sh uses this variable
|
|
22
|
+
ENV AICAGE_ENTRYPOINT_CMD=${AGENT}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Install Claude using the official installer.
|
|
5
|
+
curl -fsSL https://claude.ai/install.sh | bash
|
|
6
|
+
|
|
7
|
+
# Ensure the binary is on the global PATH for the runtime user.
|
|
8
|
+
if [[ -x "/root/.local/bin/claude" ]]; then
|
|
9
|
+
install -m 0755 /root/.local/bin/claude /usr/local/bin/claude
|
|
10
|
+
elif command -v claude >/dev/null 2>&1; then
|
|
11
|
+
# Fallback: copy whatever the installer placed on PATH.
|
|
12
|
+
install -m 0755 "$(command -v claude)" /usr/local/bin/claude
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
16
|
+
echo "[install_claude] 'claude' executable not found after installation." >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Use npm to determine version despite installing with non-rpm
|
|
5
|
+
# Reason: Claude infrastructure reports wrong/old version
|
|
6
|
+
#
|
|
7
|
+
# The official installer way leads to this url:
|
|
8
|
+
# https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/stable
|
|
9
|
+
# but there this version is reported:
|
|
10
|
+
# 2.0.67
|
|
11
|
+
# while actually this version is currently correct:
|
|
12
|
+
# 2.0.76
|
|
13
|
+
#
|
|
14
|
+
# See bug: https://github.com/anthropics/claude-code/issues/13888
|
|
15
|
+
#
|
|
16
|
+
# The official installer is preferred as only then does Claude support syntax-highlighting
|
|
17
|
+
|
|
18
|
+
npm view @anthropic-ai/claude-code version
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ARG BASE_IMAGE=base
|
|
2
|
+
FROM ${BASE_IMAGE} AS runtime
|
|
3
|
+
|
|
4
|
+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
5
|
+
|
|
6
|
+
RUN --mount=type=bind,source=scripts,target=/tmp/aicage/scripts,readonly \
|
|
7
|
+
set -e; \
|
|
8
|
+
shopt -s nullglob; \
|
|
9
|
+
mkdir -p /tmp/aicage/scripts-run; \
|
|
10
|
+
for script in /tmp/aicage/scripts/*.sh; do cp "$script" /tmp/aicage/scripts-run/; done; \
|
|
11
|
+
for script in /tmp/aicage/scripts-run/*.sh; do chmod +x "$script"; "$script"; done; \
|
|
12
|
+
rm -rf /tmp/aicage/scripts-run
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
aicage-image:
|
|
2
|
-
version: 0.
|
|
2
|
+
version: 0.7.0
|
|
3
3
|
aicage-image-base:
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
bases:
|
|
6
6
|
act:
|
|
7
7
|
root_image: ghcr.io/catthehacker/ubuntu:act-latest
|
|
@@ -50,20 +50,21 @@ agent:
|
|
|
50
50
|
agent_path: ~/.claude
|
|
51
51
|
agent_full_name: Claude Code
|
|
52
52
|
agent_homepage: https://claude.com/product/claude-code
|
|
53
|
-
|
|
53
|
+
# Build locally because the license does not allow redistributing prebuilt images.
|
|
54
|
+
build_local: true
|
|
54
55
|
valid_bases:
|
|
55
|
-
act:
|
|
56
|
-
alpine:
|
|
57
|
-
debian:
|
|
58
|
-
fedora:
|
|
59
|
-
minimal:
|
|
60
|
-
node:
|
|
61
|
-
ubuntu:
|
|
56
|
+
act: aicage:claude-act
|
|
57
|
+
alpine: aicage:claude-alpine
|
|
58
|
+
debian: aicage:claude-debian
|
|
59
|
+
fedora: aicage:claude-fedora
|
|
60
|
+
minimal: aicage:claude-minimal
|
|
61
|
+
node: aicage:claude-node
|
|
62
|
+
ubuntu: aicage:claude-ubuntu
|
|
62
63
|
codex:
|
|
63
64
|
agent_path: ~/.codex
|
|
64
65
|
agent_full_name: Codex CLI
|
|
65
66
|
agent_homepage: https://developers.openai.com/codex/cli
|
|
66
|
-
|
|
67
|
+
build_local: false
|
|
67
68
|
valid_bases:
|
|
68
69
|
act: ghcr.io/aicage/aicage:codex-act
|
|
69
70
|
alpine: ghcr.io/aicage/aicage:codex-alpine
|
|
@@ -76,7 +77,7 @@ agent:
|
|
|
76
77
|
agent_path: ~/.copilot
|
|
77
78
|
agent_full_name: GitHub Copilot CLI
|
|
78
79
|
agent_homepage: https://github.com/features/copilot/cli
|
|
79
|
-
|
|
80
|
+
build_local: false
|
|
80
81
|
base_exclude:
|
|
81
82
|
- alpine
|
|
82
83
|
- minimal
|
|
@@ -92,7 +93,7 @@ agent:
|
|
|
92
93
|
agent_path: ~/.local/share/crush
|
|
93
94
|
agent_full_name: Crush
|
|
94
95
|
agent_homepage: https://github.com/charmbracelet/crush
|
|
95
|
-
|
|
96
|
+
build_local: false
|
|
96
97
|
valid_bases:
|
|
97
98
|
act: ghcr.io/aicage/aicage:crush-act
|
|
98
99
|
alpine: ghcr.io/aicage/aicage:crush-alpine
|
|
@@ -105,20 +106,21 @@ agent:
|
|
|
105
106
|
agent_path: ~/.factory
|
|
106
107
|
agent_full_name: Factory CLI
|
|
107
108
|
agent_homepage: https://factory.ai/product/cli
|
|
108
|
-
|
|
109
|
+
# Build locally because the license does not allow redistributing prebuilt images.
|
|
110
|
+
build_local: true
|
|
109
111
|
valid_bases:
|
|
110
|
-
act:
|
|
111
|
-
alpine:
|
|
112
|
-
debian:
|
|
113
|
-
fedora:
|
|
114
|
-
minimal:
|
|
115
|
-
node:
|
|
116
|
-
ubuntu:
|
|
112
|
+
act: aicage:droid-act
|
|
113
|
+
alpine: aicage:droid-alpine
|
|
114
|
+
debian: aicage:droid-debian
|
|
115
|
+
fedora: aicage:droid-fedora
|
|
116
|
+
minimal: aicage:droid-minimal
|
|
117
|
+
node: aicage:droid-node
|
|
118
|
+
ubuntu: aicage:droid-ubuntu
|
|
117
119
|
gemini:
|
|
118
120
|
agent_path: ~/.gemini
|
|
119
121
|
agent_full_name: Gemini CLI
|
|
120
122
|
agent_homepage: https://geminicli.com
|
|
121
|
-
|
|
123
|
+
build_local: false
|
|
122
124
|
valid_bases:
|
|
123
125
|
act: ghcr.io/aicage/aicage:gemini-act
|
|
124
126
|
alpine: ghcr.io/aicage/aicage:gemini-alpine
|
|
@@ -131,7 +133,7 @@ agent:
|
|
|
131
133
|
agent_path: ~/.config/goose
|
|
132
134
|
agent_full_name: Goose CLI
|
|
133
135
|
agent_homepage: https://block.github.io/goose
|
|
134
|
-
|
|
136
|
+
build_local: false
|
|
135
137
|
valid_bases:
|
|
136
138
|
act: ghcr.io/aicage/aicage:goose-act
|
|
137
139
|
alpine: ghcr.io/aicage/aicage:goose-alpine
|
|
@@ -144,7 +146,7 @@ agent:
|
|
|
144
146
|
agent_path: ~/.qwen
|
|
145
147
|
agent_full_name: Qwen Code
|
|
146
148
|
agent_homepage: https://github.com/QwenLM/qwen-code
|
|
147
|
-
|
|
149
|
+
build_local: false
|
|
148
150
|
valid_bases:
|
|
149
151
|
act: ghcr.io/aicage/aicage:qwen-act
|
|
150
152
|
alpine: ghcr.io/aicage/aicage:qwen-alpine
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "AICAGE agent definition",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"agent_path",
|
|
7
|
+
"agent_full_name",
|
|
8
|
+
"agent_homepage"
|
|
9
|
+
],
|
|
10
|
+
"properties": {
|
|
11
|
+
"agent_path": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"agent_full_name": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"agent_homepage": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"build_local": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": true
|
|
23
|
+
},
|
|
24
|
+
"base_exclude": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "string"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"base_distro_exclude": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": {
|
|
33
|
+
"type": "string"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"additionalProperties": false
|
|
38
|
+
}
|
|
@@ -26,12 +26,16 @@ packages = ["src/aicage"]
|
|
|
26
26
|
"config/config.yaml" = "config/config.yaml"
|
|
27
27
|
"config/images-metadata.yaml" = "config/images-metadata.yaml"
|
|
28
28
|
"config/agent-build" = "config/agent-build"
|
|
29
|
+
"config/extension-build" = "config/extension-build"
|
|
30
|
+
"config/validation/agent.schema.json" = "config/validation/agent.schema.json"
|
|
29
31
|
|
|
30
32
|
[tool.hatch.build]
|
|
31
33
|
include = [
|
|
32
34
|
"config/config.yaml",
|
|
33
35
|
"config/images-metadata.yaml",
|
|
34
36
|
"config/agent-build/**",
|
|
37
|
+
"config/extension-build/**",
|
|
38
|
+
"config/validation/agent.schema.json",
|
|
35
39
|
]
|
|
36
40
|
|
|
37
41
|
[tool.hatch.build.targets.sdist]
|
|
@@ -40,6 +44,8 @@ include = [
|
|
|
40
44
|
"config/config.yaml",
|
|
41
45
|
"config/images-metadata.yaml",
|
|
42
46
|
"config/agent-build/**",
|
|
47
|
+
"config/extension-build/**",
|
|
48
|
+
"config/validation/agent.schema.json",
|
|
43
49
|
"LICENSE",
|
|
44
50
|
"README.md",
|
|
45
51
|
"pyproject.toml",
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.7.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 7, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -6,7 +6,7 @@ from aicage._logging import get_logger
|
|
|
6
6
|
from aicage.cli_types import ParsedArgs
|
|
7
7
|
from aicage.errors import CliError
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
_MIN_REMAINING_WITH_AGENT = 2
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def parse_cli(argv: Sequence[str]) -> ParsedArgs:
|
|
@@ -16,7 +16,11 @@ def parse_cli(argv: Sequence[str]) -> ParsedArgs:
|
|
|
16
16
|
"""
|
|
17
17
|
parser = argparse.ArgumentParser(add_help=False)
|
|
18
18
|
parser.add_argument("--dry-run", action="store_true", help="Print docker run command without executing.")
|
|
19
|
-
parser.add_argument(
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--aicage-entrypoint",
|
|
21
|
+
dest="entrypoint",
|
|
22
|
+
help="Override the container entrypoint with a host path.",
|
|
23
|
+
)
|
|
20
24
|
parser.add_argument("--docker", action="store_true", help="Mount the host Docker socket into the container.")
|
|
21
25
|
parser.add_argument("--config", help="Perform config actions such as 'print'.")
|
|
22
26
|
parser.add_argument("-h", "--help", action="store_true", help="Show help message and exit.")
|
|
@@ -30,8 +34,8 @@ def parse_cli(argv: Sequence[str]) -> ParsedArgs:
|
|
|
30
34
|
usage: str = (
|
|
31
35
|
"Usage:\n"
|
|
32
36
|
" aicage <agent>\n"
|
|
33
|
-
" aicage [--dry-run] [--docker] [--entrypoint PATH] -- <agent> [<agent-args>]\n"
|
|
34
|
-
" aicage [--dry-run] [--docker] [--entrypoint PATH] <docker-args> -- <agent> [<agent-args>]\n"
|
|
37
|
+
" aicage [--dry-run] [--docker] [--aicage-entrypoint PATH] -- <agent> [<agent-args>]\n"
|
|
38
|
+
" aicage [--dry-run] [--docker] [--aicage-entrypoint PATH] <docker-args> -- <agent> [<agent-args>]\n"
|
|
35
39
|
" aicage --config print\n\n"
|
|
36
40
|
"Any arguments between aicage and the agent require a '--' separator before the agent.\n"
|
|
37
41
|
"<docker-args> are any arguments not recognized by aicage.\n"
|
|
@@ -102,6 +106,8 @@ def _parse_agent_section(
|
|
|
102
106
|
if not remaining:
|
|
103
107
|
raise CliError("Missing arguments. Provide an agent name (and optional docker args).")
|
|
104
108
|
first: str = remaining[0]
|
|
105
|
-
if
|
|
109
|
+
if first.startswith("-") or "=" in first:
|
|
110
|
+
if len(remaining) < _MIN_REMAINING_WITH_AGENT:
|
|
111
|
+
raise CliError("Missing agent name after docker args. Use '--' before the agent.")
|
|
106
112
|
return first, remaining[1], remaining[2:]
|
|
107
113
|
return "", first, remaining[1:]
|
|
@@ -7,9 +7,11 @@ from aicage._logging import get_logger
|
|
|
7
7
|
from aicage.cli._parse import parse_cli
|
|
8
8
|
from aicage.cli._print_config import print_project_config
|
|
9
9
|
from aicage.cli_types import ParsedArgs
|
|
10
|
-
from aicage.config import ConfigError
|
|
10
|
+
from aicage.config import ConfigError
|
|
11
|
+
from aicage.config.runtime_config import RunConfig, load_run_config
|
|
11
12
|
from aicage.errors import CliError
|
|
12
13
|
from aicage.registry.image_pull import pull_image
|
|
14
|
+
from aicage.registry.local_build.ensure_extended_image import ensure_extended_image
|
|
13
15
|
from aicage.registry.local_build.ensure_local_image import ensure_local_image
|
|
14
16
|
from aicage.runtime.run_args import DockerRunArgs, assemble_docker_run
|
|
15
17
|
from aicage.runtime.run_plan import build_run_args
|
|
@@ -26,10 +28,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
26
28
|
run_config: RunConfig = load_run_config(parsed.agent, parsed)
|
|
27
29
|
logger.info("Resolved run config for agent %s", run_config.agent)
|
|
28
30
|
agent_metadata = run_config.images_metadata.agents[run_config.agent]
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if run_config.extensions:
|
|
32
|
+
ensure_extended_image(run_config)
|
|
33
|
+
elif agent_metadata.local_definition_dir is None:
|
|
32
34
|
pull_image(run_config)
|
|
35
|
+
else:
|
|
36
|
+
ensure_local_image(run_config)
|
|
33
37
|
run_args: DockerRunArgs = build_run_args(config=run_config, parsed=parsed)
|
|
34
38
|
|
|
35
39
|
run_cmd: list[str] = assemble_docker_run(run_args)
|
|
@@ -7,12 +7,13 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
|
+
from aicage.paths import CONFIG_FILENAME
|
|
11
|
+
|
|
10
12
|
from .errors import ConfigError
|
|
11
13
|
from .global_config import GlobalConfig
|
|
12
14
|
from .project_config import ProjectConfig
|
|
13
15
|
from .resources import find_packaged_path
|
|
14
16
|
|
|
15
|
-
_CONFIG_FILENAME = "config.yaml"
|
|
16
17
|
_DEFAULT_BASE_DIR = "~/.aicage"
|
|
17
18
|
_PROJECTS_SUBDIR = "projects"
|
|
18
19
|
|
|
@@ -66,7 +67,7 @@ class SettingsStore:
|
|
|
66
67
|
"""
|
|
67
68
|
Returns the path to the packaged global config file.
|
|
68
69
|
"""
|
|
69
|
-
return find_packaged_path(
|
|
70
|
+
return find_packaged_path(CONFIG_FILENAME)
|
|
70
71
|
|
|
71
72
|
def project_config_path(self, project_realpath: Path) -> Path:
|
|
72
73
|
"""
|
|
@@ -20,11 +20,11 @@ class ConfigContext:
|
|
|
20
20
|
return f"{self.global_cfg.image_registry}/{self.global_cfg.image_repository}"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def _build_config_context() -> ConfigContext:
|
|
24
24
|
store = SettingsStore()
|
|
25
25
|
project_path = Path.cwd().resolve()
|
|
26
26
|
global_cfg = store.load_global()
|
|
27
|
-
images_metadata = load_images_metadata()
|
|
27
|
+
images_metadata = load_images_metadata(global_cfg.local_image_repository)
|
|
28
28
|
project_cfg = store.load_project(project_path)
|
|
29
29
|
return ConfigContext(
|
|
30
30
|
store=store,
|
|
@@ -32,5 +32,3 @@ def build_config_context() -> ConfigContext:
|
|
|
32
32
|
global_cfg=global_cfg,
|
|
33
33
|
images_metadata=images_metadata,
|
|
34
34
|
)
|
|
35
|
-
|
|
36
|
-
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .errors import ConfigError
|
|
5
|
+
|
|
6
|
+
_IMAGE_REGISTRY_KEY: str = "image_registry"
|
|
7
|
+
_IMAGE_REGISTRY_API_URL_KEY: str = "image_registry_api_url"
|
|
8
|
+
_IMAGE_REGISTRY_API_TOKEN_URL_KEY: str = "image_registry_api_token_url"
|
|
9
|
+
_IMAGE_REPOSITORY_KEY: str = "image_repository"
|
|
10
|
+
_IMAGE_BASE_REPOSITORY_KEY: str = "image_base_repository"
|
|
11
|
+
_DEFAULT_IMAGE_BASE_KEY: str = "default_image_base"
|
|
12
|
+
_VERSION_CHECK_IMAGE_KEY: str = "version_check_image"
|
|
13
|
+
_LOCAL_IMAGE_REPOSITORY_KEY: str = "local_image_repository"
|
|
14
|
+
_GLOBAL_AGENTS_KEY: str = "agents"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class GlobalConfig:
|
|
19
|
+
image_registry: str
|
|
20
|
+
image_registry_api_url: str
|
|
21
|
+
image_registry_api_token_url: str
|
|
22
|
+
image_repository: str
|
|
23
|
+
image_base_repository: str
|
|
24
|
+
default_image_base: str
|
|
25
|
+
version_check_image: str
|
|
26
|
+
local_image_repository: str
|
|
27
|
+
agents: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_mapping(cls, data: dict[str, Any]) -> "GlobalConfig":
|
|
31
|
+
required = (
|
|
32
|
+
_IMAGE_REGISTRY_KEY,
|
|
33
|
+
_IMAGE_REGISTRY_API_URL_KEY,
|
|
34
|
+
_IMAGE_REGISTRY_API_TOKEN_URL_KEY,
|
|
35
|
+
_IMAGE_REPOSITORY_KEY,
|
|
36
|
+
_IMAGE_BASE_REPOSITORY_KEY,
|
|
37
|
+
_DEFAULT_IMAGE_BASE_KEY,
|
|
38
|
+
_VERSION_CHECK_IMAGE_KEY,
|
|
39
|
+
_LOCAL_IMAGE_REPOSITORY_KEY,
|
|
40
|
+
)
|
|
41
|
+
missing = [key for key in required if key not in data]
|
|
42
|
+
if missing:
|
|
43
|
+
raise ConfigError(f"Missing required config values: {', '.join(missing)}.")
|
|
44
|
+
return cls(
|
|
45
|
+
image_registry=data[_IMAGE_REGISTRY_KEY],
|
|
46
|
+
image_registry_api_url=data[_IMAGE_REGISTRY_API_URL_KEY],
|
|
47
|
+
image_registry_api_token_url=data[_IMAGE_REGISTRY_API_TOKEN_URL_KEY],
|
|
48
|
+
image_repository=data[_IMAGE_REPOSITORY_KEY],
|
|
49
|
+
image_base_repository=data[_IMAGE_BASE_REPOSITORY_KEY],
|
|
50
|
+
default_image_base=data[_DEFAULT_IMAGE_BASE_KEY],
|
|
51
|
+
version_check_image=data[_VERSION_CHECK_IMAGE_KEY],
|
|
52
|
+
local_image_repository=data[_LOCAL_IMAGE_REPOSITORY_KEY],
|
|
53
|
+
agents=data.get(_GLOBAL_AGENTS_KEY, {}) or {},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def to_mapping(self) -> dict[str, Any]:
|
|
57
|
+
return {
|
|
58
|
+
_IMAGE_REGISTRY_KEY: self.image_registry,
|
|
59
|
+
_IMAGE_REGISTRY_API_URL_KEY: self.image_registry_api_url,
|
|
60
|
+
_IMAGE_REGISTRY_API_TOKEN_URL_KEY: self.image_registry_api_token_url,
|
|
61
|
+
_IMAGE_REPOSITORY_KEY: self.image_repository,
|
|
62
|
+
_IMAGE_BASE_REPOSITORY_KEY: self.image_base_repository,
|
|
63
|
+
_DEFAULT_IMAGE_BASE_KEY: self.default_image_base,
|
|
64
|
+
_VERSION_CHECK_IMAGE_KEY: self.version_check_image,
|
|
65
|
+
_LOCAL_IMAGE_REPOSITORY_KEY: self.local_image_repository,
|
|
66
|
+
_GLOBAL_AGENTS_KEY: self.agents,
|
|
67
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
_PROJECT_PATH_KEY: str = "path"
|
|
6
|
+
_PROJECT_AGENTS_KEY: str = "agents"
|
|
7
|
+
_DOCKER_ARGS_KEY: str = "docker_args"
|
|
8
|
+
|
|
9
|
+
AGENT_BASE_KEY: str = "base"
|
|
10
|
+
_AGENT_ENTRYPOINT_KEY: str = "entrypoint"
|
|
11
|
+
_AGENT_MOUNTS_KEY: str = "mounts"
|
|
12
|
+
_AGENT_IMAGE_REF_KEY: str = "image_ref"
|
|
13
|
+
_AGENT_EXTENSIONS_KEY: str = "extensions"
|
|
14
|
+
|
|
15
|
+
_MOUNT_GITCONFIG_KEY: str = "gitconfig"
|
|
16
|
+
_MOUNT_GNUPG_KEY: str = "gnupg"
|
|
17
|
+
_MOUNT_SSH_KEY: str = "ssh"
|
|
18
|
+
_MOUNT_DOCKER_KEY: str = "docker"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class _AgentMounts:
|
|
23
|
+
gitconfig: bool | None = None
|
|
24
|
+
gnupg: bool | None = None
|
|
25
|
+
ssh: bool | None = None
|
|
26
|
+
docker: bool | None = None
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_mapping(cls, data: dict[str, Any]) -> "_AgentMounts":
|
|
30
|
+
return cls(
|
|
31
|
+
gitconfig=data.get(_MOUNT_GITCONFIG_KEY),
|
|
32
|
+
gnupg=data.get(_MOUNT_GNUPG_KEY),
|
|
33
|
+
ssh=data.get(_MOUNT_SSH_KEY),
|
|
34
|
+
docker=data.get(_MOUNT_DOCKER_KEY),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def to_mapping(self) -> dict[str, bool]:
|
|
38
|
+
payload: dict[str, bool] = {}
|
|
39
|
+
if self.gitconfig is not None:
|
|
40
|
+
payload[_MOUNT_GITCONFIG_KEY] = self.gitconfig
|
|
41
|
+
if self.gnupg is not None:
|
|
42
|
+
payload[_MOUNT_GNUPG_KEY] = self.gnupg
|
|
43
|
+
if self.ssh is not None:
|
|
44
|
+
payload[_MOUNT_SSH_KEY] = self.ssh
|
|
45
|
+
if self.docker is not None:
|
|
46
|
+
payload[_MOUNT_DOCKER_KEY] = self.docker
|
|
47
|
+
return payload
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class AgentConfig:
|
|
52
|
+
base: str | None = None
|
|
53
|
+
docker_args: str = ""
|
|
54
|
+
entrypoint: str | None = None
|
|
55
|
+
mounts: _AgentMounts = field(default_factory=_AgentMounts)
|
|
56
|
+
image_ref: str | None = None
|
|
57
|
+
extensions: list[str] = field(default_factory=list)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_mapping(cls, data: dict[str, Any]) -> "AgentConfig":
|
|
61
|
+
mounts = _AgentMounts.from_mapping(data.get(_AGENT_MOUNTS_KEY, {}) or {})
|
|
62
|
+
return cls(
|
|
63
|
+
base=data.get(AGENT_BASE_KEY),
|
|
64
|
+
docker_args=data.get(_DOCKER_ARGS_KEY, "") or "",
|
|
65
|
+
entrypoint=data.get(_AGENT_ENTRYPOINT_KEY),
|
|
66
|
+
mounts=mounts,
|
|
67
|
+
image_ref=data.get(_AGENT_IMAGE_REF_KEY),
|
|
68
|
+
extensions=_read_str_list(data.get(_AGENT_EXTENSIONS_KEY)),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def to_mapping(self) -> dict[str, Any]:
|
|
72
|
+
payload: dict[str, Any] = {}
|
|
73
|
+
if self.base:
|
|
74
|
+
payload[AGENT_BASE_KEY] = self.base
|
|
75
|
+
if self.docker_args:
|
|
76
|
+
payload[_DOCKER_ARGS_KEY] = self.docker_args
|
|
77
|
+
if self.entrypoint:
|
|
78
|
+
payload[_AGENT_ENTRYPOINT_KEY] = self.entrypoint
|
|
79
|
+
mounts = self.mounts.to_mapping()
|
|
80
|
+
if mounts:
|
|
81
|
+
payload[_AGENT_MOUNTS_KEY] = mounts
|
|
82
|
+
if self.image_ref:
|
|
83
|
+
payload[_AGENT_IMAGE_REF_KEY] = self.image_ref
|
|
84
|
+
if self.extensions:
|
|
85
|
+
payload[_AGENT_EXTENSIONS_KEY] = list(self.extensions)
|
|
86
|
+
return payload
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class ProjectConfig:
|
|
91
|
+
path: str
|
|
92
|
+
agents: dict[str, AgentConfig] = field(default_factory=dict)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_mapping(cls, project_path: Path, data: dict[str, Any]) -> "ProjectConfig":
|
|
96
|
+
raw_agents = data.get(_PROJECT_AGENTS_KEY, {}) or {}
|
|
97
|
+
agents = {name: AgentConfig.from_mapping(cfg) for name, cfg in raw_agents.items()}
|
|
98
|
+
legacy_docker_args = data.get(_DOCKER_ARGS_KEY, "")
|
|
99
|
+
if legacy_docker_args:
|
|
100
|
+
for agent_cfg in agents.values():
|
|
101
|
+
if not agent_cfg.docker_args:
|
|
102
|
+
agent_cfg.docker_args = legacy_docker_args
|
|
103
|
+
return cls(
|
|
104
|
+
path=data.get(_PROJECT_PATH_KEY, str(project_path)),
|
|
105
|
+
agents=agents,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def to_mapping(self) -> dict[str, Any]:
|
|
109
|
+
agents_payload = {name: cfg.to_mapping() for name, cfg in self.agents.items()}
|
|
110
|
+
return {_PROJECT_PATH_KEY: self.path, _PROJECT_AGENTS_KEY: agents_payload}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _read_str_list(value: Any) -> list[str]:
|
|
114
|
+
if not isinstance(value, list):
|
|
115
|
+
return []
|
|
116
|
+
return [item for item in value if isinstance(item, str) and item]
|