Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .atlas
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"type": "workspace",
"description": "Environment variable management tool: onenv-api REST service + onenv-manager admin frontend.",
"description": "1Password-backed environment variable manager. Single CLI (onenv) that replaces .env files.",
"archived": false
}
16 changes: 0 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,3 @@ jobs:
- run: bun run biome check src/
- run: bunx tsc --noEmit
- run: bun run test run

api:
name: onenv-api
runs-on: ubuntu-latest
defaults:
run:
working-directory: onenv-api
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run biome check src/
- run: bunx tsc --noEmit
- run: bun run test run
37 changes: 6 additions & 31 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t

## Overview

Monorepo with two independent TypeScript sub-projects that manage secrets stored in 1Password via the `op` CLI. A shared `install.ts` bootstraps both.
A single TypeScript CLI that stores environment variables in 1Password via the `op` CLI. A root `install.ts` script bootstraps the project (deps, build, link, optional `.env` migration).

- **onenv-manager** — CLI + interactive TUI for managing secrets (Commander, @clack/prompts)
- **onenv-api** — HTTP API (Express) for agent-driven secret access with permission brokering

Both have a parallel `onenv-client.ts` / `manager-service.ts` / `state-store.ts` architecture (duplicated, not shared as a package — copies have diverged).

## Commands

### onenv-manager

