# SAIF CLI

The SAIF CLI is a command-line tool for automating SAIF platform development tasks.

---

## Description

The SAIF CLI is a console app published to a NuGet Feed and installed using the dotnet CLI. It automates processes that developers might do manually, including:

- Creating new projects from templates (`dotnet new`)
- Setting up Azure DevOps pipelines
- Creating Azure DevOps repositories
- Configuring project infrastructure
- Generating and inspecting JWT tokens
- Discovering Entra ID application registrations

---

## Why we use it

We use the SAIF CLI to provide a single interface for operations across multiple cloud services. All Forge projects are created using the CLI to ensure standard setup and naming conventions.

---

## Commands

### `saif auth generate`

Generate an OAuth token for a SAIF application. See [JWT Test Tokens](../../guides/security/testing/create-jwt-for-testing-apis.md) for full usage.

```powershell
saif auth generate --name <application-name>
```

### `saif auth validate`

Decode and inspect a JWT token. Displays a claims breakdown and resolves the audience to an Entra ID app registration.

```powershell
# Run without arguments and paste the token interactively (recommended)
saif auth validate

# Or pass the token directly as an argument
saif auth validate eyJ0eXAiOiJKV1Qi...
```

!!! note
    Long tokens pasted directly on the command line may cause errors due to terminal word-wrapping. See [JWT Test Tokens — Troubleshooting](../../guides/security/testing/create-jwt-for-testing-apis.md#word-wrapped-tokens) for workarounds.

**Output includes:**

- Claims table — audience, issuer, subject, tenant ID, name, scopes, roles, timestamps
- Expiry status — highlighted in green (valid) or red (expired)
- Audience resolution — looks up the Entra ID app registration matching the `aud` claim

### `saif app search`

Search or list Entra ID application registrations.

```powershell
# Search by audience URI or GUID (tries identifier URI, app ID, and api://{guid} patterns)
saif app search --audience api://my-app-test
saif app search --audience 00000000-0000-0000-0000-000000000000

# Search by application (client) ID directly
saif app search --appid 00000000-0000-0000-0000-000000000000

# List all registrations
saif app search --all

# List with a name filter
saif app search --all --name my-app

# List only apps that have an audience URI configured
saif app search --all --filter audience
```

| Option | Description |
| --- | --- |
| `--audience` | Search by audience value / identifier URI (e.g., `api://my-app-test`). If a bare GUID is provided, the CLI will attempt to resolve it as both an app ID and `api://{guid}`. |
| `--appid` | Search by application (client) ID |
| `--all` | List all application registrations |
| `--name` | Partial name filter (use with `--all`) |
| `--filter` | Post-filter results; `audience` shows only apps with an identifier URI (use with `--all`) |

!!! note
    `--audience`, `--appid`, and `--all` are mutually exclusive — specify exactly one. If your JWT `aud` claim is a GUID, you can use either `--appid` or `--audience`; `--appid` is recommended for clarity.

### `saif service search`

Search for **services** by name. A service consolidates everything known about a service under a common key (the base name with environment suffixes stripped): its per-environment Entra ID application registrations (e.g. `payments-platformdev`, `payments-test`, `payments-prod`) and its correlated source repository.

The query is a case-insensitive substring match against the application registration display names — searching `pol` finds every service whose registrations contain `pol`, and `policyportal` finds `pol-api-exp-policyportal`, `pol-func-policyportal`, and `pol-web-policyportal`. Only registrations that carry a known environment suffix (see below) qualify as service facets, so free-text, vendor, or COTS application registrations (e.g. `Navex PolicyTech`, `Guidewire PolicyCenter App`) are never surfaced as services. Matching registrations are then consolidated into services.

!!! info "Minimum query length"
    The query must be **at least 2 characters**. Single-character queries are rejected immediately because they would match an impractically large fraction of the tenant and are unlikely to be useful.

!!! info "Large tenants — results may be incomplete"
    Service search scans up to 2 000 application registrations per query. In tenants with many registrations a broad query (e.g. `api`) may hit this limit before all registrations are scanned; when that happens the output shows a notice that results may be incomplete. Narrow your query to get a complete result set.

```powershell
# Find a service across all environments
saif service search payments

# Match on any segment of the name (here, the app-name segment)
saif service search policyportal

# Expand per-environment application registration details (app ID, audience URI)
saif service search payments --verbose

# Machine-readable output
saif service search payments --format json
```

| Option | Description |
| --- | --- |
| `<query>` | Name or pattern to search for; matches any substring of the service name (e.g. `payments`, `policyportal`) |
| `--verbose` | Show per-environment application registration details (app ID, audience URI) |
| `--format` | Output format: `table` (default) or `json` |

The same service-consolidation logic powers `saif search` and the `search_services` MCP tool, so results stay consistent across the CLI and agent surfaces.

!!! note "Recognized environments"
    A service is defined by the platform naming convention and deployed per-environment, so only application registrations ending in a known environment suffix — `platformdev`, `test`, `qa`, `uat`, or `prod` (e.g. `payments-prod`) — count as service facets. Registrations without one of these suffixes are not platform services and are excluded from results.

!!! note "Repository correlation"
    Service names follow `{domain}-{app type}{-app sub type}-{app name}`. Each service is matched to a repository by an exact-name lookup, probing in order: the full service name, then `{domain}-{app name}`, then the bare app name. The first exact match wins. A component that has no repository of its own — for example a `web` or `func` sibling whose code lives in another component's repository — simply shows no repository (`—` in table output, `null` in JSON).

### `saif service describe`

Show a full topology view of a **single** service: its repository (URL and branch), each environment's Entra ID application registration, the permissions that registration requests against target APIs, and a pipeline count with a follow-up hint.

The `<name>` is resolved by an **exact** service-key match (the base name with environment suffixes stripped), so both the base name and any environment-suffixed form resolve to the same service. A name that matches no service exits with a clear "not found" error.

```powershell
# Describe a service by its base name
saif service describe payments

# An environment-suffixed name resolves to the same service
saif service describe payments-prod

# Limit the view to a single environment
saif service describe payments --environment prod

# Machine-readable output
saif service describe payments --format json

# Full topology: includes service key, pipeline IDs/URLs, App ID and permission GUIDs
saif service describe payments --verbose
```

| Option | Description |
| --- | --- |
| `<name>` | The service name to describe; resolved by exact service key (environment suffix optional) |
| `--environment`, `--env` | Limit the view to a single environment (e.g. `prod`, `platformdev`). Errors with the available environments if the service has no such environment. |
| `--format` | Output format: `table` (default) or `json` |
| `--verbose` | Show additional low-level identifiers: service key, pipeline IDs and URLs, and the App ID (`resourceAppId`) and permission GUID (`id`) for each permission entry. In JSON output, adds a `pipelines` array and surfaces `resourceAppId`/`id` per permission. |

!!! note "Permissions"
    For each environment's application registration, `describe` reads the registration's requested permissions (Graph `requiredResourceAccess`) and resolves them to human-readable values against each target API's service principal: delegated permissions (OAuth2 scopes) and application permissions (app roles). Target lookups are cached so an API shared across many apps (e.g. Microsoft Graph) is resolved once. Permission resolution is best-effort — if it fails for an environment (e.g. a transient Graph error, timeout, or malformed response), that environment's permissions render as `unavailable` (`null` in JSON) while the rest of the topology still renders. A target API that cannot be resolved falls back to its App ID, and a permission whose id cannot be resolved falls back to its raw id — in both table and JSON output, even without `--verbose`.

!!! note "`saif app` vs `saif service`"
    `saif app search` operates on **individual** Entra ID application registrations — the raw, flat facets keyed by audience, app ID, or name. `saif service` works one level up: it **consolidates** those registrations into logical services (grouping `payments-platformdev`, `payments-test`, `payments-prod` under `payments`), correlates the source repository, and — for `describe` — resolves each environment's requested permissions. Use `saif app` to look up a specific registration; use `saif service` for the cross-environment, repository-aware view of a service.

!!! tip "Building features that read live Azure state"
    To extend `service describe` (or any command) with live deployed/runtime state — image tags, logs, health, cost — first read the [Service ↔ Azure Resource Model](../service-azure-resource-model.md). It documents the app-registration ↔ App Service naming invariant, the one-subscription-per-environment topology, how to discover resources via Azure Resource Graph by exact name, and the deployed-version tag contract.

---

## Related Documentation

| Guide                                                                               | Description                               |
| ----------------------------------------------------------------------------------- | ----------------------------------------- |
| [Service ↔ Azure Resource Model](../service-azure-resource-model.md)                | Map a service to its live Azure resources |
| [Installing the SAIF CLI](../../guides/development/install-saif-cli.md)             | Setup, configuration, and troubleshooting |
| [JWT Test Tokens](../../guides/security/testing/create-jwt-for-testing-apis.md)     | Generate and use tokens for API testing   |

---

## Links

- [SAIF CLI Repository](https://dev.azure.com/saifcorporation/SAIF/_git/Platform.cli)
