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
101 changes: 101 additions & 0 deletions examples/auth/auth_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
Interactive example: pick how to start authentication.

Both flows are unauthenticated (no api_key / jwt_token required):

- Email: sends a magic sign-in link via ``UserService.start_email_verification``.
- X (Twitter): returns the OAuth URL via ``UserService.get_x_oauth_url``.

Copy ``mate.yaml.example`` to ``mate.yaml`` if you want a custom ``base_url``;
credentials in that file are not needed for this script.
"""

from __future__ import annotations

import os

from wyseos.mate import Client, ClientOptions
from wyseos.mate.config import load_config
from wyseos.mate.errors import APIError


def create_client() -> Client:
"""Client without credentials; optional mate.yaml for base_url."""
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "mate.yaml")

if os.path.exists(config_path):
print(f"Loading config: {config_path}")
return Client(load_config(config_path))

print("No mate.yaml found, using default base_url without credentials.")
return Client(ClientOptions())


def run_email_flow(client: Client) -> None:
email = input("Email address: ").strip()
if not email:
print("Email is required.")
return

invite = input("Invite code (optional, press Enter to skip): ").strip()
invite_code = invite or None

try:
resp = client.user.start_email_verification(
email=email,
invite_code=invite_code,
)
except APIError as exc:
print(f"Request failed: {exc}")
return

print("\nSign-in link request accepted. Response:")
print(f" sign_type: {resp.sign_type}")
print(f" pre_auth_id: {resp.pre_auth_id}")
print(
"\nThe user should open the link in the email to finish sign-in."
)


def run_twitter_oauth_flow(client: Client) -> None:
try:
resp = client.user.get_x_oauth_url()
except APIError as exc:
print(f"Request failed: {exc}")
return

print("\nX (Twitter) OAuth authorization URL:")
print(resp.auth_url)
print(
"\nOpen the URL in a browser to finish login. The backend redirects to "
"the configured webapp host."
)


def print_menu() -> None:
print("\n=== Choose auth method ===")
print("1) Email — send verify link for sign-in / sign-up")
print("2) X (Twitter) — get OAuth login URL")
print("q) Quit")


def main() -> None:
client = create_client()

while True:
print_menu()
choice = input("Choice: ").strip().lower()
if choice == "q":
break
if choice == "1":
run_email_flow(client)
elif choice == "2":
run_twitter_oauth_flow(client)
else:
print("Unknown option.")


if __name__ == "__main__":
main()
117 changes: 117 additions & 0 deletions examples/auth/connectors_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""
Example: manage X (Twitter) connector accounts.

Demonstrates the three X-connector endpoints on UserService:
- list_x_accounts() -> GET /connectors/v1/x/accounts
- authorize_x_account() -> POST /connectors/v1/x/accounts/authorize
- delete_x_account(id) -> DELETE /connectors/v1/x/accounts/{id}

Requires a valid api_key (or jwt_token) in mate.yaml.
"""

import os
from typing import Optional

from wyseos.mate import Client
from wyseos.mate.config import load_config
from wyseos.mate.errors import APIError


def create_client() -> Optional[Client]:
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "mate.yaml")
try:
print(f"Loading config: {config_path}")
return Client(load_config(config_path))
except Exception as exc:
print(f"Failed to load config: {exc}")
print("Please configure mate.yaml with a valid api_key or jwt_token.")
return None


def print_menu() -> None:
print("\n=== X Connector Menu ===")
print("1) List connected X accounts")
print("2) Authorize a new / existing X account")
print("3) Delete a connected X account")
print("q) Quit")


def do_list(client: Client) -> None:
try:
result = client.user.list_x_accounts()
except APIError as exc:
print(f"List failed: {exc}")
return

if not result.items:
print("No X accounts are currently connected.")
return

print(f"Connected X accounts ({len(result.items)}):")
for idx, account in enumerate(result.items, start=1):
print(
f" {idx}. connector_id={account.connector_id} "
f"username=@{account.external_username} "
f"status={account.status} "
f"expires_at={account.expires_at}"
)


