Back to the catalog

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)
```

More