Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
69 changes: 67 additions & 2 deletions docs/contributing/package-invocation-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Authentication uses a private bearer token stored in Kody's database-backed
package invocation token table.

Kody stores only the token hash for request-time lookup. The raw bearer token is
generated or collected by the token-management surface, shown to the operator
once, and then sent by the trusted external service as:
generated by a trusted client or operator, pasted into the account token
creation flow once, and then sent by the trusted external service as:

```http
Authorization: Bearer <raw-token>
Expand All @@ -59,6 +59,55 @@ The token is not a global backdoor:
- tokens can be revoked without deploys
- execution uses normal package runtime machinery

### Creating and revoking tokens

Signed-in users manage package invocation tokens from:

- `/account/package-invocation-tokens`
- `/account/package-invocation-tokens/new`

The creation form accepts the raw token exactly once, hashes it server-side, and
stores only the hash. Token lists never return the raw token or token hash.

The backing same-origin JSON endpoint is:

- `GET /account/package-invocation-tokens.json` — list owned packages and token
metadata
- `POST /account/package-invocation-tokens.json` with `action: "create"` —
create a token from a raw token and allowlists
- `POST /account/package-invocation-tokens.json` with `action: "revoke"` —
revoke a token by id

Create payload shape:

```json
{
"action": "create",
"name": "Personal Raycast",
"rawToken": "<raw-token>",
"packageKodyIds": ["*"],
"packageIds": [],
"exportNames": ["*"],
"sources": ["raycast"]
}
```

Concrete `packageIds` and `packageKodyIds` must refer to packages owned by the
signed-in user. Package and export allowlists support the deliberate wildcard
value `*`. Source values do not use wildcard matching: if a request includes
`source`, that exact value must be present in the token's `sources` list.

The `/new` page supports metadata prefill query parameters for setup flows:

- `name`
- `packageKodyId` / `packageKodyIds` / `kodyId` / `kodyIds`
- `packageId` / `packageIds`
- `exportName` / `exportNames`
- `source` / `sources`

Repeat parameters or comma-separate values. Do not put the raw token in the URL;
paste it into the form or submit it over the authenticated same-origin JSON API.

## Request body

```json
Expand Down Expand Up @@ -202,6 +251,22 @@ curl --fail --silent \
}'
```

## Personal Raycast client pattern

Recommended flow for a personal Raycast extension:

1. generate a high-entropy raw token locally
2. copy the raw token to the clipboard or keep it in Raycast's local secret
storage
3. open Kody at
`/account/package-invocation-tokens/new?name=Personal%20Raycast&packageKodyIds=*&exportNames=*&sources=raycast`
4. paste the raw token into the form and create the token
5. send invocation requests with `Authorization: Bearer <raw-token>` and
`"source": "raycast"`

The Raycast wildcard token can call any package/export owned by the signed-in
Kody user, but only for requests that identify themselves with source `raycast`.

## Related

- [Packages and manifests](./packages-and-manifests.md)
Expand Down
7 changes: 7 additions & 0 deletions packages/worker/client/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function App(handle: Handle) {

return () => {
const isWideLayout =
currentPathname.startsWith('/account/package-invocation-tokens') ||
currentPathname.startsWith('/account/remote-connectors') ||
currentPathname.startsWith('/account/secrets')
const sessionEmail = session?.email ?? ''
Expand Down Expand Up @@ -182,6 +183,12 @@ export function App(handle: Handle) {
<a href="/account/secrets" mix={css(navLinkCss)}>
Secrets
</a>
<a
href="/account/package-invocation-tokens"
mix={css(navLinkCss)}
>
Package tokens
</a>
<a href="/account/remote-connectors" mix={css(navLinkCss)}>
Connectors
</a>
Expand Down
Loading