Metadata-Version: 2.4
Name: brickly-sdk
Version: 0.1.1
Summary: Brickly Brick Python runtime 官方 SDK。
Author: Brickly
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# brickly-sdk

Brickly Brick **Python runtime** 官方 SDK。它通过 stdin/stdout 与宿主通信，封装 BPP
（Brickly Plugin Protocol），让 Python Brick 可以专注写命令逻辑，而不是手写协议消息。

stdout 会被 SDK 保留给 BPP 协议消息；业务日志请使用 `brick.log(...)` 写入 stderr。

## 安装

```bash
pip install brickly-sdk==0.1.0
```

国内环境可以使用 PyPI 镜像：

```bash
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple brickly-sdk==0.1.0
```

## 快速开始

```python
from brickly import BricklyRuntime

brick = BricklyRuntime("com.example.python")


@brick.on_command("hello")
def hello(ctx, input_value):
    ctx.progress(0.5, "处理中...")
    ctx.chunk("hello\n")
    return {"ok": True, "input": input_value}


brick.run()
```

SDK 会自动处理：

- `host.hello` -> `runtime.ready`
- `runtime.ping` -> `runtime.pong`
- `command.invoke` 命令分发
- `command.cancel` 取消信号、`ctx.is_cancelled()` 和 `ctx.on_cancel(...)`
- `command.progress`、`command.chunk` 和 `command.output`
- `host.*` 请求 ID 分配，以及 `host.result` / `host.error` 路由
- `runtime.shutdown` -> 可选 shutdown hook -> `runtime.bye`
- 子窗口创建、窗口方法调用、`window.*` 事件路由
- 事件总线 `events.publish(...)` / `events.on(...)`
- 跨 Brick 调用 `invoke(...)`、`invoke_root(...)`、`invoke_stream(...)`
- 跨 Brick 会话 `open_session(...)`
- 平台能力 `platform.screenshot`、`platform.screen`、`platform.input`、`platform.clipboard`、`platform.system`

## 与 Node SDK 的同步关系

Python SDK 的协议语义与 `@syllm/brickly-sdk` 保持一致：

- Python 使用 `snake_case` 方法名，例如 `create_browser_window()`、`set_full_screen()`。
- Node 使用 `camelCase` 方法名，例如 `createBrowserWindow()`、`setFullScreen()`。
- 两者底层发送的 BPP 消息类型和窗口方法名一致。
- Python 不提供 Node 的 TypeScript `CommandMap` 类型生成能力；Python 侧依赖类型标注和中文 docstring 提供 IDE 补全。

## 命令上下文

命令处理函数会收到 `CommandContext`：

```python
@brick.on_command("process")
def process(ctx, input_value):
    ctx.progress(0.2, "准备中")

    if ctx.is_cancelled():
        return {"cancelled": True}

    ctx.output("debug", {"step": "start"})
    ctx.chunk("第一段输出\n")
    return {"ok": True}
```

常用属性和方法：

- `ctx.request_id`：当前请求 ID
- `ctx.command_id`：当前命令 ID
- `ctx.invocation`：宿主传入的可信调用来源和依赖 Profile 映射
- `ctx.config`：当前 Profile 配置快照
- `ctx.ui`：子窗口 API
- `ctx.events`：事件总线 API
- `ctx.platform`：平台能力 API
- `ctx.system`：`ctx.platform.system` 的快捷别名
- `ctx.progress(value, message)`：上报进度
- `ctx.chunk(chunk, name=None)`：追加流式输出
- `ctx.output(name, value)`：设置具名输出
- `ctx.invoke(...)`：在命令作用域内调用另一个 Brick
- `ctx.invoke_stream(...)`：流式调用另一个 Brick
- `ctx.open_session(...)`：打开跨 Brick 会话

## 子窗口

```python
from brickly import BricklyRuntime

brick = BricklyRuntime("com.example.window")


@brick.on_command("open")
def open_window(ctx, _input):
    win = ctx.ui.create_browser_window("ui/index.html", {"width": 640, "height": 480})
    win.on("closed", lambda payload: brick.log("窗口已关闭", payload))
    win.set_title("Hello from Python")
    win.web_contents.send("app:hello", {"requestId": ctx.request_id, "text": "你好"})
    return {"windowId": win.id}


brick.run()
```

