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
10 changes: 7 additions & 3 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,16 @@ pub enum Commands {
command: Option<SandboxCommands>,
},

/// Sync workspace text context with local Markdown (`./<NAME>.md` in the current directory)
/// Sync database context with local Markdown (`./<NAME>.md` in the current directory)
Context {
/// Workspace ID (defaults to first workspace from login)
#[arg(long, short = 'w', global = true)]
workspace_id: Option<String>,

/// Database ID (defaults to active database set via 'hotdata databases set')
#[arg(long, short = 'd', global = true)]
database_id: Option<String>,

#[command(subcommand)]
command: ContextCommands,
},
Expand Down Expand Up @@ -852,7 +856,7 @@ pub enum ContextCommands {
name: String,
},

/// Download context from the workspace to ./<NAME>.md
/// Download context from the database to ./<NAME>.md
Pull {
/// Context name (trailing `.md` ignored, e.g. `USER.md` → `USER`)
name: String,
Expand All @@ -866,7 +870,7 @@ pub enum ContextCommands {
dry_run: bool,
},

/// Upload ./<NAME>.md to the workspace as named context
/// Upload ./<NAME>.md to the database as named context
Push {
/// Context name (trailing `.md` ignored, e.g. `USER.md` → `USER`; reads `./USER.md`)
name: String,
Expand Down
37 changes: 19 additions & 18 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Workspace context: `/v1/context` sync with `./{NAME}.md` in the current directory.
//! Database context: `/v1/databases/{id}/context` sync with `./{NAME}.md` in the current directory.

use crate::api::ApiClient;
use crossterm::style::Stylize;
Expand Down Expand Up @@ -28,25 +28,25 @@ static RESERVED_WORDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
});