def do_authorize(client: Client) -> None:
target = input(
"Target connector_id (leave empty to create a new connector): "
).strip()
try:
resp = client.user.authorize_x_account(
target_connector_id=target or None,
)
except APIError as exc:
print(f"Authorize failed: {exc}")
return

print("Open this URL in a browser to complete the binding:")
print(resp.auth_url)


def do_delete(client: Client) -> None:
connector_id = input("connector_id to delete: ").strip()
if not connector_id:
print("connector_id is required.")
return

try:
client.user.delete_x_account(connector_id)
except APIError as exc:
print(f"Delete failed: {exc}")
return

print(f"Deleted X connector {connector_id}.")


def main() -> None:
client = create_client()
if not client:
return

actions = {
"1": do_list,
"2": do_authorize,
"3": do_delete,
}

while True:
print_menu()
choice = input("Choose an action: ").strip().lower()
if choice == "q":
break
action = actions.get(choice)
if action is None:
print("Unknown option.")
continue
action(client)


if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions examples/auth/mate.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mate:
# API base URL (override only when targeting a non-default environment).
base_url: "https://api.wyseos.com"
timeout: 30

# Required for connectors_example.py (list / authorize / delete X accounts).
# Not required for auth_example.py — those flows are public.
# Uncomment and fill in to use connector endpoints:
# api_key: "your-api-key"
59 changes: 56 additions & 3 deletions examples/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,49 @@ from wyseos.mate.config import load_config
client = Client(load_config("mate.yaml"))
```

## 3. Choose One Workflow
## 3. Register an Account (Two Ways)

Sign-in and sign-up both start without `api_key` or `jwt_token` on the client (these endpoints use `skip_auth=True`). You can use `Client(ClientOptions())` for the default `base_url`, or `load_config("mate.yaml")` if you only need a custom `base_url`.

1. **Email magic link** — send a link to the address; the user opens it in a browser to finish sign-in or sign-up:

```python
from wyseos.mate import Client, ClientOptions

client = Client(ClientOptions()) # or load_config("mate.yaml") without credentials

resp = client.user.start_email_verification(
email="you@example.com",
invite_code=None, # optional invite code
)
# resp.sign_type, resp.pre_auth_id — use after the user follows the link per product flow
```

2. **X (Twitter) OAuth** — get a login URL; open it in a browser to complete sign-in or sign-up with X:

```python
url_resp = client.user.get_x_oauth_url()
print(url_resp.auth_url) # open in browser
```

Runnable reference: `examples/auth/auth_example.py`.

## 4. X (Twitter) Authorization: Functions

Distinguish **account login/registration** (no API key) from **binding X to an existing account** (requires `api_key` or `jwt_token` after you are signed in).

| Purpose | Call | Auth required |
| ------- | ---- | ------------- |
| Sign in / sign up with X | `client.user.get_x_oauth_url()` | No |
| List connected X accounts | `client.user.list_x_accounts()` | Yes |
| Bind or re-bind an X account (connector) | `client.user.authorize_x_account(target_connector_id=None)` | Yes; optional `target_connector_id` selects the credential slot |
| Remove a connected X account | `client.user.delete_x_account(connector_id)` | Yes |

`authorize_x_account` returns an `OAuthURLResponse` with `auth_url` — open that URL in a browser to complete the connector flow.

Runnable reference: `examples/auth/connectors_example.py`.

## 5. Choose One Workflow

| Workflow | Entry Point | Transport | Main Output |
| ----------------- | -------------------------------------------------------- | ------------ | ------------------------------------------ |
Expand Down Expand Up @@ -187,7 +229,7 @@ Runnable example: `examples/product_analysis/example.py`.

---

## 4. Error Handling
## 6. Error Handling

```python
from wyseos.mate.errors import APIError, NetworkError, ConfigError, WebSocketError
Expand All @@ -205,7 +247,18 @@ except ConfigError as e:
print("ConfigError:", e)
```

## 5. Related APIs
## 7. Related APIs

Account registration (no `api_key` / `jwt_token` required):

- `client.user.start_email_verification(email, invite_code=None)`
- `client.user.get_x_oauth_url()`

X connector (after sign-in, with `api_key` or `jwt_token`):

- `client.user.list_x_accounts()`
- `client.user.authorize_x_account(target_connector_id=None)`
- `client.user.delete_x_account(connector_id)`

Marketing:

Expand Down
59 changes: 56 additions & 3 deletions examples/quickstart_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,49 @@ from wyseos.mate.config import load_config
client = Client(load_config("mate.yaml"))
```

