Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f27b228
Add multi-environment auth with encrypted v2 config.
tarik02 Jun 22, 2026
46feb4a
Mark node fs/path boundaries in tests and vite config.
tarik02 Jun 22, 2026
c9f0713
Refactor integration tests to use Effect platform services.
tarik02 Jun 22, 2026
26850f3
Harden config I/O with Schema JSON and typed error mapping.
tarik02 Jun 22, 2026
39e2a7d
Extract credential cipher into swappable layers wired at entrypoint.
tarik02 Jun 22, 2026
b0c58ae
Resolve FileSystem via yield* in hardenPrivateFileMode.
tarik02 Jun 22, 2026
a4b6ffd
Propagate keyring load failures as tagged errors for consumer fallback.
tarik02 Jun 22, 2026
c2e53ce
Resolve config paths via Effect services instead of passing deps as a…
tarik02 Jun 22, 2026
f4bc2cd
Yield config services in codec, persist, and migration helpers.
tarik02 Jun 22, 2026
93490bc
Align config modules with Effect service conventions.
tarik02 Jun 22, 2026
e06d2ca
Simplify config exports and keyring error handling.
tarik02 Jun 22, 2026
2243bbd
Export config types from types.ts directly.
tarik02 Jun 22, 2026
95e59dd
Add keystore factory service with keyring and file backends.
tarik02 Jun 22, 2026
b6e3292
Remove thin credential crypto wrapper functions.
tarik02 Jun 22, 2026
01adf62
Remove more unnecessary passthrough wrappers.
tarik02 Jun 22, 2026
68f1f75
Harden config error handling across auth, migration, and keystore.
tarik02 Jun 22, 2026
1360345
Map UrlError to ConfigError at all config boundaries.
tarik02 Jun 22, 2026
ce3f01c
Refactor tests to use shared shims and smaller focused suites.
tarik02 Jun 22, 2026
a088f8b
Restructure config/cli modules and tighten the library export surface.
tarik02 Jun 22, 2026
13ec31f
Register env command and guard default environment switching.
tarik02 Jun 22, 2026
053cb5d
Add changeset and update docs for multi-environment auth.
tarik02 Jun 22, 2026
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
44 changes: 44 additions & 0 deletions .changeset/multi-environment-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
"t3code-cli": minor
---

Add multi-environment auth with encrypted credential storage.

### Features

- Store multiple named auth environments in `~/.config/t3cli/config.json` (or `$XDG_CONFIG_HOME/t3cli/config.json`).
- Encrypt tokens at rest with AES-256-GCM; bind ciphertext to environment name, URL, and `local` flag via additional authenticated data.
- Store the master key in the OS keyring when available (`@napi-rs/keyring`), otherwise in `~/.config/t3cli/key` with `0600` permissions.
- Migrate legacy v1 flat config (plaintext `url`/`token`) to encrypted v2 on first read; rewrite `config.json` automatically.
- Add global `--environment <name>` flag and `T3CLI_ENV` for per-command environment selection.
- Add `T3CODE_URL` + `T3CODE_TOKEN` override when both are set.
- Add `env list`, `env use`, and `env remove` commands for managing stored environments.
- Add `--name` and `--replace` flags to `auth pair` and `auth local`.
- Verify credential decryption before switching the default environment (`env use`).

### Breaking changes

**CLI**

- `auth list`, `auth use`, and `auth unpair` are removed. Use `env list`, `env use`, and `env remove` instead.
- `auth unpair` is renamed to `env remove` (same behavior: removes local credentials only).

**Library (`t3code-cli/config`)**

- `T3ConfigLive`, `makeT3Config`, `UrlError`, and `StoredConfig` are removed from the public export surface.
- Import config services via namespace exports instead: `Config`, `Credential`, `Keystore`, `Selection`, `Env`, `Paths`, `Url`, `EnvironmentName`.
- `ResolvedConfig` remains exported but now includes optional `environment` and a `source` discriminator (`config` | `env`).

**Library (`t3code-cli/connection`)**

- `T3CodeNodeRpcLayer` is no longer exported from `t3code-cli/connection`. Import it from `t3code-cli/node`.

**Library (`t3code-cli/runtime` and `t3code-cli/node`)**

- `NodeEnvironmentLive` is removed. Environment/process snapshot access moved to `CliRuntime` under `t3code-cli/cli`.

