cmd¶
Index¶
- Constants
- Variables
- func BuildHTTPMux(pbURL, token, graphPath string) *http.ServeMux
- func DashboardAliases() []string
- func Execute()
- func NewGroomCmd(deps *GroomDependencies) *cobra.Command
- func NewMdCmd(deps *MdDependencies) *cobra.Command
- func NewSyncCmd(deps *SyncDependencies) *cobra.Command
- func NewTaskAddCmd(deps *TaskAddDependencies) *cobra.Command
- func NewTaskCmd() *cobra.Command
- func ParseDateFromJournalFlag(journalFlag string, timeNow func() time.Time) (time.Time, error)
- func ResolveEnvWithDefault(key, fallback string) string
- func ResolvePort(cmd *cobra.Command) int
- func addJournalFlag(cmd *cobra.Command, flagVar *string)
- func addKeyFlag(cmd *cobra.Command, flagVar *string)
- func addPageFlag(cmd *cobra.Command, flagVar *string, what string)
- func addParentFlag(cmd *cobra.Command, flagVar *string, what string)
- func applyChanges(pbClient *pocketbase.Client, desired []map[string]any) error
- func authenticate(pbURL, pbUser, pbPass string) (string, error)
- func backlogUnrankedSectionTexts() []string
- func buildDesiredRecords(tasks []logseqapi.TaskJSON, ranks map[string][]lqdsync.RankInfo, tagsByUUID map[string]string, currentTime func() time.Time) []map[string]any
- func collectBacklogRefs(graph *logseq.Graph, config *backlog.Config) (map[string][]lqdsync.RankInfo, []string)
- func collectFocusRefs(graph *logseq.Graph, focusPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
- func collectPageRefs(graph *logseq.Graph, backlogPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
- func fetchGroomTasks(now, thresholdDate time.Time, limit int) (*pocketbase.Client, []map[string]any, error)
- func fetchLogseqTasks(logseqAPI logseqapi.LogseqAPI) ([]logseqapi.TaskJSON, error)
- func gracefulShutdown(srv *http.Server) error
- func groomHandleTask(graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error, task map[string]any, now time.Time, counts *groom.Counts) bool
- func groomPrintApplyError(applyErr error, counts *groom.Counts)
- func groomSyncPocketBase(pbUpdater func(recordID string, groomedAt time.Time) error, action *groom.Action, task map[string]any, now time.Time)
- func handleConfig(writer http.ResponseWriter, graphPath string)
- func handleMoveToUnranked(writer http.ResponseWriter, req *http.Request, graphPath, pbURL, token string)
- func hasRankInBacklog(rankInfos []lqdsync.RankInfo, backlogName string) bool
- func init()
- func initCollection(client *pocketbase.Client) error
- func logseqGraphName() string
- func openGroomResources() (*logseq.Graph, api.LogseqAPI, *backlog.Config, error)
- func printTaskCard(task map[string]any, index, total int, now time.Time, termWidth int)
- func processGroomTasks(tasks []map[string]any, now time.Time, graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error) groom.Counts
- func readKey() (string, error)
- func resolveBacklogPage(graphPath, shortName string) string
- func runDashboard(cmd *cobra.Command, _ []string) error
- func runGroomWith(now time.Time, olderThan string, limit int)
- func runSyncPipeline(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pbClient *pocketbase.Client, currentTime func() time.Time) error
- func runSyncWith(currentTime func() time.Time, initFlag bool)
- func startHTTPServer(ctx context.Context, port int, mux http.Handler) error
- func terminalWidth() int
- func updateGroomCounts(counts *groom.Counts, actionName string)
- type GroomDependencies
- type MdDependencies
- type SyncDependencies
- type TaskAddDependencies
Constants¶
const (
defaultServePort = 8091
defaultPocketBaseURL = "http://127.0.0.1:8090"
pbReadyTimeout = 10 * time.Second
shutdownTimeout = 5 * time.Second
readHeaderTimeout = 5 * time.Second
)
const groomFetchMultiplier = 5 // fetch 5× the limit to absorb tasks filtered out by HasFutureDate
Variables¶
var backlogCmd = &cobra.Command{
Use: "backlog [partial page names]",
Short: "Aggregate tasks from multiple pages into a backlog",
Long: `The backlog command aggregates tasks from one or more pages into a unified backlog.
If partial page names are provided, only page titles that contain the provided names are processed.
Each line on the "backlog" page that includes references to other pages or tags generates a separate backlog.
The first page in the line determines the name of the backlog page.
Tasks are retrieved from all provided pages or tags.
This setup enables users to rearrange tasks using the arrow keys and manage task states (start/stop)
directly within the interface.`,
Run: func(_ *cobra.Command, args []string) {
path := os.Getenv("LOGSEQ_GRAPH_PATH")
logseqAPI := logseqapi.NewLogseqAPI(path,
os.Getenv("LOGSEQ_HOST_URL"), os.Getenv("LOGSEQ_API_TOKEN"))
graph := logseqapi.OpenGraphFromPath(path)
reader := backlog.NewPageConfigReader(graph, "backlog")
proc := backlog.NewBacklog(graph, logseqAPI, reader, time.Now)
err := proc.ProcessAll(args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
},
}
contentCmd represents the content command.
var contentCmd = &cobra.Command{
Use: "content",
Short: "Append raw Markdown content to Logseq",
Long: `Append raw Markdown content to Logseq.
Pipe your content via stdin.
For now, it will be appended at the end of the current journal page.`,
Run: func(_ *cobra.Command, _ []string) {
graph := api.OpenGraphFromPath(os.Getenv("LOGSEQ_GRAPH_PATH"))
stdin := internal.ReadFromStdin()
var targetDate time.Time
if journalFlag != "" {
parsedDate, err := time.Parse("2006-01-02", journalFlag)
if err != nil {
log.Fatalln("Invalid journal date format. Use YYYY-MM-DD:", err)
}
targetDate = parsedDate
} else {
targetDate = time.Now()
}
_, err := internal.AppendRawMarkdownToJournal(graph, targetDate, stdin)
if err != nil {
log.Fatalln(err)
}
},
}
var dashboardCmd = &cobra.Command{
Use: "dashboard",
Aliases: DashboardAliases(),
Short: "Start PocketBase and the backlog web UI",
Long: `Starts PocketBase as a managed subprocess, waits for it to be ready,
then serves the backlog dashboard at http://localhost:8091 (configurable).
Environment variables:
POCKETBASE_URL PocketBase URL (default http://127.0.0.1:8090)
POCKETBASE_USERNAME PocketBase admin email
POCKETBASE_PASSWORD PocketBase admin password
LOGSEQ_GRAPH_PATH Path to Logseq graph (required for write-back)
LQD_SERVE_PORT HTTP server port (default 8091)`,
RunE: runDashboard,
}
errGroomNoCollection is returned when the lqd_tasks collection does not exist in PocketBase.
groomStyles holds the lipgloss styles for the groom TUI.
var groomStyles = struct {
separator lipgloss.Style
header lipgloss.Style
taskName lipgloss.Style
label lipgloss.Style
value lipgloss.Style
age lipgloss.Style
actions lipgloss.Style
prompt lipgloss.Style
success lipgloss.Style
warning lipgloss.Style
errStyle lipgloss.Style
link lipgloss.Style
}{
separator: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
header: lipgloss.NewStyle().Foreground(lipgloss.Color("33")).Bold(true),
taskName: lipgloss.NewStyle().Foreground(lipgloss.Color("255")).Bold(true),
label: lipgloss.NewStyle().Foreground(lipgloss.Color("245")),
value: lipgloss.NewStyle().Foreground(lipgloss.Color("252")),
age: lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Italic(true),
actions: lipgloss.NewStyle().Foreground(lipgloss.Color("255")).Bold(true),
prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("33")).Bold(true),
success: lipgloss.NewStyle().Foreground(lipgloss.Color("82")),
warning: lipgloss.NewStyle().Foreground(lipgloss.Color("226")),
errStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Bold(true),
link: lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Underline(true),
}
mdCmd represents the md command using the default dependencies.
rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "lqd",
Short: "Logseq Doctor heals your Markdown files for Logseq",
Long: `Logseq Doctor heals your Markdown files for Logseq.
Convert flat Markdown to Logseq outline, clean up Markdown,
prevent invalid content, and more stuff to come.
"lqdpy" is the CLI tool originally written in Python; "lqd" is the Go version.
The intention is to slowly convert everything to Go.`,
}
taskCmd represents the task command using the default dependencies.
tidyUpCmd represents the tidyUp command.
var tidyUpCmd = &cobra.Command{
Use: "tidy-up file1.md [file2.md ...]",
Short: "Tidy up your Markdown files.",
Long: `Tidy up your Markdown files, checking for invalid content and fixing some of them automatically.
- Check for forbidden references to pages/tags
- Check for running tasks (DOING)
- Check for double spaces`,
Args: cobra.MinimumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
graph := api.OpenGraphFromPath(os.Getenv("LOGSEQ_GRAPH_PATH"))
exitCode := 0
for _, path := range args {
if internal.TidyUpOneFile(graph, path) != 0 {
exitCode = 1
}
}
os.Exit(exitCode)
},
}
func BuildHTTPMux¶
BuildHTTPMux creates the HTTP mux with all routes registered.
func DashboardAliases¶
DashboardAliases returns the Cobra aliases for the dashboard command. Exported so tests can verify the alias without accessing the unexported global.
func Execute¶
Execute adds all child commands to the root command and sets flags appropriately. This is called by main.main(). It only needs to happen once to the rootCmd.
func NewGroomCmd¶
NewGroomCmd creates a new groom command with the specified dependencies. If deps is nil, it uses default (production) implementations.
func NewMdCmd¶
NewMdCmd creates a new md command with the specified dependencies. If deps is nil, it uses default implementations.
func NewSyncCmd¶
NewSyncCmd creates a new sync command with the specified dependencies. If deps is nil, it uses default (production) implementations.
func NewTaskAddCmd¶
NewTaskAddCmd creates a new task add subcommand with the specified dependencies. If deps is nil, it uses default implementations.
func NewTaskCmd¶
NewTaskCmd creates the parent task command.
func ParseDateFromJournalFlag¶
ParseDateFromJournalFlag parses the journal flag and returns the target date. If journalFlag is empty, it returns the current time from timeNow. If journalFlag is not empty, it parses it as YYYY-MM-DD format. Returns an error if the date format is invalid.
func ResolveEnvWithDefault¶
ResolveEnvWithDefault returns the env var value or fallback if unset.
func ResolvePort¶
ResolvePort returns the effective port: flag > env var > default.
func addJournalFlag¶
addJournalFlag adds a --journal/-j flag to the command.
func addKeyFlag¶
addKeyFlag adds a --key/-k flag to the command.
func addPageFlag¶
addPageFlag adds a --page/-p flag to the command with customizable help text.
func addParentFlag¶
addParentFlag adds a --parent flag to the command with customizable help text.
func applyChanges¶
func authenticate¶
authenticate obtains a PocketBase token. Returns "" and nil when credentials are absent.
func backlogUnrankedSectionTexts¶
unrankedSectionTexts lists the header texts that mark the start of an unranked section on a backlog page. Any block ref that is a child of one of these headers backlogUnrankedSectionTexts returns the header texts that mark the start of an unranked section on a backlog page. Any block ref that is a child of one of these headers is assigned SectionUnranked during sync.
func buildDesiredRecords¶
func buildDesiredRecords(tasks []logseqapi.TaskJSON, ranks map[string][]lqdsync.RankInfo, tagsByUUID map[string]string, currentTime func() time.Time) []map[string]any
func collectBacklogRefs¶
func collectBacklogRefs(graph *logseq.Graph, config *backlog.Config) (map[string][]lqdsync.RankInfo, []string)
collectBacklogRefs scans the Focus page and all configured backlog pages, returning a rank map (uuid → []RankInfo) and the ordered list of backlog names. Each ref is classified as SectionRanked or SectionUnranked based on which section header it lives under on the page.
func collectFocusRefs¶
func collectFocusRefs(graph *logseq.Graph, focusPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
func collectPageRefs¶
func collectPageRefs(graph *logseq.Graph, backlogPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
func fetchGroomTasks¶
func fetchGroomTasks(now, thresholdDate time.Time, limit int) (*pocketbase.Client, []map[string]any, error)
fetchGroomTasks initialises PocketBase, checks the collection, and fetches matching tasks. Returns (nil, nil, nil) with a printed message when there are no tasks.
func fetchLogseqTasks¶
func gracefulShutdown¶
gracefulShutdown shuts the server down with a fresh timeout context. The parent context is already cancelled at this point, so a new one is needed.
func groomHandleTask¶
func groomHandleTask(graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error, task map[string]any, now time.Time, counts *groom.Counts) bool
groomHandleTask reads keypresses for a single task until a valid action is taken. Returns true if the user requested quit.
func groomPrintApplyError¶
func groomSyncPocketBase¶
func groomSyncPocketBase(pbUpdater func(recordID string, groomedAt time.Time) error, action *groom.Action, task map[string]any, now time.Time)
func handleConfig¶
handleConfig returns UI configuration derived from the server environment.
func handleMoveToUnranked¶
func handleMoveToUnranked(writer http.ResponseWriter, req *http.Request, graphPath, pbURL, token string)
handleMoveToUnranked handles POST /internal/move-to-unranked.
func hasRankInBacklog¶
hasRankInBacklog reports whether the given backlog name already has an entry in the rank slice.
func init¶
func initCollection¶
func logseqGraphName¶
logseqGraphName extracts the graph name from the graph path for deep links.
func openGroomResources¶
openGroomResources opens the Logseq graph, API, and reads the backlog config.
func printTaskCard¶
printTaskCard prints a single task card to stdout.
func processGroomTasks¶
func processGroomTasks(tasks []map[string]any, now time.Time, graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error) groom.Counts
processGroomTasks presents tasks one at a time in a plain scrolling terminal loop. Each task card is printed, the user presses a key, the result is printed, then the next task scrolls into view. No alternate screen — every action is permanently visible.
func readKey¶
readKey reads a single keypress from /dev/tty without requiring Enter.
func resolveBacklogPage¶
resolveBacklogPage maps a short backlog name (e.g. "self") to its full page title (e.g. "Backlogs/self") by reading the backlog config page from the graph. Falls back to the short name if the config cannot be read or the name is not found.
func runDashboard¶
func runGroomWith¶
runGroomWith is the testable core of runGroom.
func runSyncPipeline¶
func runSyncPipeline(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pbClient *pocketbase.Client, currentTime func() time.Time) error
func runSyncWith¶
runSyncWith is the testable core of runSync.
func startHTTPServer¶
startHTTPServer runs the server until a signal arrives or a listen error occurs.
func terminalWidth¶
terminalWidth returns the current terminal width, falling back to groomDefaultTermWidth.
func updateGroomCounts¶
updateGroomCounts increments the appropriate counter for the given action name.
type GroomDependencies¶
GroomDependencies holds all injectable dependencies for the groom command. This enables unit testing without connecting to PocketBase, Logseq, or a terminal.
type MdDependencies¶
MdDependencies holds all the dependencies for the md command.
type MdDependencies struct {
InsertFn func(*internal.InsertMarkdownOptions) error
OpenGraph func(string) *logseq.Graph
ReadStdin func() string
TimeNow func() time.Time
}
type SyncDependencies¶
SyncDependencies holds all injectable dependencies for the sync command. This enables unit testing without connecting to PocketBase or Logseq.
type TaskAddDependencies¶
TaskAddDependencies holds all the dependencies for the task add command.
type TaskAddDependencies struct {
AddTaskFn func(*logseqext.AddTaskOptions) error
OpenGraph func(string) *logseq.Graph
TimeNow func() time.Time
}
Generated by gomarkdoc