# Factory Package

The factory wiring layer. Constructs a fully-wired `*cmdutil.Factory` with
real dependency implementations. Called exactly once at the CLI entry point.

## Domain: Clawker-Specific Configuration

**Responsibility**: Apply clawker-specific environment configuration on top of standard terminal behavior.

The factory `ioStreams()` helper calls `iostreams.System()` then applies clawker-specific config. It does NOT handle standard terminal env vars — those belong in lower layers.

| Layer | Package | Responsibility | Env Vars |
|-------|---------|----------------|----------|
| Capabilities | `term` | What the terminal supports | `TERM`, `COLORTERM`, `NO_COLOR` |
| Behavior | `iostreams` | Terminal UX (theme, progress, paging) | `CLAWKER_PAGER`, `PAGER` |
| **App Config** | `factory` | Clawker-specific preferences | `CLAWKER_SPINNER_DISABLED` |

The cascade: `term.FromEnv()` → `iostreams.System()` → `factory.ioStreams()`

## Key File

| File | Purpose |
|------|---------|
| `default.go` | `New(version)` — constructs Factory with all lazy closures |

## Usage

```go
// Entry point only (internal/clawker/cmd.go)
f := factory.New(build.Version)
rootCmd, err := root.NewCmdRoot(f, build.Version, build.Date)
```

**Tests NEVER import this package.** Tests construct minimal Factory structs:
```go
tio, _, _, _ := iostreams.Test()
f := &cmdutil.Factory{IOStreams: tio, TUI: tui.NewTUI(tio), Version: "1.0.0"}
```

## Extracted Helper Pattern

`New()` delegates to extracted helper functions for each Factory field:
- `ioStreams(f)` -- creates IOStreams + initializes logger (eager, needs `f.Config()` for settings)
- `tui.NewTUI(ios)` -- creates TUI struct bound to IOStreams (eager, inline in `New()`)
- `clientFunc(f)` -- returns lazy Docker client constructor; closes over `f.Config()` to pass `*config.Config` to `docker.NewClient`
- `configFunc()` -- returns lazy `config.Provider` gateway constructor (the `Config` implementation uses `os.Getwd()` internally and lazy-loads project, settings, registry via `sync.Once`)
- `gitManagerFunc(f)` -- returns lazy git manager constructor; uses project root from `f.Config().ProjectCfg().RootDir()`
- `hostProxyFunc()` -- returns lazy host proxy manager constructor
- `adminClientFunc(f)` -- returns a lazy `adminv1.AdminServiceClient` constructor; closes over `f.Config()` only. Pure dial — does NOT bootstrap the CP (CP lifecycle lives in `controlPlaneFunc` / `cpboot.Manager`; CP is brought up by agent-container start flows and the explicit `clawker controlplane up` / `clawker firewall up` verbs). Reads `cp.AdminPort` / `cp.HydraPublicPort` from settings and calls `adminclient.Dial(ctx, adminPort, hydraPort, grpc.WithKeepaliveParams(...))` with mTLS + OAuth2 JWT; subsequent calls return the cached `grpc.ClientConn` unless it has entered `TransientFailure`/`Shutdown`, in which case the closure closes the conn and rebuilds. Admin commands invoked when the CP is down fail fast. No test seams — callers substitute via `AdminServiceClient` mocks at the Factory level (`cpmocks.AdminServiceClientMock`). No raw moby client.
- `controlPlaneFunc(f)` -- returns a `sync.Once`-cached `func() cpboot.Manager` that constructs a single `cpboot.NewManager(f.Client, f.Config, f.Logger)` per Factory. The `Manager` holds lazy Factory closures, not eagerly resolved Docker/Config/Logger values, so a caller that never touches the CP never resolves them. Consumed by the break-glass verbs in `internal/cmd/controlplane/` and intended for any future caller that needs to drive the CP lifecycle without hitting the AdminService.
- `socketBridgeFunc()` -- returns lazy `socketbridge.SocketBridgeManager` constructor (wraps `socketbridge.NewManager()`)
- `prompterFunc(ios)` -- returns lazy prompter constructor

Each helper is a standalone function in `default.go`, making the wiring easy to read and test.

All closures use `sync.Once` for lazy single-initialization within the `config.Config` gateway or within the helper closures themselves.

**Dependency ordering in `New()`**: Config is constructed first, then `ioStreams(f)` (needs `f.Config().Settings` for logger init), then IOStreams-dependent fields (TUI, Prompter).

## Logger Initialization

Logger initialization happens inside `ioStreams(f)`:
1. Reads `f.Config().UserSettings()` (Viper already resolved ENV > config > defaults)
2. Calls `logger.NewLogger()` with file config (rotation, compression) and optional OTEL config
3. Logger is a separate Factory lazy noun (`f.Logger`), not part of IOStreams
4. Falls back to `logger.Init()` (nop) if `LogsDir()` fails

Previously, logger init lived in `root.go`'s `initializeLogger()` — now consolidated into the factory.

## Environment Variables

| Variable | Effect |
|----------|--------|
| `CLAWKER_SPINNER_DISABLED` | Static text instead of animated spinner |

Note: Standard terminal env vars (`TERM`, `COLORTERM`, `NO_COLOR`) are handled by `term.FromEnv()`. Factory handles only clawker-specific config.
