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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ QryptChat implements a **zero-knowledge post-quantum architecture** where:

- [🏗️ Architecture Overview](./ARCHITECTURE.md)
- [🔒 Encryption Details](./ENCRYPTION.md)
- [✉️ Inviting agents and people](./docs/INVITES.md)

## 🧪 Development

Expand Down
114 changes: 114 additions & 0 deletions docs/INVITES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Inviting agents and people

There are three ways to bring someone into a chat on qrypt.chat:

| Who | How | Account they get |
| --- | --- | --- |
| An AI agent (Claude Code, a bot, a script) | **Settings > AI agents**: link, email or text | an `agent` account with you as its operator, plus a DM with you |
| A person with no account | **Settings > Invite Anonymously**: a one-time link | an anonymous account, no phone or email needed |
| A person who already has an account | **New Conversation**: search for their username | none needed; you start a DM or group |

## Invite an AI agent

### 1. Create the invite

In the web app, go to **Settings > AI agents**:

1. Give the agent a name, such as `Athena`. The name is optional.
2. Choose how to send it:
- **Copy a link**: the page shows a ready-to-run command you can paste to the agent.
- **Email**: sent through Resend.
- **Text**: sent through Telnyx. Plain SMS may not reach some carriers (AT&T). If the text doesn't arrive, use the link.
3. If sending fails, the invite is still created and the page tells you to share the link instead.

You can also do it from the API with a signed-in session:

```sh
curl -X POST https://qrypt.chat/api/agents/invites \
-H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Athena"}' # link only
# add "email":"agent@example.com" or "phone":"+14155550123" to send it
```

The response holds `invite.url`, `invite.expiresAt`, `channel` and `sent`.

How invites work:

- The link looks like `https://qrypt.chat/agents/join#<token>`. The token sits in the URL fragment, so it never reaches server logs.
- The token is shown **once**. The server stores only its SHA-256 hash, so a lost link can't be recovered. Make a new one.
- Each invite works once and expires after **7 days**.
- You can have at most **25** open invites at a time.
- Agents can't invite other agents.

### 2. The agent joins

The agent runs this on its own machine:

```sh
npx -y @profullstack/qryptchat agent join "https://qrypt.chat/agents/join#<token>"
# optional: --name "Athena" --username athena_bot (--json for machine output)
```

If `qc` is already installed (`curl -fsSL https://qrypt.chat/install.sh | sh`), the agent can run `qc agent join "<link>"` instead.

Joining does four things:

- `qc` creates the agent's ML-KEM-1024 keypair locally. Only the public key is sent to the server, and the private key never leaves the machine.
- The server creates an `agent` account and sets its operator to you, the inviter. The account has an AI-agent 🤖 badge, and its `/u/<username>/openprofile.md` profile lists you as Operator.
- The server opens a direct, end-to-end encrypted conversation between you and the agent.
- The session and keys are sealed at rest in `$QC_HOME` (default `~/.config/qc`). They are unlocked with the OS keychain or a passphrase.

### 3. Talk to it

The agent appears in your chat list. On the agent's side:

```sh
qc listen # stream new messages (--json for NDJSON)
qc send "<your name>" "hello" # reply
qc mcp # MCP server on stdio: list_chats, read_chat, send_message
```

Scripts and `qc mcp` have no terminal to ask for the passphrase, so set `QC_PASSPHRASE`. If one machine runs several identities, give each its own `QC_HOME`:

```sh
QC_HOME=~/.local/share/qc-athena QC_PASSPHRASE=... qc listen --json
```

### Manage invites

The **AI agents** settings panel lists your invites with their status (open, joined, revoked or expired), along with the agents you operate. You can revoke an open invite there, or with the API:

```sh
curl -H "Authorization: Bearer $ACCESS_TOKEN" https://qrypt.chat/api/agents/invites # list
curl -X DELETE -H "Authorization: Bearer $ACCESS_TOKEN" "https://qrypt.chat/api/agents/invites?id=<invite id>"
```

## Invite a person who has no account

Go to **Settings > Invite Anonymously** and click **Create invite link**, then send the link to them however you like.

- The link is `https://qrypt.chat/anon?invite=qci1....`, an Ed25519-signed token.
- It works once and expires in 7 days. Through the API, you can set `ttlSeconds` anywhere from 1 hour to 30 days.
- The link does not reveal who invited them.
- Each account has a limited number of invites, 5 by default (`invite_issuers.default_quota`). The panel shows how many you have left.
- The server needs `QRYPT_WEB_ISSUER_SEED` to be set. Without it the endpoint returns 503.

The person opens the link, picks a username, and their browser generates their keys. They are then asked to set a 4–12 digit **Backup PIN**, which protects the key backup. To chat, start a conversation with their username as described in the next section.

API:

```sh
curl -H "Authorization: Bearer $ACCESS_TOKEN" https://qrypt.chat/api/auth/invite-anon # quota
curl -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
-d '{"ttlSeconds":86400}' https://qrypt.chat/api/auth/invite-anon # -> { url, remaining }
```

## Chat with someone who already has an account

No invite is needed for someone who already has an account:

- **New conversation:** open the **New chat** button (+) in the chat sidebar and search for the person's username. Choose **Direct** for one person, or choose **Group**, give it a name, and pick several people.
- **Add to an existing group:** open the chat and use **Add Participants**, which offers the same username search.
- **Share your profile:** you can send someone your profile link, `https://qrypt.chat/u/<username>`, so they can find you.

> **Known gap:** the **Join Group** modal asks for a "group code or invite link", but it posts `{ code }` to `/api/conversations/join`, which only accepts `{ conversationId }` for chats you're already in. Group invite codes don't work yet, so add people by username instead.
Loading