kenkeep Index
Bundle OKF 0.1 · 0 conceitos · e0ipso/devcontainer-feature-ai-assistant
Open source Repository Open in the app JSON README (API)
About
# kenkeep Index
> kenkeep navigation: the injected body above is the root index node, the top-level catalog of branches and root-level leaves. Do not expect the whole knowledge base here; descend on demand. Read the root index node, pick one or more branches whose intent and tags match your task (several branches can be relevant), and read those branch `index.md` nodes. Descend further only where the task needs it, opening only the leaves you have confirmed are relevant. Follow each leaf's `relates_to` and `depends_on` cross edges to reach related leaves in other branches. You decide how deep to go per branch.
> This index only orients you; leaves hold the durable guidance. Open at least one relevant leaf before acting.
## Subfolders
_None._
## Conventions (how we build)
- Open [**CI auto-publishes features to GHCR on every main push**](practice-ci-auto-publishes-features-on-main.md) to learn about: CI publishes every feature to GHCR on push to main; semantic-release owns the versi
Details
- Kind
- OKF bundles
- Topic
- AI, RAG & memory
- Publisher
- e0ipso
- Origin
- okf_github
- Category
- dados
- Version
- 0.1
- Last push
- 2026-08-05T09:02:36Z
- Repository state
- ativo
- Language
- Shell
- License
- MIT
- Added
- 2026-09-09 19:04:11
- Updated
- 2026-09-09 19:04:11
- Origin id
e0ipso/devcontainer-feature-ai-assistant:.ai/kenkeep/nodes/index.md
README
# devcontainer-feature-ai-assistant
A collection of [dev container Features](https://containers.dev/implementors/features/) that install AI coding assistant CLIs into your dev container.
| Feature | CLI installed | Published at |
| ------- | ------------- | ------------ |
| [`claude`](src/claude/README.md) | [Claude Code](https://claude.ai/) | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude:1` |
| [`codex`](src/codex/README.md) | [OpenAI Codex CLI](https://github.com/openai/codex) | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/codex:1` |
| [`copilot`](src/copilot/README.md) | [GitHub Copilot CLI](https://github.com/github/copilot-cli) | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/copilot:1` |
| [`cursor`](src/cursor/README.md) | [Cursor CLI](https://cursor.com/) (`agent`) | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/cursor:1` |
| [`opencode`](src/opencode/README.md) | [OpenCode](https://opencode.ai/) | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/opencode:1` |
| [`t3`](src/t3/README.md) | [T3 Code](https://github.com/pingdotgg/t3code) | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/t3:1` |
Plus four opt-in companions that forward host credentials into the container. Each depends on its CLI feature, so listing the `*-auth` feature alone installs both:
| Feature | Forwards | Published at |
| ------- | -------- | ------------ |
| [`claude-auth`](src/claude-auth/README.md) | `~/.claude/.credentials.json` | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude-auth:1` |
| [`codex-auth`](src/codex-auth/README.md) | `~/.codex/auth.json` | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/codex-auth:1` |
| [`cursor-auth`](src/cursor-auth/README.md) | `~/.config/cursor/auth.json` | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/cursor-auth:1` |
| [`opencode-auth`](src/opencode-auth/README.md) | `~/.local/share/opencode/auth.json` | `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/opencode-auth:1` |
There is no `t3-auth` (t3 keeps its state on a named volume, which survives rebuilds) and no `copilot-auth` (Copilot authenticates through `gh` and has no credential file).
---
## Using a feature in your project
Add a `features` block to your project's `.devcontainer/devcontainer.json`:
```jsonc
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
// Pick one or more:
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude:1": {},
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/codex:1": {},
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/copilot:1": {},
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/cursor:1": {},
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/opencode:1": {},
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/t3:1": {}
// To also forward your host credentials, use the matching *-auth feature
// instead — it installs the CLI too. See "Forwarding host authentication".
// "ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude-auth:1": {}
}
}
```
> **Node requirement.** The `codex`, `copilot`, `opencode`, and `t3` features install via npm and require Node.js in the base image (or the `ghcr.io/devcontainers/features/node` feature installed before them). The `claude` and `cursor` features use shell installers that also declare `installsAfter: node` — use a Node base image or add the Node feature when composing with other features.
### Recommended base image
`node:24` or `mcr.microsoft.com/devcontainers/universal:2` cover all features out of the box.
---
## Forwarding host authentication
Use the `*-auth` features. Add one entry and you get the CLI, the host mount, and the seeding script together:
```jsonc
{
"image": "node:24",
"features": {
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude-auth:1": {}
}
}
```
On first start the feature copies the host credential file into place — only when the destination does not already exist — and `chmod 600`s it. Container-side credentials drift freely afterwards, and a host with nothing to forward is a no-op.
### Why this is a separate feature
A mount has exactly one on/off switch: whether the feature appears in your `features` list. Feature options are delivered to `install.sh` as `_BUILD_ARG_*` environment variables, and no option-based substitution variable exists, so a static `mounts` block can never see an option. Putting the mount in `claude:1` would make host-home forwarding mandatory and undeclinable; the feature therefore *is* the unit of opt-in.
Keeping the mount and the script in one artifact is the other half of the reasoning. When the mount lived in your `devcontainer.json` and the script lived in the CLI feature, the two could version-skew — and the stale side can never warn you, because it does not contain the code that would warn. The result was a silent logout.
### Prerequisite: the host directory must exist
Each `*-auth` feature bind-mounts a host *directory* (`~/.claude`, `~/.codex`, `~/.config/cursor`, `~/.local/share/opencode`) at `/cred-seed/<tool>`. The dev containers CLI passes mounts to Docker as `--mount`, which **refuses a missing bind source** instead of creating it the way `-v` does:
```text
docker: Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /home/you/.claude
```
So a contributor who has never run the tool on their host cannot start the container until they sign in on the host or run `mkdir -p ~/.claude`. That is loud rather than silent, but it is a real prerequisite for everyone sharing the project, and there is no fallback path — without the directory the container does not start.
The *directory* is mounted rather than the credential file because the directory exists for anyone who has used the tool at all, while the credential file may legitimately be absent; and because a runtime that does create missing bind sources creates them as directories, which on top of a credential file would break the host install.
### Windows: `HOME` may be unset
`${localEnv:HOME}` resolves against the environment of the process running the CLI, and **a missing variable substitutes to the empty string, not to a literal placeholder**. Under Windows `cmd`/PowerShell `HOME` is normally unset (Windows uses `USERPROFILE`), so `${localEnv:HOME}/.claude` becomes `/.claude` — a root-level path. Git Bash does set `HOME`. The two-argument default form `${localEnv:VAR:default}` exists, but no single expression covers HOME-or-USERPROFILE, so override the mount instead.
### Overriding a feature's mount
A feature mount can be re-pointed but never removed. Mounts are deduplicated by `target`, keeping the **last** occurrence, and a `devcontainer.json`'s own `mounts` are appended after the features', so declaring the same target wins:
```jsonc
"mounts": [
"source=${localEnv:USERPROFILE}/.claude,target=/cred-seed/claude,readonly,type=bind,consistency=cached"
]
```
Use this to fix an unset `HOME`, to point at a different host directory, or to add `readonly` — feature-declared mounts are objects with only `type`, `source` and `target`, so they cannot express `readonly` themselves and the host directory is mounted read-write. To turn forwarding off, drop the feature.
### Lock files pin the old behaviour
If your project has a `.devcontainer/devcontainer-lock.json`, it pins exact feature versions, and **no change on this side can override it**. After adding or changing an auth feature, run `devcontainer upgrade` (or delete the pinned entries) — otherwise you silently keep whatever the lock file recorded, with no error and no warning. This is exactly how the original credential-forwarding outage happened: a lock file pinned CLI feature versions from before credential seeding shipped, so the consumer's mounts were correct and nothing read them.
Config seeding (`seedConfig`) and other mounts are documented per feature: [`claude`](src/claude/README.md), [`codex`](src/codex/README.md), [`cursor`](src/cursor/README.md), [`opencode`](src/opencode/README.md), [`t3`](src/t3/README.md).
---
## Feature details
### `claude` — Claude Code CLI
Installs the [Claude Code](https://claude.ai/) CLI (`claude`) via the official shell installer. The binary lands on a shared, user-writable npm prefix (`/usr/local/share/npm-global/bin`) so it is available to the non-root remote user at runtime.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude:1": {}
```
After the container starts, run `claude` to authenticate interactively — or add [`claude-auth`](src/claude-auth/README.md) to forward the credentials you already have on the host.
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `updateOnPostStart` | `boolean` | `true` | Re-run the Claude Code installer during `postStart` to update to the latest version. |
| `seedConfig` | `boolean` | `true` | Seed `~/.claude/settings.json` from a host mount or image-baked default on first start only. |
---
### `codex` — OpenAI Codex CLI
Installs the [Codex CLI](https://github.com/openai/codex) (`codex`) via `npm install -g @openai/codex`. Requires Node.js in the base image.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/codex:1": {}
```
Set `OPENAI_API_KEY` in `remoteEnv`, or run `codex` interactively after the container starts to sign in — or add [`codex-auth`](src/codex-auth/README.md) to forward the credentials you already have on the host.
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `updateOnPostStart` | `boolean` | `true` | Run `npm install -g @openai/codex` during `postStart` to update to the latest version. |
| `seedConfig` | `boolean` | `true` | Seed `~/.codex/config.toml` from a host mount or image-baked default on first start only. |
---
### `copilot` — GitHub Copilot CLI
Installs the [`@github/copilot`](https://www.npmjs.com/package/@github/copilot) npm package globally.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/copilot:1": {}
```
Requires Node.js in the base image. Authenticate with GitHub (`gh auth login` on the host, or inside the container). Optionally bind-mount `~/.config/gh` to reuse host credentials.
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `updateOnPostStart` | `boolean` | `true` | Run `npm install -g @github/copilot` during `postStart` to update to the latest version. |
---
### `cursor` — Cursor CLI
Installs the [Cursor](https://cursor.com/) agentic CLI via the official shell installer. The installed command is `agent`, not `cursor`.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/cursor:1": {}
```
After the container starts, run `agent login` to authenticate interactively, or set `CURSOR_API_KEY` in `remoteEnv` — or add [`cursor-auth`](src/cursor-auth/README.md) to forward the credentials you already have on the host.
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `updateOnPostStart` | `boolean` | `true` | Re-run the Cursor installer during `postStart` to update to the latest version. |
| `seedConfig` | `boolean` | `true` | Seed `~/.cursor/cli-config.json` from a host mount or image-baked default on first start only. |
---
### `opencode` — OpenCode CLI
Installs [OpenCode](https://opencode.ai/) (`opencode`) via `npm install -g opencode-ai`. Requires Node.js in the base image.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/opencode:1": {}
```
After the container starts, run `opencode` to authenticate interactively — or add [`opencode-auth`](src/opencode-auth/README.md) to forward the credentials you already have on the host.
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `updateOnPostStart` | `boolean` | `true` | Run `npm install -g opencode-ai` during `postStart` to update to the latest version. |
| `seedConfig` | `boolean` | `true` | Seed `~/.config/opencode/opencode.json` from a host mount or image-baked default on first start only. |
---
### `t3` — t3 CLI
Installs the [T3 Code](https://github.com/pingdotgg/t3code) CLI globally via npm and writes a pre-seeded `settings.json` to `/usr/local/share/devcontainer-feature-ai-assistant/t3/settings.json` at build time. On `postStart`, seeds `$T3CODE_HOME/userdata/settings.json` when absent (host mount or the baked default). Requires Node.js in the base image.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/t3:1": {
"version": "latest",
"textGenerationModelSelectionInstanceId": "claudeAgent",
"textGenerationModelSelectionModel": "claude-sonnet-4-6",
"enableAssistantStreaming": true,
"providersClaudeAgentEnabled": true,
"providersCodexEnabled": false
}
```
See [`src/t3/devcontainer-feature.json`](src/t3/devcontainer-feature.json) for the full list of options and their defaults.
---
### `claude-auth`, `codex-auth`, `cursor-auth`, `opencode-auth` — host credential forwarding
Each forwards one tool's host credentials into the container, shipping the bind mount and the seeding script as a single versioned artifact. Each declares `dependsOn` on its CLI feature, so one entry installs both.
```jsonc
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude-auth:1": {}
```
| Feature | Host source (must exist) | Mount target | Live destination |
| ------- | ------------------------ | ------------ | ---------------- |
| `claude-auth` | `~/.claude` | `/cred-seed/claude` | `$HOME/.claude/.credentials.json` |
| `codex-auth` | `~/.codex` | `/cred-seed/codex` | `$HOME/.codex/auth.json` |
| `cursor-auth` | `~/.config/cursor` | `/cred-seed/cursor` | `$HOME/.config/cursor/auth.json` |
| `opencode-auth` | `~/.local/share/opencode` | `/cred-seed/opencode` | `$HOME/.local/share/opencode/auth.json` |
No options — the feature's presence in `features` is the only switch a mount can have. See [Forwarding host authentication](#forwarding-host-authentication) for the prerequisites and caveats, and the per-feature READMEs for the full reasoning: [`claude-auth`](src/claude-auth/README.md), [`codex-auth`](src/codex-auth/README.md), [`cursor-auth`](src/cursor-auth/README.md), [`opencode-auth`](src/opencode-auth/README.md).
---
## Using multiple features together
Features are independent and compose freely. A typical full-stack AI setup:
```jsonc
{
"image": "node:24",
"features": {
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/claude:1": {},
"ghcr.io/e0ipso/devcontainer-feature-ai-assistant/t3:1": {
"providersClaudeAgentEnabled": true,
"textGenerationModelSelectionInstanceId": "claudeAgent"
}
}
}
```
Each feature prepends `/usr/local/share/npm-global/bin` to `PATH` via `containerEnv`, so extra `remoteEnv` wiring is usually unnecessary.
---
## Publishing this repo
### One-time setup
1. Fork or push this repo to GitHub.
2. In **Settings → Actions → General**, enable **Read and write permissions** for the `GITHUB_TOKEN` (the release workflow needs `packages: write` and `contents: write`).
3. If the release workflow cannot set package visibility via the API, open each package under **Packages**, go to **Package settings**, and set visibility to **Public** (required for unauthenticated `devcontainer` pulls).
### Publish features
After CI passes on `main`, the **CI** workflow publishes automatically. You can also run it manually from the Actions tab. It will:
1. Push each `src/<feature>/` directory as an OCI image to `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/<feature>`.
2. Attempt to set each GHCR package visibility to **public**, then verify anonymous pulls succeed.
Feature READMEs in `src/<feature>/README.md` are maintained by hand and are **not** overwritten by the release workflow.
Until the release workflow has run at least once, `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/<feature>:1` references will fail with "Could not resolve Feature manifest".
### Versioning
Each feature is versioned independently by [semantic-release](https://semantic-release.gitbook.io/), driven by [Conventional Commits](https://www.conventionalcommits.org/) touching that feature's `src/<feature>/` directory:
- `fix:` → patch release
- `feat:` → minor release
- `BREAKING CHANGE:` in the commit body (or `!` after the type/scope) → major release
- other prefixes (`chore:`, `docs:`, `ci:`, `test:`, …) → no release
The **CI** workflow's `release` job runs `semantic-release` once per feature on every push to `main`. For each feature with a releasable commit since its last tag, it:
1. Writes the new version into `src/<feature>/devcontainer-feature.json`.
2. Commits that change and pushes a `feature_<id>_<version>` tag.
3. Publishes a GitHub Release with generated notes.
`"version"` in `devcontainer-feature.json` should not be bumped by hand — semantic-release owns that field. It only reads commits scoped to a feature's own directory, so a commit that only touches `src/codex/` will never version-bump `src/cursor/`.
Once versions are updated, the existing publish step pushes each `src/<feature>/` directory as an OCI image to `ghcr.io/e0ipso/devcontainer-feature-ai-assistant/<feature>` and tags the major version (`:1`) so pinned consumers pick up the update automatically.
---
## Local development & testing
### Prerequisites
```bash
npm install -g @devcontainers/cli
```
The `*-auth` features bind-mount host credential directories, and Docker refuses a bind mount whose source is missing. Create them once before running their tests, or those containers will not start:
```bash
mkdir -p ~/.claude ~/.codex ~/.config/cursor ~/.local/share/opencode
```
CI does the same thing for the same reason (see `.github/workflows/ci.yaml`). Each `*-auth` feature's `seed_credentials` scenario redirects `/cred-seed/<tool>` to a throwaway volume, so tests never write to your real credential directories.
### Run all tests
```bash
# All features, all scenarios
devcontainer features test .
# One feature only
devcontainer features test -f claude .
# Specific base image
devcontainer features test -f t3 -i node:24 .
```
### Validate feature JSON
```bash
devcontainer features package -f -o /tmp/devcontainer-feature-package ./src
```
### Project structure
```
src/
<feature>/
devcontainer-feature.json # metadata & options schema
install.sh # runs as root during image build
README.md # hand-maintained feature docs
test/
<feature>/
test.sh # default option smoke test
scenarios.json # named scenario matrix
_global/
scenarios.json # multi-feature integration tests
.github/
workflows/
ci.yaml # validate, test, semantic-release, and publish to GHCR on main
scripts/
list-feature-ids.sh # discover src/* feature ids
release-features.sh # run semantic-release once per feature, in order
set-feature-version.mjs # format-preserving version writer used by release.config.mjs
post-publish-ghcr.sh # set GHCR visibility public and verify pulls
semantic-release/
feature-scope.mjs # scopes commit-analyzer/release-notes-generator to one feature's directory
release.config.mjs # shared semantic-release config, parameterized by $FEATURE_ID
package.json # devDependencies for semantic-release tooling (not a published package)
```