Metadata-Version: 2.4
Name: integry-app
Version: 0.1.3
Summary: Agent-ergonomic CLI for the Integry backend
Author-email: Integry <moiz@integry.io>
License: MIT
Keywords: integry,ipaas,cli,automation
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.12
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-httpx>=0.30; extra == "dev"

# Integry CLI

Agent-ergonomic CLI over the Integry backend. Covers the template-management surface:

- list templates (with search + type filter)
- create a template
- partial update a template
- retrieve a template (or its draft payload)
- list versions / retrieve a specific version
- publish the draft as a new version
- test a FUNCTION template draft

Designed following the [AXI principles](https://axi.md): TOON output by default, minimal default schemas, content truncation, structured errors, content-first default invocation, contextual disclosure of next-step commands.

## Install

End users (Linux + macOS):

```
pipx install integry-app           # or: uv tool install integry-app
integry --help
```

Upgrade with `pipx upgrade integry-app` (or `uv tool upgrade integry-app`).

## Develop

```
nix-shell                              # enter dev shell (latest stable Python + deps)
python -m integry_cli                  # run the CLI from inside the shell
nix-shell --run pytest                 # run tests
nix-shell --run 'python -m integry_cli --help'
```

If installed via `pip install -e web/cli`, an `integry` console script is exposed.

## Publish

```
export UV_PUBLISH_TOKEN=<pypi-token>   # https://pypi.org/manage/account/token/
./scripts/publish.sh                   # bump version in pyproject.toml first
```

## Configuration

| Variable | Purpose | Default |
|---|---|---|
| `INTEGRY_API_URL` | Backend base URL. Wins over stored config. | `https://api.integry.io` |
| `INTEGRY_HOME` | Config directory. | `~/.integry` |
| `INTEGRY_ACCOUNT_ID` | Account id sent on every request as the `X-Account-Id` header. Wins over stored config. | _unset_ |

Stored files (mode `0o600` on credentials):

- `$INTEGRY_HOME/config.json` - `{"api_url": "...", "account_id": <int>}` (set by `auth login --api-url` and `accounts use`)
- `$INTEGRY_HOME/credentials.json` - `{"access", "refresh", "obtained_at"}`

## Output

- Default format is TOON (token-efficient). Pass `--format json` for a machine-readable envelope.
- Default schemas:
  - Template detail → `id, name, type, machine_name, app_id, publishing_status, revision_number, live_or_latest_version, is_draft_empty`
  - Template list rows → `id, name, type, machine_name, publishing_status, revision_number, live_or_latest_version`
  - Template versions → `id, version, publishing_status, is_live, integration_count, changelog`
- Pass `--full` to skip field filtering and disable the 400-char string truncation.
- Successful mutations always end with one or more `next:` lines suggesting plausible follow-ups.
- Errors print to stdout as `error: <code>: <message>` with optional indented detail.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success |
| `1` | Backend API error (4xx/5xx other than 401) |
| `2` | Auth/credential problem (no credentials, or refresh failed) |
| `3` | Local usage error (bad flags, unreadable JSON file, mutually-exclusive flags) |

---

## Commands

### `integry` (no args)

Prints ambient state and next-step hints - no help-text dump (AXI principle 8).

```
integry [--format toon|json]
```

Output (unauthenticated):

```
api_url: "https://api.integry.io"
authenticated: false
access_expires_in: null
account_id: null
next: integry auth login --username <you>
next: integry accounts list
```

Output (authenticated, no account selected):

```
api_url: "https://api.integry.io"
authenticated: true
access_expires_in: 3140
account_id: null
next: integry accounts list
next: integry accounts use <id>
```

Output (authenticated, account selected - templates hints kick in):

```
api_url: "https://api.integry.io"
authenticated: true
access_expires_in: 3140
account_id: 7
next: integry templates create --input ./template.json
next: integry templates update <id> --input ./patch.json
```

Exit code: `0` if authenticated, `2` if not.

---

### `integry auth login`

Obtain an access + refresh token pair and persist them.

```
integry auth login --username <name> [--password <secret>] [--api-url <url>] [--format toon|json]
```

| Flag | Effect |
|---|---|
| `--username, -u` (required) | Account username. |
| `--password, -p` | Password. Prompted (hidden) if omitted - the only interactive prompt anywhere in the CLI. |
| `--api-url` | Override and persist `api_url` to `$INTEGRY_HOME/config.json`. `INTEGRY_API_URL` env var still wins. |
| `--format` | `toon` (default) or `json`. |

Output:

```
username: alice
api_url: "https://api.integry.io"
access_expires_in: 3599
refresh_expires_in: 86399
next: integry accounts list
```

Picking an account is the very next step after login - see [`integry accounts`](#integry-accounts) below.

Exits `2` on bad credentials (backend `401`), `1` on other backend errors.

---

### `integry auth logout`

Delete stored credentials. Idempotent.

```
integry auth logout [--format toon|json]
```

Output:

```
cleared: true        # or false if there was nothing to clear
api_url: "https://api.integry.io"
```

Always exits `0`.

---

### `integry auth status`

Show the currently stored credentials' state.

```
integry auth status [--format toon|json]
```

Output (authenticated):

```
api_url: "https://api.integry.io"
user_id: 42
access_expires_in: 3140
refresh_expires_in: 86340
```

If no credentials exist, prints `error: usage_error: not authenticated - run: integry auth login` and exits `3`.

---

### `integry accounts`

Selects which account is sent on every request as the `X-Account-Id` header. The backend reads this header to scope the request to a specific tenant.

The active account is resolved in this order:

1. `INTEGRY_ACCOUNT_ID` env var
2. `account_id` in `$INTEGRY_HOME/config.json` (set via `accounts use`)
3. None - the header is omitted

The active id (and `null` if unset) is included in the output of bare `integry` and `integry auth status`.

#### `integry accounts list`

```
integry accounts list [--search TEXT] [--page N] [--page-size N] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `--search, -s TEXT` | Case-insensitive substring filter on account `name`. Applied client-side. |
| `--page N` | 1-indexed page number. Sliced client-side after `--search`. |
| `--page-size N` | Results per page. Sliced client-side. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Skip field filtering. |

Default schema per row: `id, name, is_currently_selected`.

#### `integry accounts use`

```
integry accounts use <account_id> [--format toon|json]
```

| Flag | Effect |
|---|---|
| `<account_id>` (positional, required) | Account primary key to send as `X-Account-Id`. |
| `--format` | `toon` (default) or `json`. |

#### `integry accounts clear`

```
integry accounts clear [--format toon|json]
```

| Flag | Effect |
|---|---|
| `--format` | `toon` (default) or `json`. |

#### `integry accounts current`

```
integry accounts current [--format toon|json]
```

| Flag | Effect |
|---|---|
| `--format` | `toon` (default) or `json`. |

Exits `3` if no account is selected.

---

### `integry templates list`

List templates owned by the active account.

```
integry templates list \
    [--search TEXT] [--type TYPE]... \
    [--page N] [--page-size N] \
    [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `--search, -s TEXT` | `?search=` - server-side substring match against `name`, `machine_name`, and `branding_app__name`. |
| `--type, -t TYPE` | Repeatable, joined into `?types=A,B,C`. Valid: `STANDARD`, `QUERY`, `ACTION`, `TRIGGER`, `FUNCTION`, `INTEGRATION`, `AGENT`, `FUNCTION_TRIGGER`. |
| `--page N` | 1-indexed page number. |
| `--page-size N` | Results per page (backend default 10). |
| `--format` | `toon` (default) or `json`. |
| `--full` | Disable field filtering. |

Default schema per row: `id, name, type, machine_name, publishing_status, revision_number, live_or_latest_version` - `machine_name` is the identifier for `FUNCTION` templates and is `null` for most other types.

Output:

```
total: 27
templates:
  [10] id,name,type,machine_name,publishing_status,revision_number,live_or_latest_version:
    101,send_email,FUNCTION,send_email,PUB,r4,3
    99,Onboarding,STANDARD,,DR,r1,
    ...
next: integry templates get <id>
```

JSON envelope additionally carries `query` and `types` reflecting the active filters.

Empty states: `no templates found` (unfiltered), `no templates match the filter` (with `--search`/`--type`).

---

### `integry templates create`

Create a template. Body is forwarded verbatim - backend serializers are authoritative.

```
integry templates create --input <file|-> [--publish] [--example] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `--input, -i` | Path to a JSON file, or `-` to read from stdin. Required unless `--example`. |
| `--publish` | Adds `?publish=true` so the template is created with status `PUB`. |
| `--example` | Print a comprehensive example body to stdout and exit. No API call. `--input` is ignored. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Disable field filtering and string truncation. |

Body (forwarded verbatim). Two branches:

Fast path - just a `template_type` (the backend builds a starter tree):

```json
{ "template_type": "FUNCTION",         "type": "ACTION" }
{ "template_type": "FUNCTION_TRIGGER" }
{ "template_type": "AGENT",            "playbook_id": 123 }
```

Standard branch - full `meta` + `steps` tree:

```json
{
  "meta": {
    "title":       "My template",
    "description": "...",
    "app_id":      7,
    "branding_app_id": 7,
    "machine_name": "my_template"
  },
  "steps": [ /* recursive step tree, validated server-side */ ]
}
```

Output:

```
id: 99
name: "My template"
app_id: 7
publishing_status: DR
revision_number: r1
next: integry templates update 99 --input ./patch.json
```

---

### `integry templates update`

Partial-update a template.

```
integry templates update <id> --input <file|-> \
    [--version N] [--publish] [--archive] [--meta-only] [--revision N] \
    [--example] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `<id>` (positional, required) | Template primary key. |
| `--input, -i` | Path to a JSON file, or `-` for stdin. Body must be an object containing a `meta` object. Required unless `--example`. |
| `--version` | `?version=N` - patch a specific template version. |
| `--publish` | `?publish=true`. |
| `--archive` | `?archive=true`. |
| `--meta-only` | `?exclude_template_from_response=true`. Mutually exclusive with `steps` in the body - fails fast (exit `3`). |
| `--revision` | Convenience: sets `meta.revision_number` in the body before sending. Lets you keep a static patch file and bump the revision via flag. |
| `--example` | Print a comprehensive example body to stdout and exit. No API call. `--input` is ignored. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Disable field filtering and string truncation. |

Body:

```json
{
  "meta": {
    "revision_number": 7,
    "app_id":          1,
    "branding_app_id": 1,
    "product_info":    { /* optional */ },
    "events":          [ /* optional */ ]
  },
  "steps": [ /* optional; omit for meta-only updates */ ]
}
```

`steps` is not partial - when included, the backend replaces all steps with what you send. Omitted steps are deleted. Fetch the full steps first via `get-draft` or `get --full`, modify, and send back the complete array. To update only `meta`, omit `steps` and use `--meta-only`.

Output (full update):

```
id: 42
name: "My template"
app_id: 1
publishing_status: PUB
revision_number: 8
next: integry templates update 42 --input ./patch.json
```

Output (`--meta-only`):

```
success: true
next: integry templates update 42 --input ./patch.json
```

Common backend rejections (exit `1`):
- `Revision number does not match` - your local revision is stale; refetch.
- `Template already being updated` - the template is locked.
- `Cannot update template while integration update job is in progress` - drop the `steps` and use `--meta-only`, or wait for the job.

---

### `integry templates get`

Retrieve a template or one of its versions.

```
integry templates get <id> [--version N] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `<id>` (positional, required) | Template primary key. |
| `--version N` | Fetch published version N instead of the draft. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Disable field filtering and string truncation. |

Output (no `--version`):

```
id: 42
name: "My template"
app_id: 7
publishing_status: PUB
revision_number: 8
live_or_latest_version: 3
is_draft_empty: true
next: integry templates list-versions 42
```

Output (`--version 3`):

```
id: 901
version: 3
publishing_status: PUB
is_live: true
integration_count: 12
changelog: "Added retry logic"
next: integry templates get-draft 42 --version 3
```

---

### `integry templates list-versions`

List published versions of a template.

```
integry templates list-versions <id> [--page N] [--page-size N] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `<id>` (positional, required) | Base template primary key. |
| `--page N` | 1-indexed page number. |
| `--page-size N` | Results per page (backend default 10). |
| `--format` | `toon` (default) or `json`. |
| `--full` | Emit each version's full payload instead of the default 6-field schema. |

Output:

```
total: 3
versions:
  [3] id,version,publishing_status,is_live,integration_count,changelog:
    901,3,PUB,true,12,"Added retry logic"
    877,2,PUB,false,4,"Bumped action timeout"
    803,1,PUB,false,0,"Initial release"
next: integry templates publish 42
```

Empty state (no versions yet):

```
no versions yet - publish a draft to create one
next: integry templates publish 42
```

`--format json` always emits a structured envelope, including for the empty case (`{"total": 0, "versions": []}`).

---

### `integry templates publish`

Publish the current draft as a new version.

```
integry templates publish <id> \
    [--changelog "text"] [--beta] [--upgrade-integrations] \
    [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `<id>` (positional, required) | Draft template primary key. |
| `--changelog TEXT` | Optional changelog string sent in the request body. |
| `--beta` | Adds `?publish_for_beta_users=true`. |
| `--upgrade-integrations` | Adds `?upgrade_integrations=true`. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Disable field filtering and string truncation. |

Output (the `id` is the new version-template id, not the base id):

```
id: 904
name: "My template"
app_id: 7
publishing_status: PUB
revision_number: 9
next: integry templates list-versions 42
```

---

### `integry templates get-draft`

Fetch a template (or a specific published version) reshaped into a draft body suitable for `templates create`.

```
integry templates get-draft <template_id> [--version N] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `<template_id>` (positional, required) | Base template primary key. |
| `--version N` | Specific version number to export as draft. Without it, returns the current draft state. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Required if you intend to feed the output into `templates create`. Without it, only a 4-field summary is printed. |

Default output (summary):

```
title: "My template"
app_id: 7
publishing_status: DR
step_count: 6
next: integry templates create --input <draft.json>
```

Round-trip example:

```bash
integry templates get-draft 42 --version 3 --full --format json \
  | jq '.data' > /tmp/draft.json
integry templates create --input /tmp/draft.json
```

---

### `integry templates diff`

Show differences between a published version and the current draft, ignoring IDs and read-only fields.

```
integry templates diff <id> --version N [--context N] [--format toon|json]
```

| Flag | Effect |
|---|---|
| `<id>` (positional, required) | Base template primary key. |
| `--version N` (required) | Version number to compare against the current draft. |
| `--context, -C N` | Number of context lines around each change. Default `5`. |
| `--format` | `toon` (default) or `json`. |

Strips `id`, `version_template_id`, `version_of`, `version_number`, `last_modified`, `last_modified_by`, `created`, `publishing_status`, `revision_number` from both sides before diffing. Output is unified diff format:

```
--- version 3
+++ draft
@@ -1,7 +1,7 @@
 {
   "meta": {
     "app_id": 7,
-    "description": "Original description",
+    "description": "Updated description",
```

When there are no semantic changes: `no differences`. `--format json` emits `{"ok": true, "data": {"diff": "...", "has_changes": true/false}}`.

---

### `integry templates test`

Test a FUNCTION template's draft version by calling it via the signed-request API. Automatically fetches workspace app credentials, resolves connected accounts, and computes the HMAC hash.

```
integry templates test <id> [--input <file|->] [--connected-account-id N] [--format toon|json] [--full]
```

| Flag | Effect |
|---|---|
| `<id>` (positional, required) | Template primary key. Must be `type=FUNCTION`. |
| `--input, -i` | Path to a JSON file with function arguments, or `-` for stdin. Defaults to `{}` if omitted. |
| `--connected-account-id N` | Use this connected account directly - skips the app lookup and selection prompt. |
| `--format` | `toon` (default) or `json`. |
| `--full` | Disable field filtering and string truncation. |

Prerequisites: must be authenticated and have an account selected (`accounts use <id>`).

Flow:
1. Fetches the template, verifies `type=FUNCTION`, extracts `machine_name`.
2. Gets the active account's workspace app `key` and `secret`.
3. Computes signed-request credentials (`HMAC-SHA256`).
4. Fetches connected accounts for the app. Single account → auto-selected; multiple → interactive prompt.
5. Calls the function against the draft version, passing the arguments body.

Output:

```
result: "sent"
```

Exits `1` on function call failure, `3` if the template is not FUNCTION, has no `machine_name`, no account is selected, or no connected accounts exist.

---

## Examples

```bash
# Local backend, fresh login
export INTEGRY_API_URL=http://localhost:8000
integry auth login --username alice

# Discover the accounts you belong to, then pin one
integry accounts list
integry accounts list --search acme   # narrow the list when you belong to many
integry accounts use 7

# List templates in the active account, narrow to FUNCTION templates
integry templates list
integry templates list --type FUNCTION
integry templates list --search send --type FUNCTION --type ACTION
# ...or one-shot via env (wins over the persisted value)
INTEGRY_ACCOUNT_ID=7 integry templates list-versions 42

# Create a template from a file
integry templates create --input ./examples/my-template.json

# Pipe a body through stdin
jq '.template' big.json | integry templates create --input -

# Bump a template's revision number and patch its name
integry templates update 42 --input ./patch.json --revision 11 --meta-only

# Ask for full JSON output for an agent
integry templates update 42 --input ./patch.json --meta-only --format json

# Publish a draft
integry templates update 42 --input ./empty-meta.json --publish --meta-only

# Inspect a template and its version history
integry templates get 42
integry templates list-versions 42
integry templates get 42 --version 3

# Publish the current draft as a new version with a changelog
integry templates publish 42 --changelog "Added retry logic"

# Clone a published version back into a fresh draft
integry templates get-draft 42 --version 3 --full --format json | jq '.data' > /tmp/draft.json
integry templates create --input /tmp/draft.json

# Diff a version against the current draft
integry templates diff 42 --version 3
integry templates diff 42 --version 3 --context 10

# Bootstrap a template body from --example
integry templates create --example > template.json
integry templates update 42 --example > patch.json

# Test a FUNCTION template draft
integry templates test 42
integry templates test 42 --input args.json
integry templates test 42 --input args.json --connected-account-id 12345
```

## Further reading

- [`docs/`](docs/) - architecture, conventions, version control, per-command feature docs
- [AXI principles](https://axi.md)
