aircon 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +249 -0
- data/exe/aircon +7 -0
- data/lib/aircon/cli.rb +215 -0
- data/lib/aircon/commands/down.rb +17 -0
- data/lib/aircon/commands/up.rb +186 -0
- data/lib/aircon/commands/vscode.rb +26 -0
- data/lib/aircon/configuration.rb +58 -0
- data/lib/aircon/docker.rb +25 -0
- data/lib/aircon/version.rb +5 -0
- data/lib/aircon.rb +9 -0
- metadata +95 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7a82194933fb47bffb6daacf310c4f244ab1d9a25db2f9b32ec25edd578c4a06
|
|
4
|
+
data.tar.gz: 0222cec49d295996c4066d56518ee84fff8747c8189713f4581a17e103ac8c61
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 937ab4b996d225743372ed6d5c25fe319c2305dfd84bca24b95671465ae1846ab20043f2ad7e442096e09cb060840e2a2bd948b5b723932469bb99f744c85dd4
|
|
7
|
+
data.tar.gz: 7623f3fd28dd5adffbd85908447c1d5e640a952a9eb2c27d0c31f8de7f2407d76a22762f34f50b0f3f4aab3fe750b5e34e33cc69fdb29575804bee222aa02629
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Philip Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Aircon
|
|
2
|
+
|
|
3
|
+
No more worktrees. Aircon gives every feature branch its own isolated Docker container pre-loaded with Claude Code, your credentials, and a running shell — so you can work on multiple branches in parallel without them stepping on each other. Dependencies like databases are isolated, so a db migration in one container does not affect the other.
|
|
4
|
+
|
|
5
|
+
Each container gets:
|
|
6
|
+
- Your Claude Code credentials and settings injected at startup (no Dockerfile changes needed)
|
|
7
|
+
- Claude Code installed automatically if not already in the image
|
|
8
|
+
- Your GitHub token set for authenticated `git` and `gh` operations
|
|
9
|
+
- A git branch checked out and ready to go
|
|
10
|
+
- An optional project-specific init script that runs after the container is up
|
|
11
|
+
|
|
12
|
+
When you close the last shell session, the container and its volumes are automatically torn down.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- Ruby >= 3.3.0
|
|
19
|
+
- Docker and Docker Compose
|
|
20
|
+
- Claude Code installed on your host machine (aircon copies credentials from it)
|
|
21
|
+
- VS Code with the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension (only needed for `aircon vscode`)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install aircon
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or add to your Gemfile:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
gem "aircon"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Getting Started
|
|
40
|
+
|
|
41
|
+
**1. Initialize aircon in your project:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd your-project
|
|
45
|
+
aircon init
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This creates four files under `.aircon/`:
|
|
49
|
+
|
|
50
|
+
| File | Purpose |
|
|
51
|
+
|------|---------|
|
|
52
|
+
| `aircon.yml` | Main config (tokens, paths, user settings) |
|
|
53
|
+
| `aircon_init.sh` | Script run inside the container after setup |
|
|
54
|
+
| `Dockerfile` | Base image for your dev container, change it or use your own |
|
|
55
|
+
| `docker-compose.yml` | Compose config wired to the Dockerfile, change it or use your own |
|
|
56
|
+
|
|
57
|
+
Existing files are never overwritten, so `aircon init` is safe to re-run.
|
|
58
|
+
|
|
59
|
+
**2. Configure your tokens:**
|
|
60
|
+
|
|
61
|
+
Edit `.aircon/aircon.yml` and set at minimum:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
gh_token: <%= ENV['GITHUB_TOKEN'] %>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**3. Start a container:**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
aircon up my-feature
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This builds the image, injects your Claude credentials, checks out a branch named `my-feature`, runs your init script, and drops you into a shell inside the container. The container and dependencies set in the docker-compose file will all be running under the `my-feature` docker project, fully isolating it from other containers.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Commands
|
|
78
|
+
|
|
79
|
+
### `aircon up NAME [PORT]`
|
|
80
|
+
|
|
81
|
+
Start or attach to a dev container.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
aircon up my-feature # start on default port 3001
|
|
85
|
+
aircon up my-feature 3005 # start on port 3005
|
|
86
|
+
aircon up my-feature -b feat/auth # use a different git branch than NAME
|
|
87
|
+
aircon up my-feature -d # start detached (no interactive shell)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**What `aircon up` does, step by step:**
|
|
91
|
+
|
|
92
|
+
1. Looks for an existing container named `NAME-SERVICE-1` (e.g. `my-feature-app-1`).
|
|
93
|
+
- If found → attaches a new `bash` session to it and skips to step 12.
|
|
94
|
+
2. Warns if `gh_token` is not configured.
|
|
95
|
+
3. Runs `docker compose up -d --build` with `HOST_PORT`, `AIRCON_APP_NAME`, `AIRCON_CONTAINER_USER`, and `AIRCON_WORKSPACE_PATH` injected as environment variables.
|
|
96
|
+
4. Copies `~/.claude.json` and `~/.claude/` from your host into the container via `docker cp` (no `COPY` lines needed in your Dockerfile). Host home paths inside those files are rewritten to match the container home directory.
|
|
97
|
+
5. Installs Claude Code inside the container if not already present (`curl -fsSL https://claude.ai/install.sh | bash`).
|
|
98
|
+
6. Adds `~/.local/bin` to `PATH` in `/etc/bash.bashrc` so `claude` is available in all sessions.
|
|
99
|
+
7. Writes `GH_TOKEN` and `GITHUB_PERSONAL_ACCESS_TOKEN` to `/etc/bash.bashrc` (if `gh_token` is set).
|
|
100
|
+
8. Writes `CLAUDE_CODE_OAUTH_TOKEN` to `/etc/bash.bashrc` (if configured).
|
|
101
|
+
9. Sets `git config user.email` and `git config user.name` globally inside the container.
|
|
102
|
+
10. Configures `git` to authenticate GitHub URLs with your token (covers both `https://github.com/` and `git@github.com:`).
|
|
103
|
+
11. Checks out the branch:
|
|
104
|
+
- If the branch exists on `origin` → fetches and checks it out.
|
|
105
|
+
- Otherwise → creates a new branch from `origin/main`.
|
|
106
|
+
12. Runs the `init_script` (`.aircon/aircon_init.sh` by default) inside the container via `bash -l`, if the file exists.
|
|
107
|
+
13. Attaches an interactive `bash` session.
|
|
108
|
+
14. When the last `bash` session exits → runs `docker compose down -v --remove-orphans` and `docker image prune -f`.
|
|
109
|
+
|
|
110
|
+
**Options:**
|
|
111
|
+
|
|
112
|
+
| Option | Alias | Description |
|
|
113
|
+
|--------|-------|-------------|
|
|
114
|
+
| `--branch BRANCH` | `-b` | Git branch to check out (defaults to NAME) |
|
|
115
|
+
| `--detach` | `-d` | Start without attaching an interactive session |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### `aircon down NAME`
|
|
120
|
+
|
|
121
|
+
Tear down the container and volumes for a project.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
aircon down my-feature
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Runs `docker compose down -v --remove-orphans` and prunes unused images. Use this to clean up manually if you need to reset state without waiting for a session to end.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### `aircon vscode NAME`
|
|
132
|
+
|
|
133
|
+
Attach VS Code to a running container.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
aircon vscode my-feature
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The container must already be running (`aircon up` first). Opens VS Code connected to the container via the Dev Containers extension, with the workspace set to `workspace_path`.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `aircon init`
|
|
144
|
+
|
|
145
|
+
Generate the `.aircon/` config files in the current directory.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
aircon init
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Creates `aircon.yml`, `aircon_init.sh`, `Dockerfile`, and `docker-compose.yml` under `.aircon/`. Safe to re-run — existing files are not overwritten.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### `aircon version`
|
|
156
|
+
|
|
157
|
+
Print the installed version.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
aircon version
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Configuration
|
|
166
|
+
|
|
167
|
+
Config is loaded from `.aircon/aircon.yml` in your project root. All keys are optional. ERB is supported, so you can pull in environment variables.
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
# Docker Compose file to use
|
|
171
|
+
compose_file: .aircon/docker-compose.yml
|
|
172
|
+
|
|
173
|
+
# Application name — used for DB credentials in the default Compose template
|
|
174
|
+
# Defaults to the basename of the current directory
|
|
175
|
+
app_name: my-app
|
|
176
|
+
|
|
177
|
+
# GitHub personal access token — authenticates git and gh inside the container
|
|
178
|
+
gh_token: <%= ENV['GITHUB_TOKEN'] %>
|
|
179
|
+
|
|
180
|
+
# Claude Code OAuth token — set as CLAUDE_CODE_OAUTH_TOKEN inside the container
|
|
181
|
+
claude_code_oauth_token: <%= ENV['CLAUDE_CODE_OAUTH_TOKEN'] %>
|
|
182
|
+
|
|
183
|
+
# Workspace folder path inside the container
|
|
184
|
+
workspace_path: /my-app
|
|
185
|
+
|
|
186
|
+
# Path to your Claude config file on the host
|
|
187
|
+
claude_config_path: ~/.claude.json
|
|
188
|
+
|
|
189
|
+
# Path to your Claude directory on the host
|
|
190
|
+
claude_dir_path: ~/.claude
|
|
191
|
+
|
|
192
|
+
# Docker Compose service name
|
|
193
|
+
service: app
|
|
194
|
+
|
|
195
|
+
# Git identity inside the container
|
|
196
|
+
git_email: claude_docker@localhost.com
|
|
197
|
+
git_name: Claude Docker
|
|
198
|
+
|
|
199
|
+
# Non-root user inside the container (determines home directory)
|
|
200
|
+
container_user: appuser
|
|
201
|
+
|
|
202
|
+
# Script to run inside the container after setup (path relative to this file)
|
|
203
|
+
init_script: .aircon/aircon_init.sh
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Configuration Reference
|
|
207
|
+
|
|
208
|
+
| Key | Default | Description |
|
|
209
|
+
|-----|---------|-------------|
|
|
210
|
+
| `compose_file` | `.aircon/docker-compose.yml` | Docker Compose file to use |
|
|
211
|
+
| `app_name` | basename of cwd | App name passed to Compose as `AIRCON_APP_NAME` |
|
|
212
|
+
| `gh_token` | `nil` | GitHub token; sets `GH_TOKEN` and `GITHUB_PERSONAL_ACCESS_TOKEN` in the container |
|
|
213
|
+
| `claude_code_oauth_token` | `nil` | Claude Code OAuth token; sets `CLAUDE_CODE_OAUTH_TOKEN` in the container |
|
|
214
|
+
| `workspace_path` | `/workspace` | Workspace folder path inside the container |
|
|
215
|
+
| `claude_config_path` | `~/.claude.json` | Host path to `claude.json` |
|
|
216
|
+
| `claude_dir_path` | `~/.claude` | Host path to `.claude/` directory |
|
|
217
|
+
| `service` | `app` | Docker Compose service name for the main container |
|
|
218
|
+
| `git_email` | `claude_docker@localhost.com` | Git author email inside the container |
|
|
219
|
+
| `git_name` | `Claude Docker` | Git author name inside the container |
|
|
220
|
+
| `container_user` | `appuser` | Non-root user inside the container |
|
|
221
|
+
| `init_script` | `.aircon/aircon_init.sh` | Script run after setup; has access to `GH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, etc. |
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Notes
|
|
226
|
+
|
|
227
|
+
- SSH keys are not managed by aircon — handle them in your `init_script` if needed.
|
|
228
|
+
- Your `Dockerfile` does not need `COPY` instructions for Claude settings; aircon injects them at runtime via `docker cp`.
|
|
229
|
+
- The Docker Compose project name is set to `NAME` (the argument to `aircon up`), so the container will be named `NAME-SERVICE-1` (e.g. `my-feature-app-1`). This is how aircon identifies containers across commands.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Releasing to RubyGems
|
|
234
|
+
|
|
235
|
+
1. Bump the version in `lib/aircon/version.rb`.
|
|
236
|
+
2. Build and push:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
gem build aircon.gemspec
|
|
240
|
+
gem push aircon-<version>.gem
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
You'll be prompted for your RubyGems credentials on first push. Subsequent pushes use the stored API key at `~/.gem/credentials`.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT — see [LICENSE.txt](LICENSE.txt).
|
data/exe/aircon
ADDED
data/lib/aircon/cli.rb
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "thor"
|
|
5
|
+
|
|
6
|
+
module Aircon
|
|
7
|
+
class CLI < Thor
|
|
8
|
+
def self.exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc "up NAME [PORT]", "Start or attach to a dev container for the given project"
|
|
13
|
+
method_option :detach, type: :boolean, default: false, aliases: "-d",
|
|
14
|
+
desc: "Start container without attaching an interactive session"
|
|
15
|
+
method_option :branch, type: :string, aliases: "-b",
|
|
16
|
+
desc: "Git branch to check out (defaults to NAME)"
|
|
17
|
+
def up(name, port = "3001")
|
|
18
|
+
config = Configuration.new
|
|
19
|
+
branch = options[:branch] || name
|
|
20
|
+
Commands::Up.new(config: config).call(name, branch: branch, port: port, detach: options[:detach])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc "down NAME", "Tear down the container and volumes for the given project"
|
|
24
|
+
def down(name)
|
|
25
|
+
config = Configuration.new
|
|
26
|
+
Commands::Down.new(config: config).call(name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc "vscode NAME", "Attach VS Code to a running container for the given project"
|
|
30
|
+
def vscode(name)
|
|
31
|
+
config = Configuration.new
|
|
32
|
+
Commands::Vscode.new(config: config).call(name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "init", "Create a sample .aircon/aircon.yml, Dockerfile, and docker-compose.yml in the current directory"
|
|
36
|
+
def init
|
|
37
|
+
FileUtils.mkdir_p(File.join(Dir.pwd, ".aircon"))
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
"aircon.yml" => SAMPLE_CONFIG,
|
|
41
|
+
"aircon_init.sh" => INIT_SCRIPT_TEMPLATE,
|
|
42
|
+
"Dockerfile" => DOCKERFILE_TEMPLATE,
|
|
43
|
+
"docker-compose.yml" => COMPOSE_TEMPLATE
|
|
44
|
+
}.each do |filename, content|
|
|
45
|
+
path = File.join(Dir.pwd, ".aircon", filename)
|
|
46
|
+
if File.exist?(path)
|
|
47
|
+
puts "Skipped .aircon/#{filename} (already exists)"
|
|
48
|
+
else
|
|
49
|
+
File.write(path, content)
|
|
50
|
+
puts "Created .aircon/#{filename}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc "version", "Show aircon version"
|
|
56
|
+
def version
|
|
57
|
+
puts "aircon #{VERSION}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
SAMPLE_CONFIG = <<~'YAML'
|
|
61
|
+
# Aircon configuration — ERB is supported (e.g. <%= ENV['GITHUB_TOKEN'] %>)
|
|
62
|
+
# See: https://github.com/creativesorcery/aircon
|
|
63
|
+
|
|
64
|
+
# Docker Compose file to use (default: .aircon/docker-compose.yml)
|
|
65
|
+
# compose_file: .aircon/docker-compose.yml
|
|
66
|
+
|
|
67
|
+
# Application name used for database credentials etc. (default: directory basename)
|
|
68
|
+
# app_name: myapp
|
|
69
|
+
|
|
70
|
+
# GitHub personal access token (supports ERB)
|
|
71
|
+
# gh_token: <%= ENV['GITHUB_TOKEN'] %>
|
|
72
|
+
|
|
73
|
+
# Claude Code OAuth token (supports ERB)
|
|
74
|
+
# claude_code_oauth_token: <%= ENV['CLAUDE_CODE_OAUTH_TOKEN'] %>
|
|
75
|
+
|
|
76
|
+
# Workspace folder path inside the container
|
|
77
|
+
# workspace_path: /myproject
|
|
78
|
+
|
|
79
|
+
# Path to host's Claude config file
|
|
80
|
+
# claude_config_path: ~/.claude.json
|
|
81
|
+
|
|
82
|
+
# Path to host's Claude directory
|
|
83
|
+
# claude_dir_path: ~/.claude
|
|
84
|
+
|
|
85
|
+
# Docker Compose service name for the main container
|
|
86
|
+
# service: app
|
|
87
|
+
|
|
88
|
+
# Git author identity inside the container
|
|
89
|
+
# git_email: claude_docker@localhost.com
|
|
90
|
+
# git_name: Claude Docker
|
|
91
|
+
|
|
92
|
+
# Non-root user inside the container
|
|
93
|
+
# container_user: appuser
|
|
94
|
+
|
|
95
|
+
# Script to run inside the container after setup (path relative to project root)
|
|
96
|
+
# Defaults to .aircon/aircon_init.sh — edit that file to add your setup steps.
|
|
97
|
+
# init_script: .aircon/aircon_init.sh
|
|
98
|
+
YAML
|
|
99
|
+
|
|
100
|
+
INIT_SCRIPT_TEMPLATE = <<~'BASH'
|
|
101
|
+
#!/bin/bash
|
|
102
|
+
# .aircon/aircon_init.sh
|
|
103
|
+
#
|
|
104
|
+
# This script runs inside the container after aircon completes its setup.
|
|
105
|
+
# It is invoked as a login shell (bash -l), so environment variables
|
|
106
|
+
# configured by aircon are available:
|
|
107
|
+
#
|
|
108
|
+
# GH_TOKEN / GITHUB_PERSONAL_ACCESS_TOKEN — GitHub personal access token
|
|
109
|
+
# CLAUDE_CODE_OAUTH_TOKEN — Claude Code OAuth token
|
|
110
|
+
# PATH — includes ~/.local/bin (claude, gh, etc.)
|
|
111
|
+
#
|
|
112
|
+
# The working directory is the repository root inside the container.
|
|
113
|
+
#
|
|
114
|
+
# Examples:
|
|
115
|
+
# npm install
|
|
116
|
+
# bundle install
|
|
117
|
+
# cp .env.example .env
|
|
118
|
+
BASH
|
|
119
|
+
|
|
120
|
+
DOCKERFILE_TEMPLATE = <<~'DOCKERFILE'
|
|
121
|
+
FROM ruby:4.0.1
|
|
122
|
+
|
|
123
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
|
124
|
+
|
|
125
|
+
# Add GitHub CLI repository
|
|
126
|
+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
127
|
+
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
128
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null
|
|
129
|
+
|
|
130
|
+
# Install dependencies
|
|
131
|
+
RUN apt-get update -qq && \
|
|
132
|
+
apt-get install -y --no-install-recommends \
|
|
133
|
+
bash \
|
|
134
|
+
build-essential \
|
|
135
|
+
git \
|
|
136
|
+
libpq-dev \
|
|
137
|
+
postgresql-client \
|
|
138
|
+
curl \
|
|
139
|
+
libvips \
|
|
140
|
+
bubblewrap \
|
|
141
|
+
socat \
|
|
142
|
+
nodejs \
|
|
143
|
+
gh \
|
|
144
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
145
|
+
|
|
146
|
+
# Make /bin/sh point to bash instead of dash (required for devcontainer features)
|
|
147
|
+
RUN ln -sf /bin/bash /bin/sh
|
|
148
|
+
|
|
149
|
+
# Ensure bash is the default shell for RUN commands
|
|
150
|
+
SHELL ["/bin/bash", "-c"]
|
|
151
|
+
|
|
152
|
+
# Create a non-root user
|
|
153
|
+
ARG USERNAME=appuser
|
|
154
|
+
ARG USER_UID=1000
|
|
155
|
+
ARG USER_GID=$USER_UID
|
|
156
|
+
ARG WORKSPACE_PATH=/workspace
|
|
157
|
+
|
|
158
|
+
RUN groupadd --gid $USER_GID $USERNAME \
|
|
159
|
+
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
|
|
160
|
+
&& apt-get update \
|
|
161
|
+
&& apt-get install -y sudo \
|
|
162
|
+
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
|
|
163
|
+
&& chmod 0440 /etc/sudoers.d/$USERNAME \
|
|
164
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
165
|
+
|
|
166
|
+
# Give the container user write access to the gem directory
|
|
167
|
+
RUN chown -R $USER_UID:$USER_GID /usr/local/bundle
|
|
168
|
+
|
|
169
|
+
RUN npm install -g @anthropic-ai/sandbox-runtime
|
|
170
|
+
RUN npm install -g yarn
|
|
171
|
+
RUN npm install -g playwright@1.58.1
|
|
172
|
+
RUN playwright install --with-deps chromium
|
|
173
|
+
|
|
174
|
+
COPY --chown=$USERNAME:$USERNAME . $WORKSPACE_PATH
|
|
175
|
+
|
|
176
|
+
USER $USERNAME
|
|
177
|
+
|
|
178
|
+
RUN playwright install chromium
|
|
179
|
+
|
|
180
|
+
WORKDIR $WORKSPACE_PATH
|
|
181
|
+
|
|
182
|
+
RUN bundle install
|
|
183
|
+
DOCKERFILE
|
|
184
|
+
|
|
185
|
+
COMPOSE_TEMPLATE = <<~'YAML'
|
|
186
|
+
services:
|
|
187
|
+
app:
|
|
188
|
+
build:
|
|
189
|
+
context: ..
|
|
190
|
+
dockerfile: .aircon/Dockerfile
|
|
191
|
+
args:
|
|
192
|
+
USERNAME: ${AIRCON_CONTAINER_USER:-appuser}
|
|
193
|
+
WORKSPACE_PATH: ${AIRCON_WORKSPACE_PATH:-/workspace}
|
|
194
|
+
ports:
|
|
195
|
+
- "${HOST_PORT:-3001}:3000"
|
|
196
|
+
command: sleep infinity
|
|
197
|
+
environment:
|
|
198
|
+
DATABASE_HOST: db
|
|
199
|
+
DATABASE_USER: ${AIRCON_APP_NAME:-app}
|
|
200
|
+
RAILS_ENV: development
|
|
201
|
+
RAILS_BIND: 0.0.0.0
|
|
202
|
+
depends_on:
|
|
203
|
+
db:
|
|
204
|
+
condition: service_started
|
|
205
|
+
|
|
206
|
+
db:
|
|
207
|
+
image: postgres:18
|
|
208
|
+
restart: unless-stopped
|
|
209
|
+
environment:
|
|
210
|
+
POSTGRES_USER: ${AIRCON_APP_NAME:-app}
|
|
211
|
+
POSTGRES_HOST_AUTH_METHOD: trust
|
|
212
|
+
POSTGRES_DB: ${AIRCON_APP_NAME:-app}_development
|
|
213
|
+
YAML
|
|
214
|
+
end
|
|
215
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aircon
|
|
4
|
+
module Commands
|
|
5
|
+
class Down
|
|
6
|
+
def initialize(config:)
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(name)
|
|
11
|
+
puts "Tearing down containers for '#{name}'..."
|
|
12
|
+
system("docker", "compose", "-p", name, "down", "-v", "--remove-orphans")
|
|
13
|
+
system("docker", "image", "prune", "-f")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
|
|
7
|
+
module Aircon
|
|
8
|
+
module Commands
|
|
9
|
+
class Up
|
|
10
|
+
def initialize(config:)
|
|
11
|
+
@config = config
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(name, branch:, port: "3001", detach: false)
|
|
15
|
+
container = Docker.find_container(project: name, service: @config.service)
|
|
16
|
+
|
|
17
|
+
if container
|
|
18
|
+
attach_existing(container, name, detach: detach)
|
|
19
|
+
else
|
|
20
|
+
start_new(name, branch, port, detach: detach)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def attach_existing(container, name, detach: false)
|
|
27
|
+
if detach
|
|
28
|
+
puts "Container for '#{name}' is already running: #{container}"
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "Attaching to existing container for '#{name}'..."
|
|
33
|
+
system("docker", "exec", "-it", container, "bash")
|
|
34
|
+
cleanup_if_last(container, name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start_new(name, branch, port, detach: false)
|
|
38
|
+
if @config.gh_token.nil? || @config.gh_token.to_s.empty?
|
|
39
|
+
warn "Warning: gh_token not configured. GitHub CLI (gh) will not be authenticated."
|
|
40
|
+
warn " Set gh_token in .aircon.yml if you want to use 'gh' commands."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
env = {
|
|
44
|
+
"HOST_PORT" => port.to_s,
|
|
45
|
+
"AIRCON_APP_NAME" => @config.app_name,
|
|
46
|
+
"AIRCON_CONTAINER_USER" => @config.container_user,
|
|
47
|
+
"AIRCON_WORKSPACE_PATH" => @config.workspace_path
|
|
48
|
+
}
|
|
49
|
+
system(env, "docker", "compose",
|
|
50
|
+
"-f", @config.compose_file,
|
|
51
|
+
"-p", name,
|
|
52
|
+
"up", "-d", "--build")
|
|
53
|
+
|
|
54
|
+
container = Docker.find_container(project: name, service: @config.service)
|
|
55
|
+
abort "Error: Could not find container after starting services." unless container
|
|
56
|
+
|
|
57
|
+
inject_claude_settings(container)
|
|
58
|
+
setup_container(container, branch)
|
|
59
|
+
run_init_script(container)
|
|
60
|
+
|
|
61
|
+
if detach
|
|
62
|
+
puts "Container started: #{container}"
|
|
63
|
+
return
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
system("docker", "exec", "-it", container, "bash")
|
|
67
|
+
cleanup_if_last(container, name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def inject_claude_settings(container)
|
|
71
|
+
Dir.mktmpdir("aircon_claude_settings") do |staging|
|
|
72
|
+
claude_config = File.expand_path(@config.claude_config_path)
|
|
73
|
+
claude_dir = File.expand_path(@config.claude_dir_path)
|
|
74
|
+
|
|
75
|
+
FileUtils.cp(claude_config, File.join(staging, ".claude.json")) if File.exist?(claude_config)
|
|
76
|
+
|
|
77
|
+
if File.directory?(claude_dir)
|
|
78
|
+
FileUtils.cp_r(claude_dir, File.join(staging, ".claude"))
|
|
79
|
+
else
|
|
80
|
+
FileUtils.mkdir_p(File.join(staging, ".claude"))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
home = @config.container_home
|
|
84
|
+
rewrite_paths(staging, home)
|
|
85
|
+
user = @config.container_user
|
|
86
|
+
system("docker", "cp", "#{File.join(staging, '.claude')}/.", "#{container}:#{home}/.claude")
|
|
87
|
+
system("docker", "cp", File.join(staging, ".claude.json"), "#{container}:#{home}/.claude.json")
|
|
88
|
+
system("docker", "exec", "-u", "root", container,
|
|
89
|
+
"bash", "-c", "chmod -R u+rwX #{home}/.claude #{home}/.claude.json && " \
|
|
90
|
+
"chown -R #{user}:#{user} #{home}/.claude #{home}/.claude.json")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def rewrite_paths(staging, container_home)
|
|
95
|
+
host_home = File.expand_path("~")
|
|
96
|
+
|
|
97
|
+
Dir.glob(File.join(staging, "**", "*"), File::FNM_DOTMATCH).each do |path|
|
|
98
|
+
next unless File.file?(path)
|
|
99
|
+
next unless File.readable?(path)
|
|
100
|
+
|
|
101
|
+
content = File.binread(path)
|
|
102
|
+
next unless content.valid_encoding?
|
|
103
|
+
next unless content.include?(host_home)
|
|
104
|
+
|
|
105
|
+
File.write(path, content.gsub(host_home, container_home))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def setup_container(container, branch)
|
|
110
|
+
home = @config.container_home
|
|
111
|
+
|
|
112
|
+
# Install Claude Code if not already present, and ensure it's on PATH for all shells
|
|
113
|
+
system("docker", "exec", container, "bash", "-c",
|
|
114
|
+
"command -v claude >/dev/null 2>&1 || curl -fsSL https://claude.ai/install.sh | bash")
|
|
115
|
+
system("docker", "exec", "-u", "root", container, "bash", "-c",
|
|
116
|
+
"grep -qF '#{home}/.local/bin' /etc/bash.bashrc 2>/dev/null || " \
|
|
117
|
+
"echo 'export PATH=\"#{home}/.local/bin:$PATH\"' >> /etc/bash.bashrc")
|
|
118
|
+
|
|
119
|
+
if @config.gh_token && !@config.gh_token.to_s.empty?
|
|
120
|
+
system("docker", "exec", "-u", "root", container, "bash", "-c",
|
|
121
|
+
"grep -qF 'export GH_TOKEN=' /etc/bash.bashrc 2>/dev/null || " \
|
|
122
|
+
"echo 'export GH_TOKEN=\"#{@config.gh_token}\"' >> /etc/bash.bashrc")
|
|
123
|
+
system("docker", "exec", "-u", "root", container, "bash", "-c",
|
|
124
|
+
"grep -qF 'export GITHUB_PERSONAL_ACCESS_TOKEN=' /etc/bash.bashrc 2>/dev/null || " \
|
|
125
|
+
"echo 'export GITHUB_PERSONAL_ACCESS_TOKEN=\"#{@config.gh_token}\"' >> /etc/bash.bashrc")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if @config.claude_code_oauth_token && !@config.claude_code_oauth_token.to_s.empty?
|
|
129
|
+
system("docker", "exec", "-u", "root", container, "bash", "-c",
|
|
130
|
+
"grep -qF 'export CLAUDE_CODE_OAUTH_TOKEN=' /etc/bash.bashrc 2>/dev/null || " \
|
|
131
|
+
"echo 'export CLAUDE_CODE_OAUTH_TOKEN=\"#{@config.claude_code_oauth_token}\"' >> /etc/bash.bashrc")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Configure git and create branch
|
|
135
|
+
system("docker", "exec", container, "git", "config", "--global", "user.email", @config.git_email)
|
|
136
|
+
system("docker", "exec", container, "git", "config", "--global", "user.name", @config.git_name)
|
|
137
|
+
# Configure git authentication for GitHub using the personal access token
|
|
138
|
+
if @config.gh_token && !@config.gh_token.to_s.empty?
|
|
139
|
+
authed = "https://x-access-token:#{@config.gh_token}@github.com/"
|
|
140
|
+
system("docker", "exec", container, "git", "config", "--global",
|
|
141
|
+
"url.#{authed}.insteadOf", "https://github.com/")
|
|
142
|
+
system("docker", "exec", container, "git", "config", "--global",
|
|
143
|
+
"url.#{authed}.insteadOf", "git@github.com:")
|
|
144
|
+
end
|
|
145
|
+
# Check if branch exists on remote; if so, check it out, otherwise create new
|
|
146
|
+
_, status = Open3.capture2("docker", "exec", container, "git", "ls-remote", "--heads", "origin", branch)
|
|
147
|
+
if status.success? && !_.strip.empty?
|
|
148
|
+
system("docker", "exec", container, "git", "fetch", "origin", branch)
|
|
149
|
+
system("docker", "exec", container, "git", "checkout", "-b", branch, "origin/#{branch}")
|
|
150
|
+
else
|
|
151
|
+
system("docker", "exec", container, "git", "fetch", "origin", "main")
|
|
152
|
+
system("docker", "exec", container, "git", "checkout", "-b", branch, "origin/main")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# If you have the official anthropic marketplace plugin installed, it will always make a call to the anthropic github repo on claude startup. It uses SSH, but it should be https for universal compatibility since its a public repository.
|
|
156
|
+
system("docker", "exec", container, "git", "config", "--global", "url.\"https://github.com/anthropics/\".insteadOf", "ssh://git@github.com/anthropics/")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def run_init_script(container)
|
|
160
|
+
return unless @config.init_script && !@config.init_script.to_s.empty?
|
|
161
|
+
|
|
162
|
+
script_path = File.expand_path(@config.init_script)
|
|
163
|
+
unless File.exist?(script_path)
|
|
164
|
+
warn "Warning: init_script '#{@config.init_script}' not found, skipping."
|
|
165
|
+
return
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
home = @config.container_home
|
|
169
|
+
remote_script = "#{home}/.aircon_init.sh"
|
|
170
|
+
system("docker", "cp", script_path, "#{container}:#{remote_script}")
|
|
171
|
+
system("docker", "exec", container, "bash", "-l", remote_script)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def cleanup_if_last(container, name)
|
|
175
|
+
out, = Open3.capture2("docker", "exec", container, "pgrep", "-x", "bash")
|
|
176
|
+
remaining = out.strip.lines.size
|
|
177
|
+
|
|
178
|
+
return unless remaining == 0
|
|
179
|
+
|
|
180
|
+
puts "Last session ended. Cleaning up..."
|
|
181
|
+
system("docker", "compose", "-p", name, "down", "-v", "--remove-orphans")
|
|
182
|
+
system("docker", "image", "prune", "-f")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aircon
|
|
4
|
+
module Commands
|
|
5
|
+
class Vscode
|
|
6
|
+
def initialize(config:)
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(name)
|
|
11
|
+
container = Docker.find_container(project: name, service: @config.service)
|
|
12
|
+
|
|
13
|
+
unless container
|
|
14
|
+
abort "Error: No running container found for project '#{name}'.\n" \
|
|
15
|
+
"Start one first with: aircon up #{name}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
hex_id = Docker.hex_encode_id(container)
|
|
19
|
+
folder_uri = "vscode-remote://attached-container+#{hex_id}#{@config.workspace_path}"
|
|
20
|
+
|
|
21
|
+
puts "Attaching VS Code to container #{container} for project '#{name}'..."
|
|
22
|
+
system("code", "--folder-uri", folder_uri)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "erb"
|
|
5
|
+
|
|
6
|
+
module Aircon
|
|
7
|
+
class Configuration
|
|
8
|
+
CONFIG_FILE = ".aircon/aircon.yml"
|
|
9
|
+
|
|
10
|
+
DEFAULTS = {
|
|
11
|
+
"compose_file" => ".aircon/docker-compose.yml",
|
|
12
|
+
"app_name" => nil,
|
|
13
|
+
"gh_token" => nil,
|
|
14
|
+
"claude_code_oauth_token" => nil,
|
|
15
|
+
"workspace_path" => nil,
|
|
16
|
+
"claude_config_path" => "~/.claude.json",
|
|
17
|
+
"claude_dir_path" => "~/.claude",
|
|
18
|
+
"service" => "app",
|
|
19
|
+
"git_email" => "claude_docker@localhost.com",
|
|
20
|
+
"git_name" => "Claude Docker",
|
|
21
|
+
"container_user" => "appuser",
|
|
22
|
+
"init_script" => ".aircon/aircon_init.sh"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
attr_reader :compose_file, :app_name, :gh_token, :claude_code_oauth_token, :workspace_path,
|
|
26
|
+
:claude_config_path, :claude_dir_path, :service, :git_email, :git_name,
|
|
27
|
+
:container_user, :init_script
|
|
28
|
+
|
|
29
|
+
def initialize(dir: Dir.pwd)
|
|
30
|
+
attrs = DEFAULTS.dup
|
|
31
|
+
config_path = File.join(dir, CONFIG_FILE)
|
|
32
|
+
|
|
33
|
+
if File.exist?(config_path)
|
|
34
|
+
raw = File.read(config_path)
|
|
35
|
+
rendered = ERB.new(raw).result
|
|
36
|
+
user_attrs = YAML.safe_load(rendered) || {}
|
|
37
|
+
attrs.merge!(user_attrs)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@compose_file = attrs["compose_file"]
|
|
41
|
+
@app_name = attrs["app_name"] || File.basename(dir)
|
|
42
|
+
@gh_token = attrs["gh_token"]
|
|
43
|
+
@claude_code_oauth_token = attrs["claude_code_oauth_token"]
|
|
44
|
+
@workspace_path = attrs["workspace_path"] || "/workspace"
|
|
45
|
+
@claude_config_path = attrs["claude_config_path"]
|
|
46
|
+
@claude_dir_path = attrs["claude_dir_path"]
|
|
47
|
+
@service = attrs["service"]
|
|
48
|
+
@git_email = attrs["git_email"]
|
|
49
|
+
@git_name = attrs["git_name"]
|
|
50
|
+
@container_user = attrs["container_user"]
|
|
51
|
+
@init_script = attrs["init_script"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def container_home
|
|
55
|
+
@container_user == "root" ? "/root" : "/home/#{@container_user}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Aircon
|
|
6
|
+
module Docker
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def find_container(project:, service:)
|
|
10
|
+
out, _, status = Open3.capture3(
|
|
11
|
+
"docker", "ps", "-q",
|
|
12
|
+
"--filter", "label=com.docker.compose.project=#{project}",
|
|
13
|
+
"--filter", "label=com.docker.compose.service=#{service}"
|
|
14
|
+
)
|
|
15
|
+
return nil unless status.success?
|
|
16
|
+
|
|
17
|
+
id = out.strip.lines.first&.strip
|
|
18
|
+
id.nil? || id.empty? ? nil : id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def hex_encode_id(container_id)
|
|
22
|
+
container_id.each_byte.map { |b| format("%02x", b) }.join
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/aircon.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: aircon
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Philip Nguyen
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.13'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.13'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: fakefs
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
description: Aircon spins up one Docker Compose environment per git branch, injects
|
|
55
|
+
Claude Code credentials, and attaches an interactive shell or VS Code.
|
|
56
|
+
email:
|
|
57
|
+
- 5519675+philipqnguyen@users.noreply.github.com
|
|
58
|
+
executables:
|
|
59
|
+
- aircon
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- LICENSE.txt
|
|
64
|
+
- README.md
|
|
65
|
+
- exe/aircon
|
|
66
|
+
- lib/aircon.rb
|
|
67
|
+
- lib/aircon/cli.rb
|
|
68
|
+
- lib/aircon/commands/down.rb
|
|
69
|
+
- lib/aircon/commands/up.rb
|
|
70
|
+
- lib/aircon/commands/vscode.rb
|
|
71
|
+
- lib/aircon/configuration.rb
|
|
72
|
+
- lib/aircon/docker.rb
|
|
73
|
+
- lib/aircon/version.rb
|
|
74
|
+
homepage: https://github.com/creativesorcery/aircon
|
|
75
|
+
licenses:
|
|
76
|
+
- MIT
|
|
77
|
+
metadata: {}
|
|
78
|
+
rdoc_options: []
|
|
79
|
+
require_paths:
|
|
80
|
+
- lib
|
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: 3.3.0
|
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
requirements: []
|
|
92
|
+
rubygems_version: 4.0.5
|
|
93
|
+
specification_version: 4
|
|
94
|
+
summary: Manage Docker-based isolated Claude Code development containers
|
|
95
|
+
test_files: []
|