## 3. 选择一个工作流
## 3. 注册账户(两种方式)

登录与注册都可在**不提供** `api_key`、`jwt_token` 的情况下发起(这些接口在 SDK 中走 `skip_auth`)。无密钥时可用 `Client(ClientOptions())` 使用默认 `base_url`,或仅用 `load_config("mate.yaml")` 配置自定义 `base_url`。

1. **邮箱 magic link** — 向邮箱发送登录/注册链接,用户在浏览器中打开完成验证:

```python
from wyseos.mate import Client, ClientOptions

client = Client(ClientOptions()) # 或使用不含密钥的 mate.yaml

resp = client.user.start_email_verification(
email="you@example.com",
invite_code=None, # 可选填邀请码
)
# resp.sign_type、resp.pre_auth_id — 用户点击邮件内链接后按产品流程续接
```

2. **X(Twitter)OAuth** — 获取用于登录/注册的授权 URL,在浏览器中打开完成:

```python
url_resp = client.user.get_x_oauth_url()
print(url_resp.auth_url) # 在浏览器中打开
```

可运行参考:`examples/auth/auth_example.py`。

## 4. 授权与绑定 X(相关函数)

区分两类场景:**账户登录/注册**(无 API 密钥)与**将 X 账号绑定到已有账户**(登录后使用 `api_key` 或 `jwt_token`)。

| 用途 | 调用 | 是否需要已登录 |
| ---- | ---- | -------------- |
| 使用 X 登录或注册 | `client.user.get_x_oauth_url()` | 否 |
| 列出已绑定的 X 账号 | `client.user.list_x_accounts()` | 是 |
| 发起绑定或换绑 X(连接器) | `client.user.authorize_x_account(target_connector_id=None)` | 是;可选 `target_connector_id` 指定要写入的凭据位 |
| 解绑已连接的 X 账号 | `client.user.delete_x_account(connector_id)` | 是 |

`authorize_x_account` 返回 `OAuthURLResponse`,通过 `auth_url` 在浏览器中完成授权。

可运行参考:`examples/auth/connectors_example.py`。

## 5. 选择一个工作流

| 工作流 | 入口 | 传输方式 | 主要输出 |
| -------- | -------------------------------------------------------- | --------- | ----------------------- |
Expand Down Expand Up @@ -186,7 +228,7 @@ if info.analysis_result and info.analysis_result.report_id:

---

## 4. 错误处理
## 6. 错误处理

```python
from wyseos.mate.errors import APIError, NetworkError, ConfigError, WebSocketError
Expand All @@ -204,7 +246,18 @@ except ConfigError as e:
print("ConfigError:", e)
```

## 5. 相关 API
## 7. 相关 API

账户注册(不需要 `api_key` / `jwt_token`):

- `client.user.start_email_verification(email, invite_code=None)`
- `client.user.get_x_oauth_url()`

X 连接器(登录后、带 `api_key` 或 `jwt_token`):

- `client.user.list_x_accounts()`
- `client.user.authorize_x_account(target_connector_id=None)`
- `client.user.delete_x_account(connector_id)`

营销:

Expand Down
Loading
Loading