`WindowHandle` 提供与 Node SDK 对齐的常用窗口方法，例如：

- 几何尺寸：`set_bounds()`、`get_bounds()`、`set_position()`、`set_size()`
- 内容区域：`set_content_bounds()`、`get_content_bounds()`、`set_content_size()`
- 状态切换：`minimize()`、`maximize()`、`restore()`、`show()`、`hide()`、`focus()`
- 状态查询：`is_visible()`、`is_focused()`、`is_minimized()`、`is_full_screen()`
- 外观能力：`set_title()`、`set_opacity()`、`set_background_color()`、`set_has_shadow()`
- 网络事件：`on_network()`
- webContents：`send()`、`execute_javascript()`、`open_dev_tools()`、`go_back()`、`set_zoom_factor()`、`copy()`、`paste()`、`undo()`

## 跨 Brick 调用

命令处理函数内部使用 `ctx.invoke(...)`：

```python
result = ctx.invoke(
    "com.brickly.openai",
    "chat",
    {"prompt": "hello"},
    profile_id="work",
)
```

如果宿主在 `ctx.invocation.dependencyProfiles` 中给目标 Brick 指定了 Profile，且调用时没有显式传
`profile_id`，SDK 会自动沿用该 Profile。这一点与 Node SDK 行为一致。

命令处理函数外部需要使用显式 root 调用：

```python
result = brick.invoke_root(
    "com.brickly.openai",
    "chat",
    {"prompt": "hello"},
    profile_id="work",
)
```

流式调用：

```python
for event in ctx.invoke_stream("com.brickly.openai", "chat", {"prompt": "hello"}):
    if event["type"] == "progress":
        ctx.progress(event.get("progress", 0), event.get("message"))
    elif event["type"] == "chunk":
        ctx.chunk(event.get("chunk"))
    elif event["type"] == "output":
        ctx.output(event["name"], event.get("value"))
    elif event["type"] == "result":
        return event["result"]
```

跨 Brick 调用需要在调用方 manifest 的 `dependencies` 中声明目标 Brick 和命令：

```json
"dependencies": {
  "com.brickly.openai": {
    "commands": ["chat"]
  }
}
```

## 跨 Brick 会话

当目标 Brick 需要保留内存状态时，使用 `ctx.open_session(...)`：

```python
session = ctx.open_session("com.brickly.openai", profile_id="work")

try:
    session.invoke("start-thread", {"title": "草稿"})
    reply = session.invoke("chat", {"prompt": "继续这个话题"})
finally:
    session.close()
```

## 平台能力

系统 API：

```python
@brick.on_command("show-app-info")
def show_app_info(ctx, _input):
    return {
        "appName": ctx.system.get_app_name(),
        "appVersion": ctx.system.get_app_version(),
        "userData": ctx.system.get_path("userData"),
        "isWindows": ctx.system.is_windows(),
    }
```

剪贴板 API：

```python
@brick.on_command("replace-clipboard")
def replace_clipboard(ctx, _input):
    previous = ctx.platform.clipboard.read_content()
    updated = ctx.platform.clipboard.set_content({"kind": "text", "text": "来自 Python"})
    return {"previous": previous, "updated": updated}
```

输入和屏幕 API：

```python
@brick.on_command("screen-info")
def screen_info(ctx, _input):
    point = ctx.platform.screen.get_cursor_screen_point()
    display = ctx.platform.screen.get_primary_display()
    return {"point": point, "display": display}


@brick.on_command("click")
def click(ctx, _input):
    ctx.platform.input.mouse_click(100, 100)
    ctx.platform.input.keyboard_tap("A", "control")
    return {"ok": True}
```

这些能力由宿主按 manifest 权限校验。缺少权限时，宿主会返回 `host.error`，SDK 会抛出 `BppError`。

## 错误处理

抛出 `BppError` 可以保留明确错误码：

```python
from brickly import BppError

raise BppError("INVALID_INPUT", "url 不能为空")
```

普通异常会被 SDK 转换为 `INTERNAL_ERROR` 并返回给宿主。

## 日志

```python
brick.log("开始处理", {"id": 1})
```

`brick.log(...)` 会写入 stderr。不要手动给日志加 `[brickId]` 前缀，宿主日志中心会自动附加 Brick ID、来源、流和作用域信息。
