Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
- A spinner on stderr while any command waits on the network. Off under
`--json`, `-q/--quiet`, `NO_COLOR`, and whenever stderr isn't a
terminal. (#55)
- `client/vault` and `client/prefs` are importable on their own, for code
that wants the vault or the relay list without the rest of `client`.
Importing them pulls 10 modules instead of 38 -- no TUI, no viper, no
bbolt. `client` re-exports every previous name unchanged, so existing
code needs no edits. (#60)

### Changed

Expand Down
36 changes: 36 additions & 0 deletions appdir/appdir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Package appdir resolves ncli's per-user application directory.
//
// It is deliberately a leaf: stdlib only, no logging and no config stack, so
// the packages that merely need to know where ncli keeps its files -- the
// vault and prefs stores, which a library consumer may import on their own --
// don't inherit anything heavier. cli/common re-exports it for the CLI.
package appdir

import (
"os"
"path/filepath"
)

// Name is ncli's own subdirectory within the OS's per-user application
// directory.
const Name = ".ncli"

// Config returns the OS-appropriate per-user application directory for ncli --
// %AppData% on Windows, ~/Library/Application Support on macOS,
// $XDG_CONFIG_HOME (or ~/.config) on Linux -- joined with ncli's own .ncli
// subdirectory. It's the one base directory everything ncli writes outside a
// project lives under: prefs.yaml, vault.yaml, the CLI's log file, and its
// crash log. Falls back to the home directory, then the working directory, if
// the platform's config directory can't be determined (e.g. neither
// $XDG_CONFIG_HOME nor $HOME set).
func Config() string {
dir, err := os.UserConfigDir()
if err != nil {
home, homeErr := os.UserHomeDir()
if homeErr != nil {
home, _ = os.Getwd()
}
dir = home
}
return filepath.Join(dir, Name)
}
33 changes: 9 additions & 24 deletions cli/common/appdir.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
package common

import (
"os"
"path/filepath"
)
import "github.com/ohstr/ncli/appdir"

// The implementation lives in the leaf package appdir, so the vault and prefs
// stores can resolve the same directory without importing this package (and
// with it viper and zerolog). These stay as the CLI's spelling of it.

// AppDirName is ncli's own subdirectory within the OS's per-user
// application directory.
const AppDirName = ".ncli"
const AppDirName = appdir.Name

// AppConfigDir returns the OS-appropriate per-user application directory
// for ncli -- %AppData% on Windows, ~/Library/Application Support on
// macOS, $XDG_CONFIG_HOME (or ~/.config) on Linux -- joined with ncli's
// own .ncli subdirectory. It's the one base directory everything ncli
// writes outside a project lives under: prefs.yaml, the CLI's log file,
// and its crash log. Falls back to the home directory, then the working
// directory, if the platform's config directory can't be determined
// (e.g. neither $XDG_CONFIG_HOME nor $HOME set).
func AppConfigDir() string {
dir, err := os.UserConfigDir()
if err != nil {
home, homeErr := os.UserHomeDir()
if homeErr != nil {
home, _ = os.Getwd()
}
dir = home
}
return filepath.Join(dir, AppDirName)
}
// AppConfigDir returns the OS-appropriate per-user application directory for
// ncli. See appdir.Config.
func AppConfigDir() string { return appdir.Config() }
100 changes: 100 additions & 0 deletions client/compat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package client

import (
"net/url"

"github.com/ohstr/ncli/client/prefs"
"github.com/ohstr/ncli/client/vault"
)

// The vault, prefs and keypair code moved into client/vault and client/prefs
// so a consumer that only wants programmatic vault access stops inheriting
// this package's TUI, bbolt and viper closure (issue #59). Everything they
// exported is re-exported here unchanged, permanently -- these are the
// spellings the CLI and any existing consumer already use, and there is no
// deprecation planned.
//
// Types are aliases (=), not definitions, so a *client.VaultEntry and a
// *vault.Entry are the same type and callers can mix the two freely.

// --- client/vault ---

type (
// VaultEntry is vault.Entry.
VaultEntry = vault.Entry
// Identity is vault.Identity.
Identity = vault.Identity
)

// ErrLabelExists reports that a vault label is already taken. Same value as
// vault.ErrLabelExists, so errors.Is works across both spellings.
var ErrLabelExists = vault.ErrLabelExists

// GenerateIdentity mints a new keypair. See vault.GenerateIdentity.
func GenerateIdentity() (*Identity, error) { return vault.GenerateIdentity() }

// VaultPath returns the path to vault.yaml. See vault.Path.
func VaultPath() string { return vault.Path() }

// VaultExists reports whether the vault identity exists. See vault.Exists.
func VaultExists() (bool, error) { return vault.Exists() }

// CreateVaultIdentity creates the vault's own keypair. See vault.CreateIdentity.
func CreateVaultIdentity(password string) (npub, privKeyHex string, err error) {
return vault.CreateIdentity(password)
}

// UnlockVaultIdentity decrypts the vault key. See vault.Unlock.
func UnlockVaultIdentity(password string) (string, error) { return vault.Unlock(password) }

// LoadVaultEntries reads vault.yaml. See vault.LoadEntries.
func LoadVaultEntries() ([]VaultEntry, error) { return vault.LoadEntries() }

// SaveVaultEntries writes vault.yaml. See vault.SaveEntries.
func SaveVaultEntries(entries []VaultEntry) error { return vault.SaveEntries(entries) }

// AddVaultEntry saves a new identity into the vault. See vault.AddEntry.
func AddVaultEntry(vaultPrivKeyHex, label, entryPrivKeyHex string) (*VaultEntry, error) {
return vault.AddEntry(vaultPrivKeyHex, label, entryPrivKeyHex)
}

// DecryptVaultEntry reverses AddVaultEntry. See vault.DecryptEntry.
func DecryptVaultEntry(vaultPrivKeyHex string, entry VaultEntry) (string, error) {
return vault.DecryptEntry(vaultPrivKeyHex, entry)
}

// FindVaultEntry looks a vault entry up by label or key. See vault.FindEntry.
func FindVaultEntry(labelOrNpub string) (*VaultEntry, bool, error) {
return vault.FindEntry(labelOrNpub)
}

// --- client/prefs ---

type (
// Prefs is prefs.Prefs.
Prefs = prefs.Prefs
// VaultIdentityRef is prefs.VaultIdentityRef.
VaultIdentityRef = prefs.VaultIdentityRef
)

// PrefsPath returns the path to prefs.yaml. See prefs.Path.
func PrefsPath() string { return prefs.Path() }

// LoadPrefs reads prefs.yaml. See prefs.Load.
func LoadPrefs() (*Prefs, error) { return prefs.Load() }

// SavePrefs writes prefs.yaml. See prefs.Save.
func SavePrefs(p *Prefs) error { return prefs.Save(p) }

// BlossomServersFromPrefs returns the configured Blossom servers. See
// prefs.BlossomServers.
func BlossomServersFromPrefs() ([]string, error) { return prefs.BlossomServers() }

// PrefsRelayURLs returns the configured relays as URLs. See prefs.RelayURLs.
func PrefsRelayURLs() ([]*url.URL, error) { return prefs.RelayURLs() }

// ResolveRelayURL parses a relay input into its primary and fallback URLs.
// See prefs.ResolveRelayURL.
func ResolveRelayURL(raw string) (primary *url.URL, fallback *url.URL, err error) {
return prefs.ResolveRelayURL(raw)
}
36 changes: 0 additions & 36 deletions client/identity.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,19 @@
package client

import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"

btcec "github.com/flokiorg/go-flokicoin/crypto"
"github.com/ohstr/nmilat/nip05"
"github.com/ohstr/nmilat/nip19"
"github.com/ohstr/nmilat/utils"
)

const nip05Timeout = 10 * time.Second

// Identity bundles every representation of a Nostr keypair.
type Identity struct {
PrivKeyHex string
PubKeyHex string
Nsec string
Npub string
}

// GenerateIdentity mints a brand-new secp256k1 keypair and returns every
// display form of it. This is the only place a new keypair is ever created.
func GenerateIdentity() (*Identity, error) {
priv, err := btcec.NewPrivateKey()
if err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}
privHex := hex.EncodeToString(priv.Serialize())

pubHex, err := utils.GetPublicKey(privHex)
if err != nil {
return nil, fmt.Errorf("failed to derive public key: %w", err)
}

nsec, err := nip19.EncodePrivateKey(privHex)
if err != nil {
return nil, err
}
npub, err := nip19.EncodePublicKey(pubHex)
if err != nil {
return nil, err
}

return &Identity{PrivKeyHex: privHex, PubKeyHex: pubHex, Nsec: nsec, Npub: npub}, nil
}

// IdentityInspection is the read-only result of resolving an identifier
// (vault label, npub, hex pubkey, nsec, nprofile, or nip-05 address). It is
// deliberately not named Inspector/Inspect to avoid clashing with the
Expand Down
Loading
Loading