#[derive(Debug, Deserialize, Serialize)]
struct WorkspaceContextEntry {
struct DatabaseContextEntry {
name: String,
content: String,
updated_at: String,
}

#[derive(Deserialize)]
struct ListResponse {
contexts: Vec<WorkspaceContextEntry>,
contexts: Vec<DatabaseContextEntry>,
}

#[derive(Deserialize)]
struct GetResponse {
context: WorkspaceContextEntry,
context: DatabaseContextEntry,
}

#[derive(Deserialize)]
struct UpsertResponse {
context: WorkspaceContextEntry,
context: DatabaseContextEntry,
}

/// Normalizes a context name from the CLI: trims, takes the final path segment, and strips a
Expand Down Expand Up @@ -124,9 +124,10 @@ fn local_md_path(name: &str) -> PathBuf {

fn fetch_context(
api: &ApiClient,
database_id: &str,
name: &str,
) -> Result<WorkspaceContextEntry, reqwest::StatusCode> {
let path = format!("/context/{name}");
) -> Result<DatabaseContextEntry, reqwest::StatusCode> {
let path = format!("/databases/{database_id}/context/{name}");
let (status, body) = api.get_raw(&path);
if status == reqwest::StatusCode::NOT_FOUND {
return Err(status);
Expand All @@ -143,11 +144,11 @@ fn fetch_context(
Ok(parsed.context)
}

pub fn list(workspace_id: &str, prefix: Option<&str>, format: &str) {
pub fn list(workspace_id: &str, database_id: &str, prefix: Option<&str>, format: &str) {
let api = ApiClient::new(Some(workspace_id));
let body: ListResponse = api.get("/context");
let body: ListResponse = api.get(&format!("/databases/{database_id}/context"));

let mut rows: Vec<WorkspaceContextEntry> = body.contexts;
let mut rows: Vec<DatabaseContextEntry> = body.contexts;
if let Some(p) = prefix {
rows.retain(|c| c.name.starts_with(p));
}
Expand Down Expand Up @@ -176,15 +177,15 @@ pub fn list(workspace_id: &str, prefix: Option<&str>, format: &str) {
}
}

pub fn show(workspace_id: &str, name: &str) {
pub fn show(workspace_id: &str, database_id: &str, name: &str) {
let name = normalize_context_cli_name(name);
if let Err(e) = validate_context_stem(&name) {
eprintln!("error: {e}");
std::process::exit(1);
}

let api = ApiClient::new(Some(workspace_id));
match fetch_context(&api, &name) {
match fetch_context(&api, database_id, &name) {
Ok(ctx) => {
print!("{}", ctx.content);
if !ctx.content.ends_with('\n') {
Expand All @@ -194,7 +195,7 @@ pub fn show(workspace_id: &str, name: &str) {
Err(reqwest::StatusCode::NOT_FOUND) => {
eprintln!(
"{}",
format!("error: no context named '{name}' in this workspace.").red()
format!("error: no context named '{name}' in this database.").red()
);
eprintln!(
"{}",
Expand All @@ -207,7 +208,7 @@ pub fn show(workspace_id: &str, name: &str) {
}
}

pub fn pull(workspace_id: &str, name: &str, force: bool, dry_run: bool) {
pub fn pull(workspace_id: &str, database_id: &str, name: &str, force: bool, dry_run: bool) {
let name = normalize_context_cli_name(name);
if let Err(e) = validate_context_stem(&name) {
eprintln!("error: {e}");
Expand All @@ -229,12 +230,12 @@ pub fn pull(workspace_id: &str, name: &str, force: bool, dry_run: bool) {
}

let api = ApiClient::new(Some(workspace_id));
let ctx = match fetch_context(&api, &name) {
let ctx = match fetch_context(&api, database_id, &name) {
Ok(c) => c,
Err(reqwest::StatusCode::NOT_FOUND) => {
eprintln!(
"{}",
format!("error: no context named '{name}' in this workspace.").red()
format!("error: no context named '{name}' in this database.").red()
);
std::process::exit(1);
}
Expand Down Expand Up @@ -270,7 +271,7 @@ pub fn pull(workspace_id: &str, name: &str, force: bool, dry_run: bool) {
);
}

pub fn push(workspace_id: &str, name: &str, dry_run: bool) {
pub fn push(workspace_id: &str, database_id: &str, name: &str, dry_run: bool) {
let name = normalize_context_cli_name(name);
if let Err(e) = validate_context_stem(&name) {
eprintln!("error: {e}");
Expand Down Expand Up @@ -310,7 +311,7 @@ pub fn push(workspace_id: &str, name: &str, dry_run: bool) {

let api = ApiClient::new(Some(workspace_id));
let body = json!({ "name": &name, "content": content });
let resp: UpsertResponse = api.post("/context", &body);
let resp: UpsertResponse = api.post(&format!("/databases/{database_id}/context"), &body);

println!(
"{}",
Expand Down
4 changes: 2 additions & 2 deletions src/datasets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ fn create_dataset(

pub fn create_from_query(workspace_id: &str, sql: &str, description: Option<&str>, name: &str) {
let api = ApiClient::new(Some(workspace_id));
create_dataset(&api, description, name, json!({ "sql": sql }));
create_dataset(&api, description, name, json!({ "type": "sql_query", "sql": sql }));
}

pub fn create_from_saved_query(
Expand All @@ -117,7 +117,7 @@ pub fn create_from_saved_query(
name: &str,
) {
let api = ApiClient::new(Some(workspace_id));
create_dataset(&api, description, name, json!({ "saved_query_id": query_id }));
create_dataset(&api, description, name, json!({ "type": "saved_query", "saved_query_id": query_id }));
}

pub fn list(workspace_id: &str, limit: Option<u32>, offset: Option<u32>, format: &str) {
Expand Down
19 changes: 15 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,21 +939,32 @@ fn main() {
}
Commands::Context {
workspace_id,
database_id,
command,
} => {
let workspace_id = resolve_workspace(workspace_id);
let database_id = database_id
.or_else(|| config::load_current_database("default", &workspace_id))
.unwrap_or_else(|| {
eprintln!(
"error: no active database. Use 'hotdata databases set <id>' to set one, or pass --database-id."
);
std::process::exit(1);
});
match command {
ContextCommands::List { output, prefix } => {
context::list(&workspace_id, prefix.as_deref(), &output)
context::list(&workspace_id, &database_id, prefix.as_deref(), &output)
}
ContextCommands::Show { name } => {
context::show(&workspace_id, &database_id, &name)
}
ContextCommands::Show { name } => context::show(&workspace_id, &name),
ContextCommands::Pull {
name,
force,
dry_run,
} => context::pull(&workspace_id, &name, force, dry_run),
} => context::pull(&workspace_id, &database_id, &name, force, dry_run),
ContextCommands::Push { name, dry_run } => {
context::push(&workspace_id, &name, dry_run)
context::push(&workspace_id, &database_id, &name, dry_run)
}
}
}
Expand Down
Loading