# CLI — Governance as Code

The Powerloom CLI is how you operate your agent fleet like infrastructure. Declare resources in YAML manifests. Apply them with a single command. Plan changes before they land. Diff against live state. Destroy what you don't need. Version everything in git, review in PRs, roll back on drift.

If you've used `terraform apply` or `kubectl apply`, this works the way you'd expect.

---

## Install and Authenticate

```bash
pip install loomcli
```

Authenticate against your Powerloom control plane:

```bash
weave auth login
```

Verify your identity:

```bash
weave auth whoami
# admin@acme.com (a1b2c3d4) @ org acme-corp
```

Credentials are stored at `~/.config/powerloom/credentials` with 0600 permissions. One token, one line. `weave auth logout` clears it.

### Configuration

| Variable | Default | Purpose |
|---|---|---|
| `POWERLOOM_API_BASE_URL` | `http://localhost:8000` | Control plane URL |
| `POWERLOOM_HOME` | `~/.config/powerloom` | Credentials and config directory |

Override the API URL per-command with `--api-url`.

---

## Commands

### `weave plan <paths>`

Show what `apply` would do. Read-only — no changes made.

```bash
$ weave plan acme/

+ ou              acme/engineering/platform         (create)
+ role-binding    eng-leads → OUAdmin               (create, scope=platform)
~ role-binding    contractors ✗ AgentBuilder         (update, effect: allow → deny)
+ agent           pg-writer                         (create, model=claude-sonnet-4-6)
+ mcp-deployment  pg-analytics                      (create, template=postgres)

Plan: 4 create, 1 update, 0 destroy
```

Each resource is diffed field-by-field against live state. Create means the resource doesn't exist yet. Update shows exactly which fields changed. Noop means the manifest matches what's already deployed.

### `weave apply <paths>`

Apply manifests. Creates, updates, or reconciles resources to match the declared state.

```bash
$ weave apply acme/ --auto-approve

✓ ou             acme/engineering/platform         ok
✓ role-binding   eng-leads → OUAdmin               ok
✓ role-binding   contractors ✗ AgentBuilder         ok
✓ agent          pg-writer                         ok
✓ mcp-deployment pg-analytics                      ok

5 applied, 0 failed
```

Without `--auto-approve`, you see the plan and confirm before any changes land. One resource failing doesn't abort the rest — each resource is applied independently, and the outcome table shows what succeeded and what didn't.

**Dependency order is automatic.** OUs before groups. Groups before agents. Agents before attachments. Attachments before credentials. You don't need to order your manifests — Powerloom sorts them.

### `weave destroy <paths>`

Delete everything declared in the manifests. Reverse dependency order — attachments before agents, agents before OUs.

```bash
$ weave destroy acme/engineering/platform/

- credential     pg-writer ↔ pg-analytics          (destroy)
- agent-mcp      pg-writer ↔ pg-analytics          (destroy)
- agent          pg-writer                         (destroy)
- mcp-deployment pg-analytics                      (destroy)

Plan: 0 create, 0 update, 4 destroy. Confirm? [y/N]
```

Resources that don't exist are silently skipped.

### `weave get <kind>`

List resources in tabular or JSON format.

```bash
$ weave get agents --ou /acme/engineering
NAME            OU                          MODEL               STATUS
pg-writer       /acme/engineering/platform   claude-sonnet-4-6   synced
code-reviewer   /acme/engineering            claude-sonnet-4-6   synced

$ weave get agents -o json
[{"id": "a1b2...", "name": "pg-writer", ...}]
```

Supported kinds: `ou`, `group`, `agent`, `skill`, `mcp-server`, `mcp-deployment`, `session`.

### `weave describe <kind> <identifier>`

Full detail on a single resource. Accepts a UUID or an OU-path.

```bash
$ weave describe agent /acme/engineering/platform/pg-writer
{
  "id": "a1b2c3d4-...",
  "name": "pg-writer",
  "model": "claude-sonnet-4-6",
  "system_prompt": "...",
  "skills": [...],
  "mcp_servers": [...],
  ...
}
```

### `weave import <kind> <identifier>`

Export an existing resource as a YAML manifest. Useful for bringing manually-created resources into version control.

```bash
$ weave import agent /acme/engineering/platform/pg-writer > pg-writer.yaml
```

The output is a valid manifest you can edit and re-apply.

---

## Manifests

A manifest is a YAML file that declares the desired state of one or more resources. Multi-document YAML (separated by `---`) puts multiple resources in one file.

### Structure

Every manifest has four fields:

```yaml
apiVersion: powerloom/v1
kind: Agent
metadata:
  name: pg-writer
  ou_path: /acme/engineering/platform
spec:
  display_name: PG Writer
  model: claude-sonnet-4-6
  system_prompt: "Analyze and migrate PostgreSQL schemas."
  owner_principal_ref: user:admin@acme.com
  skills: [python-lint]
  mcp_servers: [pg-analytics]
```

`apiVersion` is always `powerloom/v1`. `kind` determines the resource type. `metadata` identifies it. `spec` defines it.

### Supported Kinds

**Organizational structure:**

