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:
callebtc
2026-05-12 22:06:44 -05:00
committed by GitHub
parent 8c36ba86c6
commit dbbee1cdbf
17 changed files with 1469 additions and 27 deletions
+35 -2
View File
@@ -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();
+46 -1
View File
@@ -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 '{}'...",