generated from eblume/project-template
feat: heph context <task-id> reads/edits a task's context doc by id
Editing a task's canonical-context doc body previously meant looking up its `canonical_context_id` (e.g. via `heph list --json`) and then `heph node update <doc-id> --body`. Add a `heph context <task-id>` command that resolves the canonical-context doc from the task's outgoing links and: * prints the body with no flag, * `--body <text>` replaces it (`-` reads stdin, matching `node update`), * `--append <text>` adds a blank-line-separated paragraph. Errors clearly when the id has no canonical-context doc (e.g. a plain doc node rather than a task). Purely a client-side CLI convenience — no new RPC. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fc25f6ac51
commit
babdb21c0a
2 changed files with 68 additions and 0 deletions
|
|
@ -160,6 +160,18 @@ enum Command {
|
|||
#[arg(short = 'n', long, default_value_t = 10)]
|
||||
n: usize,
|
||||
},
|
||||
/// Read or edit a task's canonical-context doc body **by task id** — no
|
||||
/// manual `canonical_context_id` lookup. With neither flag, prints the body.
|
||||
Context {
|
||||
/// Task node id.
|
||||
id: String,
|
||||
/// Replace the body with this text (`-` reads from stdin).
|
||||
#[arg(long)]
|
||||
body: Option<String>,
|
||||
/// Append this text to the body (separated by a blank line).
|
||||
#[arg(long)]
|
||||
append: Option<String>,
|
||||
},
|
||||
/// Working-set health — the §6.2 tensions (orange vs 6, active vs ~30, …).
|
||||
Health,
|
||||
/// Create a document node.
|
||||
|
|
@ -577,6 +589,35 @@ fn main() -> Result<()> {
|
|||
println!("Logged to {id}");
|
||||
}
|
||||
}
|
||||
Command::Context { id, body, append } => {
|
||||
let doc_id = canonical_context_id(&mut client, &id)?;
|
||||
match (body, append) {
|
||||
(Some(_), Some(_)) => bail!("pass only one of --body / --append"),
|
||||
(Some(body), None) => {
|
||||
let body = read_body_arg(Some(body))?;
|
||||
client.call("node.update", json!({ "id": doc_id, "body": body }))?;
|
||||
println!("Set context of {id}");
|
||||
}
|
||||
(None, Some(text)) => {
|
||||
let current = context_body(&mut client, &doc_id)?;
|
||||
let combined = if current.trim().is_empty() {
|
||||
text
|
||||
} else {
|
||||
format!("{}\n\n{text}", current.trim_end())
|
||||
};
|
||||
client.call("node.update", json!({ "id": doc_id, "body": combined }))?;
|
||||
println!("Appended to context of {id}");
|
||||
}
|
||||
(None, None) => {
|
||||
let current = context_body(&mut client, &doc_id)?;
|
||||
if current.trim().is_empty() {
|
||||
println!("(empty context)");
|
||||
} else {
|
||||
println!("{}", current.trim_end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Health => {
|
||||
let h = client.call("health", json!({}))?;
|
||||
println!(
|
||||
|
|
@ -800,6 +841,32 @@ fn resolve_project(client: &mut Client, name: Option<&str>) -> Result<Option<Str
|
|||
Ok(Some(node.id))
|
||||
}
|
||||
|
||||
/// Resolve a task's canonical-context doc id by walking its outgoing links.
|
||||
/// Errors if the node has no such doc (e.g. it isn't a task).
|
||||
fn canonical_context_id(client: &mut Client, task_id: &str) -> Result<String> {
|
||||
let links = client.call("links.outgoing", json!({ "id": task_id }))?;
|
||||
let dst = links
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|l| l.get("link_type").and_then(Value::as_str) == Some("canonical-context"))
|
||||
.and_then(|l| l.get("dst_id").and_then(Value::as_str));
|
||||
match dst {
|
||||
Some(id) => Ok(id.to_string()),
|
||||
None => bail!("{task_id} has no canonical-context doc (is it a task?)"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a context doc's current body (empty string when unset).
|
||||
fn context_body(client: &mut Client, doc_id: &str) -> Result<String> {
|
||||
let node = client.call("node.get", json!({ "id": doc_id }))?;
|
||||
Ok(node
|
||||
.get("body")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_string())
|
||||
}
|
||||
|
||||
/// `--body -` reads the body from stdin; otherwise pass it through.
|
||||
fn read_body_arg(body: Option<String>) -> Result<Option<String>> {
|
||||
match body.as_deref() {
|
||||
|
|
|
|||
1
docs/changelog.d/heph-context-command.feature.md
Normal file
1
docs/changelog.d/heph-context-command.feature.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
New `heph context <task-id>` command reads or edits a task's canonical-context doc body **by task id**, with no manual `canonical_context_id` lookup. With no flag it prints the body; `--body <text>` replaces it (`-` reads stdin, like `node update`); `--append <text>` adds a blank-line-separated paragraph. Errors clearly on a node that has no canonical-context doc (e.g. a plain doc, not a task).
|
||||
Loading…
Add table
Add a link
Reference in a new issue