| Kind | Purpose | Key metadata | Key spec fields |
|---|---|---|---|
| `OU` | Organizational unit | `name`, `parent_ou_path` | `display_name` |
| `Group` | Security group | `name`, `ou_path` | `display_name`, `description` |
| `GroupMembership` | Add a principal to a group | `group_path`, `member_ref` | — |
| `RoleBinding` | Bind a role to a principal at an OU | `principal_ref`, `role`, `scope_ou_path`, `decision_type` | — |

**Agent resources:**

| Kind | Purpose | Key metadata | Key spec fields |
|---|---|---|---|
| `Agent` | AI agent definition | `name`, `ou_path` | `model`, `system_prompt`, `owner_principal_ref`, `skills`, `mcp_servers` |
| `Skill` | Custom skill | `name`, `ou_path` | `display_name`, `description` |
| `SkillAccessGrant` | Grant skill access to a principal | `skill_path`, `principal_ref` | — |

**Infrastructure:**

| Kind | Purpose | Key metadata | Key spec fields |
|---|---|---|---|
| `MCPServerRegistration` | Register a BYO MCP server | `name`, `ou_path` | `display_name`, `url` |
| `MCPDeployment` | Deploy a managed MCP server | `name`, `ou_path` | `template_kind`, `config`, `policy` |
| `Credential` | Bearer token for MCP auth | `agent_path`, `mcp_registration_path` | — |

### Addressing

Resources are addressed by OU path plus name: `/acme/engineering/platform/pg-writer`.

Principals use a prefix syntax:
- `user:admin@acme.com` — a user
- `group:/acme/engineering/eng-leads` — a group
- `ou:/acme/engineering` — an OU principal

### Inline Attachments

Agents support shorthand for skills and MCP servers:

```yaml
kind: Agent
metadata:
  name: pg-writer
  ou_path: /acme/engineering/platform
spec:
  skills: [python-lint, sql-review]
  mcp_servers: [pg-analytics, reports-files]
```

At apply time, Powerloom expands these into discrete `AgentSkill` and `AgentMCPServer` resources automatically. You don't need to write them out separately unless you need to control version pinning or other attachment-level settings.

---

## How Apply Works

The full pipeline from YAML to live state:

**1. Parse.** YAML files are loaded and validated against Pydantic schemas. Multi-document files are split. Parse errors include the filename and document index.

**2. Expand.** Inline `skills` and `mcp_servers` on Agent manifests are expanded into discrete attachment resources.

**3. Sort.** Resources are ordered by dependency: OUs first, then groups and infrastructure, then agents, then attachments, then credentials. Within each tier, parents come before children. Manifest order is preserved as a tiebreaker.

**4. Plan.** Each resource is compared against live state via the API. The result is one of: create (doesn't exist yet), update (exists but differs), noop (already matches), or unknown (dependency not yet resolved).

**5. Confirm.** The plan is rendered. You approve or abort.

**6. Execute.** Each resource is applied independently. One failure doesn't stop the others. Creates become POSTs. Updates become PATCHes. Destroys become DELETEs.

**7. Report.** An outcome table shows what happened to each resource: ok, failed, or skipped.

Exit code 0 means everything succeeded. Exit code 1 means at least one resource failed.

---

## Example: Full Deployment

A directory that declares an OU, a group with bindings, an MCP server, and an agent:

```
acme/engineering/platform/
├── ou.yaml
├── groups.yaml
├── mcp.yaml
└── agent.yaml
```

**ou.yaml:**
```yaml
apiVersion: powerloom/v1
kind: OU
metadata:
  name: platform
  parent_ou_path: /acme/engineering
spec:
  display_name: Platform Team
```

**groups.yaml:**
```yaml
apiVersion: powerloom/v1
kind: Group
metadata:
  name: eng-leads
  ou_path: /acme/engineering/platform
spec:
  display_name: Engineering Leads
---
apiVersion: powerloom/v1
kind: RoleBinding
metadata:
  principal_ref: group:/acme/engineering/platform/eng-leads
  role: OUAdmin
  scope_ou_path: /acme/engineering/platform
  decision_type: allow
```

**mcp.yaml:**
```yaml
apiVersion: powerloom/v1
kind: MCPDeployment
metadata:
  name: pg-analytics
  ou_path: /acme/engineering/platform
spec:
  display_name: PG Analytics
  template_kind: postgres
  config:
    database_url_secret: pg-analytics-dsn
  policy:
    allowed_tables: [metrics, events, users]
    deny_mutation_keywords: true
```

**agent.yaml:**
```yaml
apiVersion: powerloom/v1
kind: Agent
metadata:
  name: pg-writer
  ou_path: /acme/engineering/platform
spec:
  display_name: PG Writer
  model: claude-sonnet-4-6
  system_prompt: |
    You are a PostgreSQL migration assistant. Analyze schemas,
    suggest migrations, and explain query plans.
  agent_kind: service
  owner_principal_ref: group:/acme/engineering/platform/eng-leads
  mcp_servers: [pg-analytics]
```

Deploy everything:

```bash
$ weave apply acme/engineering/platform/
```

Powerloom sorts the resources, shows the plan, and applies them in the right order. The OU is created first, then the group and binding, then the MCP deployment, then the agent with its MCP attachment.

Tear it all down:

```bash
$ weave destroy acme/engineering/platform/
```

Reverse order. Attachments first, then agents, then infrastructure, then org structure.
