Skip to content
Draft
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
14 changes: 13 additions & 1 deletion crates/buzz-acp/src/base_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The `buzz` CLI is your primary interface. Auth env vars: `BUZZ_RELAY_URL`, `BUZZ
| `buzz agents` | `draft-create`, `draft-update` |
| `buzz messages` | `send`, `get`, `thread`, `search` |
| `buzz channels` | `list`, `get`, `create`, `join`, `members` |
| `buzz canvas` | `get`, `set` |
| `buzz canvas` | `get`, `set`, `notify` |
| `buzz reactions` | `add`, `remove` |
| `buzz dms` | `list`, `open` |
| `buzz users` | `get`, `set-profile`, `presence` |
Expand Down Expand Up @@ -40,6 +40,18 @@ A project is a named grouping (`kind:30621`) with a home channel. Creating a sec

To assign an issue to someone, run `buzz issues assign --issue <event-id> --repo-owner <hex> --repo-id <id> --assignee <hex> --label <name>` after creating it. Remove an assignment with the matching `buzz issues unassign` arguments. Writing assignee names in the issue body or adding recipients with `issues create --to` is notification/presentation only — Buzz Desktop's Assignees rail and the "Assigned to me" filter read the signed assignment operations. Only operations signed by the issue author or repo owner are trusted for other people; anyone may assign or unassign themselves.

## Project Canvas Packages

Project widget Canvases are local packages, distinct from relay-backed channel Canvas markdown. When asked to update one, read the active nest's `CANVASES/index.json`, match the exact community and canonical `30621:<owner>:<dtag>` project coordinate, and edit only that entry's `sourcePath`. Never edit `index.json` or anything under `.runtime/`.

- Widget values live in `data/*.json`; dashboard and widget placement is declared there too, but counts as a presentation change when notifying Buzz. Presentation code lives in `widgets/*.js`, `canvas.js`, and `styles/*.css`. Assets stay in `assets/`. Declare added files in `manifest.json`, keep `canvas.js` last, and do not add `index.html`, dependencies, builds, or network access.
- After changing only one widget's data, run `buzz canvas notify --source <sourcePath> --widget <widget-id> --change data`. Buzz passes the new data to the live widget without replacing its iframe; object renderers may implement `update(currentElement, nextData, previousData, api)` to animate, while function renderers remount that widget's content.
- After changing JavaScript, CSS, layout, assets, the manifest, or other presentation behavior, run `buzz canvas notify --source <sourcePath> --widget <widget-id> --change presentation`. Buzz validates the full package and swaps in a fresh sandboxed iframe only after it renders; the manual Reload Canvas button remains available.
- Live project data is authoritative and comes through the host SDK at `window.buzzCanvas.sdk` (loaded before every package script). Render from `sdk.data.liveQuery(name, params, onUpdate)` (returns a stop function; call it before re-subscribing) or one-shot `sdk.data.query(name, params)`; results are `{status: "loading"|"ready"|"error", data}`. Queries: `project.metadata`, `project.channels.list`, `project.reviews.list`, `project.tasks.list`, `project.tasks.get`, `people.lookup` (≤32 pubkeys), `people.search`. Mutations use `sdk.data.command(name, params)`: `tasks.setStatus` (`{id, status: "open"|"done"|"closed"|"draft"}`), `tasks.assign`/`tasks.unassign` (`{id, assignee?}`, defaulting to the viewing user), and `dm.send` (`{pubkey, message}`, ≤2000 chars — sends a direct message as the viewing user). Navigation uses `sdk.app.open({type: "channel"|"task"|"review", id})` or `{type: "user", pubkey}`. All data is scoped to the hosting project; packages cannot widen it. Change values through Buzz rather than hardcoding demo rows.
- Every query maps to a manifest capability: `project.metadata.read`, `project.channels.read`, `project.reviews.read`, `project.tasks.read`, `project.people.read`, plus `project.tasks.write` for task commands, `app.open` for navigation, and `app.dm.send` for `dm.send`. Check `sdk.capabilities()` and render a fallback state when one is missing. `project.tasks.write`, `app.open`, and `app.dm.send` additionally require a one-time user approval per package revision; denial keeps read queries working.
- Budgets: ≤16 concurrent live queries, ≤10 commands per minute, ≤3 opens per 10 seconds, 64 KiB per message. A violation fails that one request with `error.code === "rate-limited"` — it never kills the canvas.
- Prefer the standard components `sdk.ui.avatar({name, avatarUrl, agent, size})`, `sdk.ui.reviewRow({review, onOpen})`, and `sdk.ui.channelRow({channel, onOpen})`, themed by host `--buzz-*` CSS variables, so people and rows render consistently with the app. `canvas notify` is local-only, requires Buzz Desktop to be running, and does not create a relay event.

## Conversational Agent Creation

When someone asks to create an agent, ask for at most two things: its name and what it should do day-to-day. Write the `--system-prompt` yourself. Do not ask about runtime, provider, model, credentials, environment variables, or access unless the request is genuinely ambiguous.
Expand Down
24 changes: 24 additions & 0 deletions crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5119,6 +5119,30 @@ mod agent_draft_prompt_tests {
.contains("add them explicitly with `buzz channels add-member` only when authorized"));
assert!(prompt.contains("never changes membership automatically"));
}

#[test]
fn shared_base_prompt_teaches_project_canvas_notifications() {
let prompt = include_str!("base_prompt.md");
assert!(prompt.contains("buzz canvas notify --source"));
assert!(prompt.contains("--change data"));
assert!(prompt.contains("--change presentation"));
assert!(prompt.contains("Never edit `index.json` or anything under `.runtime/`"));
}

#[test]
fn shared_base_prompt_documents_the_canvas_sdk_surface() {
let prompt = include_str!("base_prompt.md");
assert!(prompt.contains("window.buzzCanvas.sdk"));
assert!(prompt.contains("sdk.data.liveQuery"));
assert!(prompt.contains("tasks.setStatus"));
assert!(prompt.contains("sdk.capabilities()"));
assert!(prompt.contains("project.tasks.write"));
assert!(prompt.contains("`app.open`"));
assert!(prompt.contains("`dm.send`"));
assert!(prompt.contains("`app.dm.send`"));
assert!(prompt.contains("rate-limited"));
assert!(prompt.contains("sdk.ui.avatar"));
}
}

fn default_heartbeat_prompt() -> String {
Expand Down
2 changes: 2 additions & 0 deletions crates/buzz-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ buzz messages vote --event <event-id> --direction up
# Canvas
buzz canvas get --channel <uuid>
buzz canvas set --channel <uuid> --content "# Welcome"
buzz canvas notify --source <project-canvas-dir> --widget <widget-id> --change data
buzz canvas notify --source <project-canvas-dir> --widget <widget-id> --change presentation

# Agent Memory (NIP-AE)
buzz mem ls
Expand Down
82 changes: 44 additions & 38 deletions crates/buzz-cli/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ echo "# Canvas from stdin" | buzz canvas set --channel "$CHANNEL_ID" --content -
# canvas get
buzz canvas get --channel "$CHANNEL_ID"
# Expected: raw markdown string, or: null

# Local project Canvas notification (Buzz Desktop must be running)
buzz canvas notify --source "$CANVAS_SOURCE" --widget chores --change data | jq .
buzz canvas notify --source "$CANVAS_SOURCE" --widget chores --change presentation | jq .
# Expected: accepted true; data preserves the iframe, presentation reloads it
```

### 6.3 Messages
Expand Down Expand Up @@ -587,41 +592,42 @@ buzz channels delete --channel "$FORUM_ID" | jq .
| 22 | `channels remove-member` | ☐ | Needs admin:channels |
| 23 | `canvas get` | ☐ | |
| 24 | `canvas set` | ☐ | Direct and stdin |
| 25 | `reactions add` | ☐ | |
| 26 | `reactions remove` | ☐ | |
| 27 | `reactions get` | ☐ | |
| 28 | `dms list` | ☐ | |
| 29 | `dms open` | ☐ | |
| 30 | `dms add-member` | ☐ | Needs messages:write |
| 31 | `users get` | ☐ | Self, single, batch |
| 32 | `users set-profile` | ☐ | |
| 33 | `users presence` | ☐ | |
| 34 | `users set-presence` | ☐ | online, away, offline |
| 35 | `workflows list` | ☐ | |
| 36 | `workflows create` | ☐ | |
| 37 | `workflows update` | ☐ | |
| 38 | `workflows delete` | ☐ | |
| 39 | `workflows trigger` | ☐ | |
| 40 | `workflows runs` | ☐ | |
| 41 | `workflows get` | ☐ | |
| 42 | `workflows approve` | ☐ | Validation only (needs approval gate); bare = approve, `--approved false` = deny |
| 43 | `feed get` | ☐ | |
| 44 | `social publish` | ☐ | |
| 45 | `social set-contacts` | ☐ | |
| 46 | `social event` | ☐ | |
| 47 | `social notes` | ☐ | |
| 48 | `social contacts` | ☐ | |
| 49 | `repos create` | ☐ | |
| 50 | `repos get` | ☐ | |
| 51 | `repos list` | ☐ | |
| 52 | `repos protect list` | ☐ | Empty/populated rules; unknown rules visible; malformed rule reported in validation_error |
| 53 | `repos protect set` | ☐ | Create and replace complete exact-ref rule; verify metadata is preserved |
| 54 | `repos protect remove` | ☐ | Remove exact ref; missing rule → NotFound |
| 55 | `upload file` | ☐ | |
| 56 | `pack validate` | ☐ | Local, no relay |
| 57 | `pack inspect` | ☐ | Local, no relay |
| 58 | `notes set` | ☐ | First publish, edit/carry, --clear-tags, ambiguity, empty-stdin guard |
| 59 | `notes get` | ☐ | By name, by naddr, --content-only, cross-author, ambiguous → exit 1 |
| 60 | `notes ls` | ☐ | Own, --author all, --tag, --limit |
| 61 | `notes rm` | ☐ | Delete→get 404, double-delete idempotent, missing slug → NotFound |
| 62 | `users set-status` | ☐ | Text+emoji, text only, emoji-only (`--text ""`), `--clear`, `--clear` + `--text` → exit 1 |
| 25 | `canvas notify` | ☐ | Data and presentation with Desktop running |
| 26 | `reactions add` | ☐ | |
| 27 | `reactions remove` | ☐ | |
| 28 | `reactions get` | ☐ | |
| 29 | `dms list` | ☐ | |
| 30 | `dms open` | ☐ | |
| 31 | `dms add-member` | ☐ | Needs messages:write |
| 32 | `users get` | ☐ | Self, single, batch |
| 33 | `users set-profile` | ☐ | |
| 34 | `users presence` | ☐ | |
| 35 | `users set-presence` | ☐ | online, away, offline |
| 36 | `workflows list` | ☐ | |
| 37 | `workflows create` | ☐ | |
| 38 | `workflows update` | ☐ | |
| 39 | `workflows delete` | ☐ | |
| 40 | `workflows trigger` | ☐ | |
| 41 | `workflows runs` | ☐ | |
| 42 | `workflows get` | ☐ | |
| 43 | `workflows approve` | ☐ | Validation only (needs approval gate); bare = approve, `--approved false` = deny |
| 44 | `feed get` | ☐ | |
| 45 | `social publish` | ☐ | |
| 46 | `social set-contacts` | ☐ | |
| 47 | `social event` | ☐ | |
| 48 | `social notes` | ☐ | |
| 49 | `social contacts` | ☐ | |
| 50 | `repos create` | ☐ | |
| 51 | `repos get` | ☐ | |
| 52 | `repos list` | ☐ | |
| 53 | `repos protect list` | ☐ | Empty/populated rules; unknown rules visible; malformed rule reported in validation_error |
| 54 | `repos protect set` | ☐ | Create and replace complete exact-ref rule; verify metadata is preserved |
| 55 | `repos protect remove` | ☐ | Remove exact ref; missing rule → NotFound |
| 56 | `upload file` | ☐ | |
| 57 | `pack validate` | ☐ | Local, no relay |
| 58 | `pack inspect` | ☐ | Local, no relay |
| 59 | `notes set` | ☐ | First publish, edit/carry, --clear-tags, ambiguity, empty-stdin guard |
| 60 | `notes get` | ☐ | By name, by naddr, --content-only, cross-author, ambiguous → exit 1 |
| 61 | `notes ls` | ☐ | Own, --author all, --tag, --limit |
| 62 | `notes rm` | ☐ | Delete→get 404, double-delete idempotent, missing slug → NotFound |
| 63 | `users set-status` | ☐ | Text+emoji, text only, emoji-only (`--text ""`), `--clear`, `--clear` + `--text` → exit 1 |
3 changes: 3 additions & 0 deletions crates/buzz-cli/src/commands/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,9 @@ pub async fn dispatch_canvas(cmd: crate::CanvasCmd, client: &BuzzClient) -> Resu
match cmd {
CanvasCmd::Get { channel } => cmd_get_canvas(client, &channel).await,
CanvasCmd::Set { channel, content } => cmd_set_canvas(client, &channel, &content).await,
CanvasCmd::Notify { .. } => {
unreachable!("local Canvas notifications are handled before relay setup")
}
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/buzz-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod notes;
pub mod pack;
pub mod patches;
pub mod pr;
pub mod project_canvas;
pub mod project_channel;
pub mod projects;
pub mod reactions;
Expand Down
Loading
Loading