/**
* hn links/backlinks/graph — Link management commands
*/
import { HnClient } from '../client.js'
import { table, log, error, jsonOut } from '../output.js'
export async function linksCommand (cmdArgs, flags) {
const client = new HnClient(flags)
const notebook = cmdArgs[0]
const noteId = cmdArgs[1]
if (!notebook || !noteId) {
error('Usage: hn links <notebook> <noteId>')
}
const links = await client.get(`/api/notebooks/${enc(notebook)}/notes/${enc(noteId)}/links`)
if (flags.json) {
jsonOut(links)
return
}
if (links.length === 0) {
log('No outgoing links.')
return
}
table(links, [
{ key: 'raw', label: 'LINK', width: 30 },
{ key: 'targetNotebook', label: 'NOTEBOOK', width: 16 },
{ key: 'title', label: 'TITLE', width: 30 },
{ key: 'resolved', label: 'RESOLVED', width: 8 }
])
}
export async function backlinksCommand (cmdArgs, flags) {
const client = new HnClient(flags)
const notebook = cmdArgs[0]
const noteId = cmdArgs[1]
if (!notebook || !noteId) {
error('Usage: hn backlinks <notebook> <noteId>')
}
const backlinks = await client.get(`/api/notebooks/${enc(notebook)}/notes/${enc(noteId)}/backlinks`)
if (flags.json) {
jsonOut(backlinks)
return
}
if (backlinks.length === 0) {
log('No backlinks (nothing links to this note).')
return
}
table(backlinks, [
{ key: 'sourceNotebook', label: 'NOTEBOOK', width: 16 },
{ key: 'sourceId', label: 'NOTE ID', width: 18 },
{ key: 'targetTitle', label: 'TARGET', width: 30 }
])
}
export async function graphCommand (cmdArgs, flags) {
const client = new HnClient(flags)
const notebook = cmdArgs[0]
const path = notebook
? `/api/graph?notebook=${enc(notebook)}`
: '/api/graph'
const graph = await client.get(path)
if (flags.json) {
jsonOut(graph)
return
}
log(`Nodes: ${graph.nodes.length}`)
log(`Edges: ${graph.edges.length}`)
log('')
if (graph.nodes.length === 0) {
log('No notes found.')
return
}
// Print nodes
for (const node of graph.nodes) {
const links = graph.edges.filter(e => e.source === node.id)
const backlinks = graph.edges.filter(e => e.target === node.id)
const linkInfo = links.length || backlinks.length
? ` [${links.length} out, ${backlinks.length} in]`
: ''
log(` ${node.notebook}/${node.noteId} ${node.title}${linkInfo}`)
}
if (graph.edges.length > 0) {
log('')
log('Edges:')
for (const edge of graph.edges) {
log(` ${edge.source} -> ${edge.target} (${edge.title})`)
}
}
}
export async function mergeCommand (cmdArgs, flags) {
const client = new HnClient(flags)
const notebook = cmdArgs[0]
const noteId1 = cmdArgs[1]
const noteId2 = cmdArgs[2]
if (!notebook || !noteId1 || !noteId2) {
error('Usage: hn merge <notebook> <noteId1> <noteId2> [--title "..."]')
}
const body = { noteId1, noteId2 }
if (flags.title) body.title = flags.title
const merged = await client.post(`/api/notebooks/${enc(notebook)}/merge`, body)
if (flags.json) {
jsonOut(merged)
return
}
log(`Merged into: ${merged.id} (${merged.title})`)
}
export async function splitCommand (cmdArgs, flags) {
const client = new HnClient(flags)
const notebook = cmdArgs[0]
const noteId = cmdArgs[1]
if (!notebook || !noteId) {
error('Usage: hn split <notebook> <noteId> [--delimiter "## "]')
}
const body = {}
if (flags.delimiter) body.delimiter = flags.delimiter
const result = await client.post(`/api/notebooks/${enc(notebook)}/notes/${enc(noteId)}/split`, body)
if (flags.json) {
jsonOut(result)
return
}
if (!result.split) {
log('Nothing to split (no heading boundaries found).')
return
}
log(`Split into ${result.parts.length + 1} parts:`)
log(` Original note updated (first section)`)
for (const part of result.parts) {
log(` New: ${part.id} — ${part.title}`)
}
}
function enc (s) {
return encodeURIComponent(s)
}