### Upgrade notes

- Existing single-environment v1 configs upgrade automatically on the first command that reads config.
- After upgrade, tokens are encrypted; keep the OS keyring entry or `~/.config/t3cli/key` file backed up if you rely on stored credentials.
- Scripts using `auth list`, `auth use`, or `auth unpair` must switch to the `env` subcommands.
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ pnpm release:check

Use `pnpm format` and `pnpm lint:fix` for mechanical fixes.

## testing

- Prefer pure `it()` tests for logic with no service dependencies.
- Use `it.layer(ConfigPlatformLayer)` for config/auth tests that need filesystem, path, and crypto only — not full `NodeServices.layer`.
- Colocate tests as `{module}.test.ts` next to `{module}.ts`; shared fixtures use `{module}.test-utils.ts` in the same directory.
- Keep feature code grouped under its module (e.g. `config/credential/cipher.ts`, `config/url/error.ts`) with sibling-only `index.ts` barrels — no standalone `error/` directories, but each feature owns an `error.ts` when needed.
- Top-level module files use the module name (e.g. `config/config.ts` for `T3Config`, `config/error.ts` for shared config errors).
- Use `it.layer(ConfigPlatformLayer)` for config/auth tests that need filesystem, path, and crypto only — not full `NodeServices.layer`.
- Use `CliRuntime.layerTest()` for cwd fixtures and `config/env/env.test-utils.ts` for `HOME`.
- Split tests by module/concern; keep integration files under ~120 lines when possible.
- Reserve full app-layer smoke tests for CLI routing and end-to-end wiring only.

## pull requests

Keep changes scoped and small. Include user-facing README updates when command behavior changes.
Expand Down
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,27 @@ npx skills add tarik02/t3cli

## Authentication

`t3cli` stores multiple named auth environments in `~/.config/t3cli/config.json` (or `$XDG_CONFIG_HOME/t3cli/config.json`). Tokens are encrypted at rest with AES-256-GCM; the master key is stored in the OS keyring when available, otherwise in `~/.config/t3cli/key`.

```sh
t3cli auth pair --url <url> [--local] # Pair with a remote server
t3cli auth local # Local t3code installation
t3cli auth status # Check current auth
t3cli auth pair --url <url> [--name <name>] [--replace] [--local] # Pair with a remote server
t3cli auth local [--name <name>] [--replace] # Local t3code installation
t3cli auth status [--format json] # Check current auth
t3cli env list [--format json] # List stored environments
t3cli env use <name> [--format json] # Set default environment
t3cli env remove [--name <name>] [--yes] # Remove local credentials
t3cli --environment <name> ... # Use a specific environment once
```

- Use `auth pair` with a pairing URL from a running t3code server
- Use `auth local` to authenticate against a local t3code installation
- Default environment names: hostname slug from the paired URL, or `local` for `auth local`
- `auth pair` / `auth local` set the default environment only when creating the first stored environment; use `env use` to switch afterward
- `--replace` overwrites an existing environment and makes it the default
- `env remove` removes local CLI credentials only; any remote token can remain valid until natural expiry
- Use `auth local` or `auth pair --local` to authenticate against a local t3code installation
- Local auth enables automatic project resolution from the current directory
- Set `T3CLI_ENV=<name>` to select an environment when `--environment` is omitted
- `T3CODE_URL` and `T3CODE_TOKEN` override the selected environment only when both are set

## Project Management

