Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 21 additions & 5 deletions .github/workflows/regen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ name: Regenerate SDK
# workflow_dispatch -> manual regen PR from a given spec-repo SHA (default: main HEAD)
#
# Requires a GitHub App (org secrets PIPELINE_APP_ID / PIPELINE_APP_PRIVATE_KEY) and the
# repo secret SPEC_REPO (name of the backend repo that publishes api-spec.yaml). The App
# is installed with Contents read on the spec repo and Contents + Pull-requests write here
# (the default GITHUB_TOKEN would not fire CI on the opened PR).
# repo secret SPEC_REPO (name of the backend repo). Fetches public-spec.yaml -- the
# narrowed customer-facing export; NOT api-spec.yaml, which is the backend's full
# internal-dashboard spec and includes internal-only endpoints never meant for the
# public SDK. The App is installed with Contents read on the spec repo and Contents +
# Pull-requests write here (the default GITHUB_TOKEN would not fire CI on the opened PR).

on:
repository_dispatch:
Expand Down Expand Up @@ -99,15 +101,29 @@ jobs:
RESOLVED_SHA=$(gh api "repos/${OWNER}/${SPEC_REPO}/commits/${REF}" --jq '.sha')
echo "resolved_sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
mkdir -p fern/openapi
gh api "repos/${OWNER}/${SPEC_REPO}/contents/api-spec.yaml?ref=${RESOLVED_SHA}" \
# public-spec.yaml is the backend's narrowed customer-facing export -- NOT
# api-spec.yaml, the backend's full internal-dashboard spec, which includes
# internal-only endpoints never meant for the public SDK surface.
gh api "repos/${OWNER}/${SPEC_REPO}/contents/public-spec.yaml?ref=${RESOLVED_SHA}" \
-H "Accept: application/vnd.github.raw" > fern/openapi/openapi.yml
echo "Fetched api-spec.yaml @ ${RESOLVED_SHA} ($(wc -l < fern/openapi/openapi.yml) lines)"
echo "Fetched public-spec.yaml @ ${RESOLVED_SHA} ($(wc -l < fern/openapi/openapi.yml) lines)"
# The spec must be SHAPED (x-fern shaping present). A raw spec would silently
# produce an unshaped SDK — fail loudly instead.
if ! grep -q 'x-fern-sdk-group-name' fern/openapi/openapi.yml; then
echo "::error::Fetched spec carries no x-fern shaping — backend SDK-shaping export missing at ${RESOLVED_SHA}."
exit 1
fi
# The spec must be NARROWED, not just shaped -- this is the whole point of
# fetching public-spec.yaml instead of api-spec.yaml. A regression upstream (or
# this workflow accidentally repointed at the wrong file/repo) should fail loudly
# here rather than silently publish internal endpoints into the public SDK. Keep
# this list in sync with the backend spec repo's exclusion list if either changes.
INTERNAL_LEAK_RE='^[[:space:]]*-[[:space:]]+(name:[[:space:]]+)?(health|webhooks|api-keys|billing|provider-keys|workspace-aggregates)[[:space:]]*$|^[[:space:]]{2}/api/v1/users/me/(subscription|payment-methods|invoices)(/|:)'
Comment thread
kj-podonos marked this conversation as resolved.
if grep -qE "$INTERNAL_LEAK_RE" fern/openapi/openapi.yml; then
echo "::error::Fetched spec contains an internal-only tag/path — expected public-spec.yaml to exclude these at ${RESOLVED_SHA}."
grep -nE "$INTERNAL_LEAK_RE" fern/openapi/openapi.yml | head -5
exit 1
fi

- uses: actions/setup-node@v4
with: { node-version: '20' }
Expand Down
41 changes: 0 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,6 @@ generated from the live command tree (the same source as `onepin schema`) and ke
<!-- BEGIN GENERATED: cli-commands -->
## CLI command reference

### health

- `onepin health live` — Liveness probe.
- `onepin health ready` — Readiness probe.

### login

- `onepin login` — Validate an API key and write it to ~/.onepin/credentials.
Expand All @@ -113,12 +108,6 @@ generated from the live command tree (the same source as `onepin schema`) and ke
- `onepin nodes list` — List available node types.
- `onepin nodes show <node_type>` — Show a node type's detail (runtime options).

