add encrypted Nostr session sharing (#8922)
Signed-off-by: callebtc <93376500+callebtc@users.noreply.github.com> Signed-off-by: Douwe Osinga <douwe@squareup.com> Signed-off-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -543,6 +543,28 @@ enum SessionCommand {
|
||||
default_value = "markdown"
|
||||
)]
|
||||
format: String,
|
||||
|
||||
#[arg(
|
||||
long = "nostr",
|
||||
help = "Publish the JSON session export as an encrypted Nostr event and print a Goose share link"
|
||||
)]
|
||||
nostr: bool,
|
||||
|
||||
#[arg(
|
||||
long = "relay",
|
||||
value_name = "RELAY",
|
||||
help = "Nostr relay URL to publish to (can be specified multiple times)",
|
||||
action = clap::ArgAction::Append
|
||||
)]
|
||||
relays: Vec<String>,
|
||||
},
|
||||
#[command(about = "Import a session from JSON or an encrypted Nostr share link")]
|
||||
Import {
|
||||
#[arg(help = "Path to a JSON session export, or a goose://sessions/nostr share link")]
|
||||
input: String,
|
||||
|
||||
#[arg(long = "nostr", help = "Treat input as an encrypted Nostr share link")]
|
||||
nostr: bool,
|
||||
},
|
||||
#[command(name = "diagnostics")]
|
||||
Diagnostics {
|
||||
@@ -1227,6 +1249,8 @@ async fn handle_session_subcommand(command: SessionCommand) -> Result<()> {
|
||||
identifier,
|
||||
output,
|
||||
format,
|
||||
nostr,
|
||||
relays,
|
||||
} => {
|
||||
let session_manager = SessionManager::instance();
|
||||
let session_identifier = if let Some(id) = identifier {
|
||||
@@ -1244,8 +1268,17 @@ async fn handle_session_subcommand(command: SessionCommand) -> Result<()> {
|
||||
}
|
||||
}
|
||||
};
|
||||
crate::commands::session::handle_session_export(session_identifier, output, format)
|
||||
.await?;
|
||||
crate::commands::session::handle_session_export(
|
||||
session_identifier,
|
||||
output,
|
||||
format,
|
||||
nostr,
|
||||
relays,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
SessionCommand::Import { input, nostr } => {
|
||||
crate::commands::session::handle_session_import(input, nostr).await?;
|
||||
}
|
||||
SessionCommand::Diagnostics { identifier, output } => {
|
||||
let session_manager = SessionManager::instance();
|
||||
|
||||
@@ -3,7 +3,8 @@ use anyhow::{Context, Result};
|
||||
|
||||
use cliclack::{confirm, multiselect, select};
|
||||
use etcetera::home_dir;
|
||||
use goose::session::{generate_diagnostics, Session, SessionManager};
|
||||
use goose::config::Config;
|
||||
use goose::session::{generate_diagnostics, nostr_share, Session, SessionManager, SessionType};
|
||||
use goose::utils::safe_truncate;
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
@@ -216,6 +217,8 @@ pub async fn handle_session_export(
|
||||
session_id: String,
|
||||
output_path: Option<PathBuf>,
|
||||
format: String,
|
||||
nostr: bool,
|
||||
relays: Vec<String>,
|
||||
) -> Result<()> {
|
||||
let session_manager = SessionManager::instance();
|
||||
let session = match session_manager.get_session(&session_id, true).await {
|
||||
@@ -241,6 +244,29 @@ pub async fn handle_session_export(
|
||||
_ => return Err(anyhow::anyhow!("Unsupported format: {}", format)),
|
||||
};
|
||||
|
||||
if nostr {
|
||||
if format != "json" {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Nostr session sharing only supports --format json"
|
||||
));
|
||||
}
|
||||
if output_path.is_some() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Nostr session sharing cannot be combined with --output"
|
||||
));
|
||||
}
|
||||
|
||||
let relays = nostr_share::resolve_relays(relays, Config::global());
|
||||
let share = nostr_share::publish_session_json(&output, relays).await?;
|
||||
println!("Session published to Nostr relays:");
|
||||
for relay in &share.relays {
|
||||
println!("- {}", relay);
|
||||
}
|
||||
println!("\nShare link:");
|
||||
println!("{}", share.deeplink);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(output_path) = output_path {
|
||||
fs::write(&output_path, output).with_context(|| {
|
||||
format!("Failed to write to output file: {}", output_path.display())
|
||||
@@ -253,6 +279,25 @@ pub async fn handle_session_export(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_session_import(input: String, nostr: bool) -> Result<()> {
|
||||
let json = if nostr || input.starts_with("goose://sessions/nostr") {
|
||||
nostr_share::import_session_json_from_deeplink(&input).await?
|
||||
} else {
|
||||
fs::read_to_string(&input)
|
||||
.with_context(|| format!("Failed to read session import file: {input}"))?
|
||||
};
|
||||
|
||||
let session_manager = SessionManager::instance();
|
||||
let session = session_manager
|
||||
.import_session(&json, Some(SessionType::User))
|
||||
.await?;
|
||||
|
||||
println!("Session imported:");
|
||||
println!("{} - {}", session.id, session.name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>) -> Result<()> {
|
||||
println!(
|
||||
"Generating diagnostics bundle for session '{}'...",
|
||||
|
||||
Reference in New Issue
Block a user