Expand Down Expand Up @@ -143,12 +155,15 @@ t3cli terminal destroy [--thread <id>] <terminal-id> [--yes] [--format auto|huma

When flags are omitted, the CLI reads these environment variables (first match wins):

| Variable | Used by |
| ---------------------- | ----------------------------------------- |
| `T3CODE_PROJECT_ROOT` | `--project` |
| `T3CODE_PROJECT_ID` | `--project` (after `T3CODE_PROJECT_ROOT`) |
| `T3CODE_WORKTREE_PATH` | `--worktree` |
| `T3CODE_THREAD_ID` | `--thread` |
| Variable | Used by |
| ---------------------- | --------------------------------------------- |
| `T3CODE_PROJECT_ROOT` | `--project` |
| `T3CODE_PROJECT_ID` | `--project` (after `T3CODE_PROJECT_ROOT`) |
| `T3CODE_WORKTREE_PATH` | `--worktree` |
| `T3CODE_THREAD_ID` | `--thread` |
| `T3CLI_ENV` | `--environment` |
| `T3CODE_URL` | server URL override (requires `T3CODE_TOKEN`) |
| `T3CODE_TOKEN` | auth token override (requires `T3CODE_URL`) |

### Project Resolution

Expand Down Expand Up @@ -177,6 +192,7 @@ t3cli wait --format ndjson
```sh
--help # Show help
--version # Show version
--environment <name> # Auth environment for this command
--completions <shell> # Generate shell completions (bash|zsh|fish|sh)
--log-level <level> # Set log level
```
Expand Down
30 changes: 18 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,60 +36,65 @@
"exports": {
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./application": {
"types": "./dist/src/application/index.d.ts",
"import": "./dist/application.js",
"default": "./dist/application.js"
},
"./auth": {
"types": "./dist/src/auth/index.d.ts",
"import": "./dist/auth.js",
"default": "./dist/auth.js"
},
"./cli": {
"types": "./dist/src/cli/index.d.ts",
"import": "./dist/cli.js",
"default": "./dist/cli.js"
},
"./config": {
"types": "./dist/src/config/index.d.ts",
"import": "./dist/config.js",
"default": "./dist/config.js"
},
"./connection": {
"types": "./dist/src/connection/index.d.ts",
"import": "./dist/connection.js",
"default": "./dist/connection.js"
},
"./contracts": {
"types": "./dist/src/contracts/index.d.ts",
"import": "./dist/contracts.js",
"default": "./dist/contracts.js"
},
"./t3tools": {
"types": "./dist/upstream-t3code/packages/contracts/src/index.d.ts",
"default": "./dist/t3tools.js"
},
"./layout": {
"types": "./dist/src/layout/index.d.ts",
"default": "./dist/layout.js"
},
"./node": {
"types": "./dist/src/node/index.d.ts",
"import": "./dist/node.js",
"default": "./dist/node.js"
},
"./orchestration": {
"types": "./dist/src/orchestration/index.d.ts",
"import": "./dist/orchestration.js",
"default": "./dist/orchestration.js"
},
"./rpc": {
"types": "./dist/src/rpc/index.d.ts",
"import": "./dist/rpc.js",
"default": "./dist/rpc.js"
},
"./runtime": {
"types": "./dist/src/runtime/index.d.ts",
"import": "./dist/runtime.js",
"default": "./dist/runtime.js"
},
"./scope": {
"types": "./dist/src/scope/index.d.ts",
"default": "./dist/scope.js"
}
"./t3tools": {
"types": "./dist/upstream-t3code/packages/contracts/src/index.d.ts",
"import": "./dist/t3tools.js",
"default": "./dist/t3tools.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "vp pack && tsc -p tsconfig.dts.json",
Expand All @@ -108,6 +113,7 @@
},
"dependencies": {
"@effect/platform-node": "4.0.0-beta.78",
"@napi-rs/keyring": "^1.3.0",
"effect": "4.0.0-beta.78"
},
"devDependencies": {
Expand Down
141 changes: 135 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions skills/t3code-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ t3cli start "task description" --provider "$PROVIDER" --model "$MODEL" --wait

## Scope resolution

| Target | Flag | Env (first match wins) |
| -------- | ------------ | ------------------------------------------------------------------- |
| Project | `--project` | `T3CODE_PROJECT_ROOT` → `T3CODE_PROJECT_ID` → cwd (local auth only) |
| Worktree | `--worktree` | `T3CODE_WORKTREE_PATH` → inferred from cwd |
| Thread | `--thread` | `T3CODE_THREAD_ID` |
| Target | Flag | Env (first match wins) |
| ----------- | --------------- | ------------------------------------------------------------------- |
| Environment | `--environment` | `T3CLI_ENV` → config default |
| Project | `--project` | `T3CODE_PROJECT_ROOT` → `T3CODE_PROJECT_ID` → cwd (local auth only) |
| Worktree | `--worktree` | `T3CODE_WORKTREE_PATH` → inferred from cwd |
| Thread | `--thread` | `T3CODE_THREAD_ID` |

Project matching: id → `workspaceRoot` → ancestor under workspace → known thread `worktreePath`. Remote pairing without `--local` requires explicit `--project` or `T3CODE_PROJECT_*`.

Expand Down
Loading
Loading