### provider-keys

- `onepin provider-keys delete <provider>` — Delete a provider key.
- `onepin provider-keys list` — List configured provider keys.
- `onepin provider-keys put <provider>` — Create or replace a provider key.

### schema

- `onepin schema` — Emit the machine-readable JSON manifest of all commands.
Expand Down Expand Up @@ -208,11 +197,6 @@ generated from the live command tree (the same source as `onepin schema`) and ke
- `onepin workspace members remove <ws_id> <member_id>` — Remove a member.
- `onepin workspace members revoke-invite <ws_id> <invite_id>` — Revoke a pending invite.
- `onepin workspace members set-role <ws_id> <member_id>` — Change a member's role.

#### workspace stats

- `onepin workspace stats runs` — Workspace run statistics.
- `onepin workspace stats workflows` — Workspace workflow statistics.
<!-- END GENERATED: cli-commands -->

### Global flags
Expand Down Expand Up @@ -332,13 +316,6 @@ onepin workspace members accept <token>

See `onepin workspace members --help` for the full list (`invite-role`, `revoke-invite`).

#### workspace stats — aggregate statistics

```bash
onepin workspace stats runs --from 2026-01-01 --to 2026-02-01
onepin workspace stats workflows
```

### usage — inspect workspace usage and activity

```bash
Expand All @@ -349,30 +326,13 @@ onepin usage by-language --range 90d

See `onepin usage --help` for the full list.

### provider-keys — manage bring-your-own-key credentials

```bash
onepin provider-keys list
onepin provider-keys put <provider> --key '{"api_key": "..."}'
onepin provider-keys delete <provider> --yes
```

Stored credentials are never echoed back; reads return redacted metadata only.

### nodes — inspect available workflow node types

```bash
onepin nodes list
onepin nodes show <node_type>
```

### health — API liveness and readiness probes

```bash
onepin health live
onepin health ready
```

### schema — machine-readable command manifest

```bash
Expand Down Expand Up @@ -448,7 +408,6 @@ client = OnePinClient(token="op_...") # your API key, used as the bearer token

workflows = client.workflows.list() # paginated — iterate items directly
voices = client.voices.list()
ready = client.health.readiness()
```

### Environments
Expand Down
3 changes: 1 addition & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ python examples/quickstart.py

| File | Shows |
|------|-------|
| `quickstart.py` | Construct a client, readiness probe, list workflows |
| `quickstart.py` | Construct a client, list workflows |
| `list_workflows.py` | Paginate workflows, fetch one by id |
| `async_client.py` | `AsyncOnePinClient` + async pagination |
| `error_handling.py` | `ApiError`, retries, timeouts |
| `provider_keys.py` | Bring-your-own provider keys (BYO-key) |

By default the client targets **PROD** (`https://api.onepin.ai`). Pass
`environment=OnePinClientEnvironment.DEV` or `base_url="https://dev-api.onepin.ai"` to
Expand Down
24 changes: 0 additions & 24 deletions examples/provider_keys.py

This file was deleted.

3 changes: 0 additions & 3 deletions examples/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
def main() -> None:
client = OnePinClient(token=os.environ["ONEPIN_API_KEY"])

# Health probe — a cheap first call to confirm connectivity.
print("readiness:", client.health.readiness())

# Workflows in your workspace. `list()` returns a pager you can iterate directly.
for workflow in client.workflows.list():
print("workflow:", workflow)
Expand Down
4 changes: 2 additions & 2 deletions src/onepin/_cli/_skill/onepin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ rather than dumping raw JSON.
## Destructive operations (require explicit confirmation)

`workflows delete`, `templates delete`, `uploads delete`, `workspace delete`,
`provider-keys delete`, `workflows runs cancel`, `workspace members remove`,
`workflows runs cancel`, `workspace members remove`,
`workspace members revoke-invite` — all irreversible. Procedure:

1. **Resolve the exact target.** Confirm the id/name actually exists (via `--search` or a `show`);
Expand All @@ -119,4 +119,4 @@ rather than dumping raw JSON.
- Report failures to the user as `code: message`, plainly.

