OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / cli / commands / notebook.js

156 lines · javascript ref:
/**
 * hn notebook — Notebook management commands
 */

import { HnClient } from '../client.js'
import { table, log, error, jsonOut } from '../output.js'

export async function notebookCommand (subcommand, cmdArgs, flags) {
  const client = new HnClient(flags)

  switch (subcommand) {
    case 'list':
    case undefined:
      return await listNotebooks(client, flags)
    case 'create':
      return await createNotebook(client, cmdArgs, flags)
    case 'info':
      return await infoNotebook(client, cmdArgs, flags)
    case 'rename':
      return await renameNotebook(client, cmdArgs, flags)
    case 'delete':
      return await deleteNotebook(client, cmdArgs, flags)
    default:
      error(`Unknown notebook subcommand: ${subcommand}\nUsage: hn notebook [list|create|info|rename|delete]`)
  }
}

async function listNotebooks (client, flags) {
  const notebooks = await client.get('/api/notebooks')

  if (flags.json) {
    jsonOut(notebooks)
    return
  }

  if (notebooks.length === 0) {
    log('No notebooks found.')
    return
  }

  table(notebooks, [
    { key: 'slug', label: 'SLUG', width: 20 },
    { key: 'name', label: 'NAME', width: 30 },
    { key: 'noteCount', label: 'NOTES', width: 6 },
    { key: 'description', label: 'DESCRIPTION', width: 40 }
  ])
}

async function createNotebook (client, cmdArgs, flags) {
  const slug = cmdArgs[0]
  if (!slug) {
    error('Usage: hn notebook create <slug> [--name "..."] [--description "..."] [--encrypted --password "..."]')
  }

  const body = { slug }
  if (flags.name) body.name = flags.name
  if (flags.description) body.description = flags.description

  if (flags.encrypted) {
    body.encrypted = true
    if (flags.password) {
      body.password = flags.password
    } else {
      // Prompt for password
      let securePrompt
      try {
        securePrompt = (await import('secure-prompt')).default
      } catch {
        error('secure-prompt not available for encryption. Use --password flag or install secure-prompt')
      }
      const password = await securePrompt('Encryption password: ')
      if (!password || password.length === 0) {
        error('Password cannot be empty')
      }
      const confirm = await securePrompt('Confirm password: ')
      if (password.toString() !== confirm.toString()) {
        error('Passwords do not match')
      }
      body.password = password.toString()
    }
  }

  const notebook = await client.post('/api/notebooks', body)

  if (flags.json) {
    jsonOut(notebook)
    return
  }

  const encLabel = notebook.encrypted ? ' (encrypted)' : ''
  log(`Created notebook: ${notebook.slug} (${notebook.name})${encLabel}`)
}

async function infoNotebook (client, cmdArgs, flags) {
  const slug = cmdArgs[0]
  if (!slug) {
    error('Usage: hn notebook info <slug>')
  }

  const notebook = await client.get(`/api/notebooks/${encodeURIComponent(slug)}`)

  if (flags.json) {
    jsonOut(notebook)
    return
  }

  log(`Slug:        ${notebook.slug}`)
  log(`Name:        ${notebook.name}`)
  log(`Description: ${notebook.description || '(none)'}`)
  log(`Encrypted:   ${notebook.encrypted ? 'yes' : 'no'}`)
  log(`Notes:       ${notebook.noteCount}`)
  log(`Created:     ${new Date(notebook.createdAt).toLocaleString()}`)
  log(`Updated:     ${new Date(notebook.updatedAt).toLocaleString()}`)
}

async function renameNotebook (client, cmdArgs, flags) {
  const oldSlug = cmdArgs[0]
  const newSlug = cmdArgs[1]
  if (!oldSlug || !newSlug) {
    error('Usage: hn notebook rename <old-slug> <new-slug>')
  }

  const notebook = await client.post(`/api/notebooks/${encodeURIComponent(oldSlug)}/rename`, { slug: newSlug })

  if (flags.json) {
    jsonOut(notebook)
    return
  }

  log(`Renamed: ${oldSlug} → ${notebook.slug}`)
}

async function deleteNotebook (client, cmdArgs, flags) {
  const slug = cmdArgs[0]
  if (!slug) {
    error('Usage: hn notebook delete <slug> [--force]')
  }

  if (!flags.force) {
    log(`This will delete notebook '${slug}' and ALL its notes.`)
    log('Use --force to confirm.')
    return
  }

  const result = await client.del(`/api/notebooks/${encodeURIComponent(slug)}`)

  if (flags.json) {
    jsonOut(result)
    return
  }

  log(`Deleted notebook: ${slug}`)
}

export default notebookCommand