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
37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,25 +135,25 @@ Remove-Item -Recurse -Force (Join-Path $env:APPDATA "bt") -ErrorAction SilentlyC

## Commands

| Command | Description |
| ------------- | ------------------------------------------------------------------ |
| `bt init` | Initialize `.bt/` config directory and link to a project |
| `bt login` | Log in to Braintrust or refresh an OAuth login |
| `bt logout` | Remove a saved Braintrust login |
| `bt switch` | Switch org and project context |
| `bt status` | Show current org and project context |
| `bt datasets` | Manage datasets and dataset pipelines |
| `bt eval` | Run eval files (Unix only) |
| `bt sql` | Run SQL queries against Braintrust |
| `bt view` | View logs, traces, and spans |
| `bt projects` | Manage projects (list, create, view, delete) |
| `bt datasets` | Manage remote datasets (list, create, update, view, delete) |
| `bt prompts` | Manage prompts (list, view, delete) |
| `bt functions` | Manage functions (list, view, invoke, update, push, pull, delete) |
| Command | Description |
| -------------- | ------------------------------------------------------------------ |
| `bt init` | Initialize `.bt/` config directory and link to a project |
| `bt login` | Log in to Braintrust or refresh an OAuth login |
| `bt logout` | Remove a saved Braintrust login |
| `bt switch` | Switch org and project context |
| `bt status` | Show current org and project context |
| `bt datasets` | Manage datasets and dataset pipelines |
| `bt eval` | Run eval files (Unix only) |
| `bt sql` | Run SQL queries against Braintrust |
| `bt view` | View logs, traces, and spans |
| `bt projects` | Manage projects (list, create, view, delete) |
| `bt datasets` | Manage remote datasets (list, create, update, view, delete) |
| `bt prompts` | Manage prompts (list, view, update, delete) |
| `bt functions` | Manage functions (list, view, invoke, update, push, pull, delete) |
| `bt tools` | Manage tools (list, view, invoke, update, delete) |
| `bt scorers` | Manage scorers (list, create, view, invoke, update, delete) |
| `bt sync` | Synchronize project logs between Braintrust and local NDJSON files |
| `bt update` | Update bt in-place |
| `bt scorers` | Manage scorers (list, create, view, invoke, update, delete) |
| `bt sync` | Synchronize project logs between Braintrust and local NDJSON files |
| `bt update` | Update bt in-place |

## `bt scorers`

Expand Down Expand Up @@ -183,6 +183,7 @@ bt scorers update helpfulness --messages @messages.json
bt scorers update helpfulness --model gpt-5.4-nano
bt functions update my-function --description "Updated"
bt tools update my-tool --patch @tool-patch.json
bt prompts update my-prompt --messages @messages.json
```

The API replaces `prompt_data` rather than merging it, so `bt` reads the current definition and sends a materialized replacement with your changes. A concurrent edit can therefore be overwritten.
Expand Down
11 changes: 11 additions & 0 deletions src/prompts/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use urlencoding::encode;

use crate::http::ApiClient;
Expand Down Expand Up @@ -51,3 +52,13 @@ pub async fn delete_prompt(client: &ApiClient, prompt_id: &str) -> Result<()> {
let path = format!("/v1/prompt/{}", encode(prompt_id));
client.delete(&path).await
}

/// Partially update a prompt by id via `PATCH /v1/prompt/{id}`.
///
/// Top-level fields are patched, but object-valued fields such as `prompt_data`
/// are replaced wholesale. Callers updating `prompt_data` must materialize the
/// complete value before sending the request.
pub async fn patch_prompt(client: &ApiClient, prompt_id: &str, body: &Value) -> Result<Prompt> {
let path = format!("/v1/prompt/{}", encode(prompt_id));
client.patch(&path, body).await
}
28 changes: 28 additions & 0 deletions src/prompts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub(crate) use crate::project_context::ProjectContext as ResolvedContext;
mod api;
mod delete;
mod list;
mod update;
mod view;

#[derive(Debug, Clone, Args)]
Expand All @@ -16,6 +17,7 @@ Examples:
bt prompts list
bt prompts view my-prompt
bt prompts delete my-prompt
bt prompts update my-prompt --messages @messages.json
")]
pub struct PromptsArgs {
#[command(subcommand)]
Expand All @@ -28,6 +30,8 @@ enum PromptsCommands {
List,
/// View a prompt's content
View(ViewArgs),
/// Update a prompt in place (prompt configuration, metadata, or arbitrary patch)
Update(Box<update::UpdateArgs>),
/// Delete a prompt
Delete(DeleteArgs),
}
Expand Down Expand Up @@ -87,6 +91,7 @@ pub async fn run(base: BaseArgs, args: PromptsArgs) -> Result<()> {
Some(PromptsCommands::View(p)) => {
view::run(&ctx, p.slug(), base.json, p.web, base.verbose).await
}
Some(PromptsCommands::Update(p)) => update::run(&ctx, &p, base.json).await,
Some(PromptsCommands::Delete(p)) => delete::run(&ctx, p.slug(), p.force).await,
}
}
Expand All @@ -100,8 +105,16 @@ fn prompts_command_is_read_only(command: Option<&PromptsCommands>) -> bool {

#[cfg(test)]
mod tests {
use clap::Parser;

use super::*;

#[derive(Debug, Parser)]
struct PromptsArgsHarness {
#[command(flatten)]
args: PromptsArgs,
}

#[test]
fn prompts_routes_list_and_view_to_read_only_auth() {
assert!(prompts_command_is_read_only(None));
Expand All @@ -125,4 +138,19 @@ mod tests {
})
)));
}

#[test]
fn prompts_routes_update_to_validated_auth() {
let parsed = PromptsArgsHarness::try_parse_from([
"bt-prompts",
"update",
"my-prompt",
"--description",
"updated",
"--yes",
])
.expect("parse update");

assert!(!prompts_command_is_read_only(parsed.args.command.as_ref()));
}
}
Loading
Loading