```bash
cd onenv-manager
just check # loc-check + lint + typecheck + test (preferred)
Expand All @@ -27,19 +22,6 @@ bun run build # tsc → dist/

Single test: `bun run vitest run src/lib/state-store.test.ts`

### onenv-api

```bash
cd onenv-api
bun run biome check --write src/ # lint + format
bunx tsc --noEmit # typecheck
bun run test # tests (vitest)
bun run dev # dev with watch
bun run build # tsc → dist/
```

Single test: `bun run vitest run src/lib/config.test.ts`

### Installer

```bash
Expand All @@ -52,7 +34,7 @@ bun run install.ts # interactive setup wizard (requires op CLI)
onenv-client.ts → spawns `op` CLI, CRUD on 1Password items
manager-service.ts → orchestration: combines onenv-client + state-store
(exported functions consumed by CLI/API routes)
state-store.ts → disabled-key tracking (~/.config/onenv-manager/state.json)
```

Expand All @@ -65,20 +47,13 @@ state-store.ts → disabled-key tracking (~/.config/onenv-manager/state.j
- Secret stored in the `credential` field
- Imported JSON files (via `onenv import`) carry extra STRING fields `group`, `path`, `type` per leaf for round-trip via `onenv build-file`

### onenv-api specifics

- `src/server.ts` — Express routes, Zod validation, auth middleware (`x-onenv-token` header), pre-auth (per-IP, all methods) and post-auth (per-token, mutating only) rate limiters
- `src/lib/permission.ts` — macOS AppleScript permission dialog
- `src/lib/config.ts` — loads env vars with defaults; `AGENT_API_TOKEN` is required
- All mutating endpoints require permission approval before executing

### onenv-manager specifics

- `src/cli.ts` — Commander-based CLI entry point (set, edit, unset, list [--groups], disable, enable, init, run [--file], export, import, build-file, prime, tui)
- `src/commands/tui.ts` — interactive @clack/prompts TUI loop
- `src/commands/prime.ts` — agent primer; XML by default, JSON when `--json` or piped
- `src/commands/prime-data.ts` + `prime-data-cli.ts` + `prime-data-api.ts` — primer data shape
- `src/commands/prime-xml.ts` — flat XML renderer (top-level tags with prose bodies)
- `src/commands/prime.ts` — agent primer dispatcher (XML on TTY, Markdown when piped, JSON with `--json`)
- `src/commands/prime-data.ts` + `prime-data-cli.ts` — primer data shape
- `src/commands/prime-xml.ts` / `prime-md.ts` — primer renderers
- `src/commands/import.ts` — flatten a JSON file into onenv keys with `group`/`path`/`type` metadata
- `src/commands/build-file.ts` — reassemble a grouped JSON file from stored leaves
- `src/commands/run-files.ts` — `--file group:VAR` materialization + child-process cleanup helpers
Expand All @@ -87,6 +62,7 @@ state-store.ts → disabled-key tracking (~/.config/onenv-manager/state.j
- `src/lib/project-config.ts` — `.onenv.json` read/write for per-project setup
- `src/lib/errors.ts` — structured CLI error types
- `src/lib/output.ts` — JSON/text output formatting
- `src/lib/permission.ts` — opt-in AppleScript dialog gating mutations when `ONENV_CONFIRM_MUTATIONS=1`
- `src/lib/ref-store.ts` — @-ref shorthand storage (positional: `@1`, `@2`, `@last`)
- `src/lib/op-token.ts` — `op://` reference resolution + cache at `~/.config/onenv-manager/op-token`
- `src/lib/validation.ts` — namespace + key regex/length validation
Expand All @@ -99,5 +75,4 @@ state-store.ts → disabled-key tracking (~/.config/onenv-manager/state.j
- Biome for linting/formatting (not ESLint/Prettier)
- Vitest for testing, co-located test files (`*.test.ts`)
- Strict TypeScript, `.js` extensions in imports
- `onenv-client.ts` is duplicated across both projects (not a shared package; copies have diverged)
- State file path: `~/.config/onenv-manager/state.json` (or `$XDG_CONFIG_HOME`)
107 changes: 19 additions & 88 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@
| [Bun](https://bun.sh) | Yes | `curl -fsSL https://bun.sh/install \| bash` |
| [1Password CLI](https://developer.1password.com/docs/cli/) | Yes | `brew install 1password-cli` |
| [1Password desktop app](https://1password.com/downloads) | Yes | Required for biometric unlock / CLI integration |
| [just](https://github.com/casey/just) | Optional | `brew install just` (task runner for manager) |
| macOS | Yes | Desktop permission dialog uses AppleScript |
| [just](https://github.com/casey/just) | Optional | `brew install just` (task runner) |
| macOS | Optional | Only required to use the `ONENV_CONFIRM_MUTATIONS` desktop dialog |

## Clone & install

```bash
git clone <repo-url> onenv && cd onenv

cd onenv-manager && bun install && cd ..
cd onenv-api && bun install && cd ..
bun install.ts
```

## Build
The installer walks through dependency install, build, vault creation, and
linking `onenv` system-wide. If you prefer manual steps:

```bash
cd onenv-manager && bun run build && cd ..
cd onenv-api && bun run build && cd ..
cd onenv-manager && bun install && bun run build && bun link && cd ..
```

## 1Password setup
Expand All @@ -35,36 +33,27 @@ cd onenv-api && bun run build && cd ..
op vault create onenv
```

## Configure — agent-api

Copy the example env file and fill in values:

```bash
cp onenv-api/.env.example onenv-api/.env
```
## Configure

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `AGENT_API_TOKEN` | Yes | — | API authentication token |
| `API_HOST` | No | `127.0.0.1` | Server bind address |
| `API_PORT` | No | `4317` | Server port |
| `PERMISSION_TIMEOUT_MS` | No | `120000` | Permission request timeout (ms) |
| `ONENV_VAULT` | No | `onenv` | 1Password vault name |
| `ONENV_CATEGORY` | No | `API Credential` | 1Password item category |
| `ONENV_CONFIRM_MUTATIONS` | No | unset | Set to `1` on macOS to gate `set` / `edit` / `unset` / `import` behind an AppleScript desktop dialog |
| `PERMISSION_TIMEOUT_MS` | No | `120000` | Confirm-dialog timeout in ms (only used when `ONENV_CONFIRM_MUTATIONS=1`) |
| `OP_SERVICE_ACCOUNT_TOKEN` | No | — | Skips biometric prompts (see below) |

### Avoiding constant 1Password approvals

By default `op` requires biometric unlock (Touch ID / desktop app) for every
secret read. Agents calling `onenv-api` repeatedly — and any process invoking
the `onenv` CLI — will trigger prompt spam.
secret read. Any process invoking the `onenv` CLI will trigger prompt spam.

To run headless / no-prompts, create a **1Password service account**:

1. 1Password web → **Developer** → **Service Accounts** → **Create**.
2. Grant **read + write** access to the `onenv` vault.
3. Set `OP_SERVICE_ACCOUNT_TOKEN` for both the API (`onenv-api/.env`) and the
shell that runs the `onenv` CLI. Two forms accepted:
3. Set `OP_SERVICE_ACCOUNT_TOKEN` in the shell that runs the `onenv` CLI.
Two forms accepted:

```
# literal — fastest, but plaintext on disk
Expand All @@ -75,8 +64,7 @@ To run headless / no-prompts, create a **1Password service account**:
OP_SERVICE_ACCOUNT_TOKEN=op://Personal/<item-id>/credential
```

4. Restart the API. Startup log should show `1password auth: service-account`.
For the CLI, the first command resolves the reference; subsequent commands
4. The first `onenv` command resolves the reference; subsequent commands
read the cached literal at `~/.config/onenv-manager/op-token` (mode 0600).

Tradeoffs:
Expand All @@ -87,24 +75,12 @@ Tradeoffs:
let the next failed `op` call self-invalidate the cache) so the CLI
re-resolves the new value.

> **Note:** The code reads env vars via `process.env` directly — there is no built-in dotenv loader. Use Bun's `--env-file .env` flag or Node 20+'s `--env-file` to load `.env` files:
>
> ```bash
> bun --env-file .env run start
> ```

## Configure — manager (optional)

```bash
cp onenv-manager/.env.example onenv-manager/.env
```

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `ONENV_VAULT` | No | `onenv` | 1Password vault name |
| `ONENV_CATEGORY` | No | `API Credential` | 1Password item category |
> **Note:** The code reads env vars via `process.env` directly — there is no
> built-in dotenv loader. If you keep settings in a `.env`, source it from
> your shell or use `onenv export <ns> -- <cmd>` to inject them into the
> child process.

## Link CLI globally (manager)
## Link CLI globally

```bash
cd onenv-manager && bun link
Expand All @@ -114,8 +90,6 @@ This makes `onenv` available system-wide.

## Run & verify

### Manager

```bash
# Development mode (with watch)
cd onenv-manager && bun run dev
Expand All @@ -126,55 +100,12 @@ just dev
onenv list
```

### Agent API

```bash
cd onenv-api

# Development
bun run dev

# Production
bun run start

# Verify
curl http://127.0.0.1:4317/health
```

### Auth headers (agent-api)

All endpoints except `/health` require:

```
x-onenv-token: <your-AGENT_API_TOKEN>
```

Optional agent identifier for permission prompts:

```
x-agent-name: my-agent
```

## State file location

The manager stores disabled-key state at:
The CLI stores disabled-key state at:

```
~/.config/onenv-manager/state.json
```

Or `$XDG_CONFIG_HOME/onenv-manager/state.json` if `XDG_CONFIG_HOME` is set.

## API endpoints

| Method | Path | Auth | Permission |
|--------|------|------|------------|
| GET | `/health` | No | No |
| GET | `/v1/namespaces` | Yes | No |
| GET | `/v1/namespaces/:namespace/vars` | Yes | No |
| POST | `/v1/vars/set` | Yes | Yes |
| POST | `/v1/vars/edit` | Yes | Yes |
| POST | `/v1/vars/unset` | Yes | Yes |
| POST | `/v1/vars/disable` | Yes | Yes |
| POST | `/v1/vars/enable` | Yes | Yes |
| POST | `/v1/env/export` | Yes | Yes |
Loading
Loading