For the full command catalog and recipes (workflow definitions, uploads, workspace + members,
usage, provider-keys, nodes, health), see [reference.md](reference.md) or run `onepin schema`.
usage, nodes), see [reference.md](reference.md) or run `onepin schema`.
9 changes: 3 additions & 6 deletions src/onepin/_cli/_skill/onepin/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,18 @@ position.
| `templates` | `list`, `show`, `create`, `update`, `delete`, `clone`, `favorite`, `unfavorite` |
| `voices` | `list`, `show`, `similar`, `favorite`, `unfavorite` |
| `uploads` | `create` (presigned S3), `confirm`, `delete` |
| `workspace` | `list`, `show`, `create`, `update`, `delete`, `settings`; subgroups `members`, `stats` |
| `workspace` | `list`, `show`, `create`, `update`, `delete`, `settings`; subgroup `members` |
| `workspace members` | `list`, `invite`, `set-role`, `remove`, `accept`, `invite-role`, `revoke-invite` |
| `workspace stats` | `runs`, `workflows` (accept `--from`/`--to` ISO datetimes) |
| `usage` | `summary`, `by-language`, `activity` (`--range 30d/60d/90d`) |
| `provider-keys` | `list`, `put <provider>`, `delete <provider>` (secrets are redacted on read) |
| `nodes` | `list`, `show <node_type>` (workflow node types + runtime options) |
| `health` | `live`, `ready` |
| `auth` | `login`, `logout`, `whoami` |
| `schema` | the JSON manifest (the contract) |

## Destructive commands (need `--yes`; confirm with the user first)

`workflows delete` · `workflows runs cancel` · `templates delete` · `uploads delete` ·
`workspace delete` · `workspace members remove` · `workspace members revoke-invite` ·
`provider-keys delete`. Under `--json` without `--yes` they return `CONFIRMATION_REQUIRED` — that
`workspace delete` · `workspace members remove` · `workspace members revoke-invite`.
Under `--json` without `--yes` they return `CONFIRMATION_REQUIRED` — that
means stop and ask, not retry. `templates unfavorite` / `voices unfavorite` are *not* destructive.

## Recipe: build a workflow definition
Expand Down
73 changes: 0 additions & 73 deletions src/onepin/_cli/_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,33 +709,6 @@ def _list_opts(*extra: Opt) -> list[Opt]:
unwrap="action",
success_msg="Accepted invite.",
),
# --- workspace stats (subgroup) -----------------------------------------------------
Cmd(
"workspace",
"runs",
"workspace_aggregates.workspace_runs_stats",
"Workspace run statistics.",
subgroup="stats",
options=[
Opt("--from", "datetime", None, dest="from_", transform="datetime", help="Start (ISO 8601)."),
Opt("--to", "datetime", None, dest="to", transform="datetime", help="End (ISO 8601)."),
_JSON,
],
unwrap="data",
),
Cmd(
"workspace",
"workflows",
"workspace_aggregates.workspace_workflows_stats",
"Workspace workflow statistics.",
subgroup="stats",
options=[
Opt("--from", "datetime", None, dest="from_", transform="datetime", help="Start (ISO 8601)."),
Opt("--to", "datetime", None, dest="to", transform="datetime", help="End (ISO 8601)."),
_JSON,
],
unwrap="data",
),
# --- usage --------------------------------------------------------------------------
Cmd(
"usage",
Expand Down Expand Up @@ -804,50 +777,6 @@ def _list_opts(*extra: Opt) -> list[Opt]:
unwrap="list",
columns=[],
),
# --- provider-keys (secret redaction) -----------------------------------------------
Cmd(
"provider-keys",
"list",
"provider_keys.list_provider_keys",
"List configured provider keys.",
options=[_JSON],
unwrap="data",
redact=True,
),
Cmd(
"provider-keys",
"put",
"provider_keys.put_provider_key",
"Create or replace a provider key.",
args=[("provider", "Provider name (e.g. elevenlabs).")],
options=[
Opt(
"--key",
"str",
None,
dest="request",
required=True,
transform="provider_key_request",
help='Credential payload: a raw key string (wrapped as {"api_key": ...}) or inline JSON.',
),
_JSON,
],
unwrap="data",
redact=True,
success_msg="Saved provider key for {provider}.",
),
Cmd(
"provider-keys",
"delete",
"provider_keys.delete_provider_key",
"Delete a provider key.",
args=[("provider", "Provider name.")],
options=[_JSON],
unwrap="data",
redact=True,
success_msg="Deleted provider key for {provider}.",
destructive=True,
),
# --- nodes --------------------------------------------------------------------------
Cmd(
"nodes",
Expand All @@ -867,6 +796,4 @@ def _list_opts(*extra: Opt) -> list[Opt]:
options=[_JSON],
unwrap="data",
),
# health (live/ready) is hand-written in commands/health.py -- it blends local SDK version,
# the API's reported version, and version-gate headers, which the table model can't express.
]
10 changes: 3 additions & 7 deletions src/onepin/_cli/_update_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,7 @@ def _parse_cache(line: str) -> Optional[tuple[str, str]]:


def cached_latest() -> Optional[str]:
"""Latest version recorded in the cache (any freshness); ``None`` if unknown.

Used by ``onepin health`` to show the recommended version without a network call.
"""
"""Latest version recorded in the cache (any freshness); ``None`` if unknown."""
parsed = _parse_cache(_read_text(_cache_path()))
return parsed[1] if parsed else None

Expand All @@ -160,9 +157,8 @@ def _resolve_latest(current: str) -> Optional[str]:
return fresh
latest = _fetch_latest()
if latest is None:
# Offline / fetch error: do not cache. A failure must not masquerade as "up to date"
# (which would also mislead `onepin health`), and the next run should retry rather than
# stay silent for the full TTL.
# Offline / fetch error: do not cache. A failure must not masquerade as "up to date",
# and the next run should retry rather than stay silent for the full TTL.
return None
if is_older(current, latest):
_write_cache(f"UPGRADE_AVAILABLE {current} {latest}")
Expand Down
13 changes: 2 additions & 11 deletions src/onepin/_cli/commands/_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Wires:
- ``auth`` (login/logout/whoami) -- existing raw-httpx commands, untouched.
- Every table-driven command from :data:`onepin._cli._spec.TABLE`, grouped into sub-Typers
(with nested sub-Typers for ``workflows runs`` and ``workspace members``/``stats``).
(with a nested sub-Typer for ``workflows runs``).
- Hand-written composites (``workflows run``, ``uploads create``, run downloads,
``workflows definition-schema``).
- The top-level ``schema`` manifest command.
Expand All @@ -16,7 +16,7 @@
from onepin._cli import _dispatch, _manifest
from onepin._cli._spec import TABLE, Cmd
from onepin._cli._update_check import upgrade_check
from onepin._cli.commands import auth, composites, health, skill
from onepin._cli.commands import auth, composites, skill

# Per-group help text. Groups not listed fall back to a generic header.
_GROUP_HELP = {
Expand All @@ -26,15 +26,12 @@
"uploads": "Manage file uploads (presigned-S3 flow).",
"workspace": "Manage workspaces, members, and statistics.",
"usage": "Inspect workspace usage and activity.",
"provider-keys": "Manage bring-your-own-key provider credentials.",
"nodes": "Inspect available workflow node types.",
"health": "API liveness and readiness probes.",
}

_SUBGROUP_HELP = {
("workflows", "runs"): "Inspect and control workflow runs.",
("workspace", "members"): "Manage workspace members and invites.",
("workspace", "stats"): "Workspace aggregate statistics.",
}


Expand All @@ -61,12 +58,6 @@ def register(app: typer.Typer) -> None:
skill_app.command(name="uninstall", help="Remove the installed Onepin agent skill.")(skill.uninstall)
app.add_typer(skill_app, name="skill", help="Manage the Onepin agent skill for AI coding tools.")

# health (live/ready): hand-written so it can surface SDK/API/recommended/required versions.
health_app = typer.Typer(help=_GROUP_HELP["health"], no_args_is_help=True)
health_app.command(name="live", help="Liveness probe.")(health.live)
health_app.command(name="ready", help="Readiness probe.")(health.ready)
app.add_typer(health_app, name="health", help=_GROUP_HELP["health"])

# Hidden: the /onepin agent skill runs this to drive the upgrade prompt (not user-facing).
app.command(name="upgrade-check", hidden=True)(upgrade_check)

Expand Down
Loading
Loading