Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
392 lines
14 KiB
OCaml
392 lines
14 KiB
OCaml
type opts = { selector : string Rrbvec.t }
|
|
type parsed = Parsed_example of opts
|
|
|
|
type action = {
|
|
selector : string;
|
|
selector_path : string Rrbvec.t;
|
|
matched_commands : string Rrbvec.t;
|
|
examples : string Rrbvec.t;
|
|
message : string;
|
|
}
|
|
|
|
type entry = { path : string Rrbvec.t; examples : string Rrbvec.t }
|
|
|
|
let entry path examples =
|
|
{ path = Vec.of_array path; examples = Vec.of_array examples }
|
|
|
|
let default_entries =
|
|
Vec.of_array
|
|
[|
|
|
entry [| "graph"; "list" |] [| "logseq graph list" |];
|
|
entry [| "graph"; "create" |]
|
|
[|
|
|
"logseq graph create --graph my-graph";
|
|
"logseq graph create --graph my-graph --enable-sync";
|
|
"logseq graph create --graph my-graph --enable-sync --e2ee-password \
|
|
\"my-secret\"";
|
|
|];
|
|
entry [| "graph"; "switch" |] [| "logseq graph switch --graph my-graph" |];
|
|
entry [| "graph"; "remove" |] [| "logseq graph remove --graph my-graph" |];
|
|
entry [| "graph"; "validate" |]
|
|
[|
|
|
"logseq graph validate --graph my-graph";
|
|
"logseq graph validate --graph my-graph --fix";
|
|
|];
|
|
entry [| "graph"; "info" |] [| "logseq graph info --graph my-graph" |];
|
|
entry [| "graph"; "export" |]
|
|
[|
|
|
"logseq graph export --graph my-graph --type edn --file \
|
|
/tmp/my-graph.edn --edn-options '{:export-type :graph \
|
|
:include-timestamps? true}' --pretty-print";
|
|
"logseq graph export --graph my-graph --type sqlite --file \
|
|
/tmp/my-graph.sqlite";
|
|
|];
|
|
entry [| "graph"; "import" |]
|
|
[|
|
|
"logseq graph import --graph my-graph --type edn --input \
|
|
/tmp/my-graph.edn";
|
|
|];
|
|
entry
|
|
[| "graph"; "backup"; "list" |]
|
|
[| "logseq graph backup list --graph my-graph" |];
|
|
entry
|
|
[| "graph"; "backup"; "create" |]
|
|
[|
|
|
"logseq graph backup create --graph my-graph";
|
|
"logseq graph backup create --graph my-graph --name nightly";
|
|
|];
|
|
entry
|
|
[| "graph"; "backup"; "restore" |]
|
|
[|
|
|
"logseq graph backup restore --src my-graph-nightly --dst \
|
|
my-graph-restore";
|
|
|];
|
|
entry
|
|
[| "graph"; "backup"; "remove" |]
|
|
[| "logseq graph backup remove --src my-graph-nightly" |];
|
|
entry [| "list"; "page" |]
|
|
[|
|
|
"logseq list page --graph my-graph";
|
|
"logseq list page --graph my-graph --journal-only --limit 20";
|
|
"logseq list page --graph my-graph --limit 50 --sort updated-at \
|
|
--order desc";
|
|
|];
|
|
entry [| "list"; "tag" |]
|
|
[|
|
|
"logseq list tag --graph my-graph --with-properties";
|
|
"logseq list tag --graph my-graph --include-built-in --limit 20 \
|
|
--output json";
|
|
|];
|
|
entry [| "list"; "property" |]
|
|
[|
|
|
"logseq list property --graph my-graph --with-type";
|
|
"logseq list property --graph my-graph --include-built-in --limit 20 \
|
|
--output json";
|
|
|];
|
|
entry [| "list"; "task" |]
|
|
[|
|
|
"logseq list task --graph my-graph --status todo --priority high";
|
|
"logseq list task --graph my-graph --content \"release\" --sort \
|
|
updated-at --order desc";
|
|
|];
|
|
entry [| "list"; "node" |]
|
|
[|
|
|
"logseq list node --graph my-graph --tags project,work";
|
|
"logseq list node --graph my-graph --properties status,priority \
|
|
--sort updated-at --order desc";
|
|
|];
|
|
entry [| "list"; "asset" |]
|
|
[|
|
|
"logseq list asset --graph my-graph";
|
|
"logseq list asset --graph my-graph --limit 20 --sort updated-at \
|
|
--order desc";
|
|
|];
|
|
entry [| "upsert"; "block" |]
|
|
[|
|
|
"logseq upsert block --graph my-graph --target-page Home --content \
|
|
\"New block\"";
|
|
"logseq upsert block --graph my-graph --id 123 --content \"Updated \
|
|
content\"";
|
|
"logseq upsert block --graph my-graph --id 123 --target-page Home";
|
|
"logseq upsert block --graph my-graph --target-page Meeting Notes \
|
|
--content \"AI summary of the discussion\" --update-tags \
|
|
'[\"AI-GENERATED\"]'";
|
|
"logseq upsert block --graph my-graph --blocks '[{:block/title \
|
|
\"A\"} {:block/title \"B\"}]'";
|
|
|];
|
|
entry [| "upsert"; "page" |]
|
|
[|
|
|
"logseq upsert page --graph my-graph --page Home --update-tags \
|
|
'[\"project\"]'";
|
|
"logseq upsert page --graph my-graph --id 999 --update-properties \
|
|
'{:logseq.property/description \"Example\"}'";
|
|
|];
|
|
entry [| "upsert"; "task" |]
|
|
[|
|
|
"logseq upsert task --graph my-graph --content \"Ship release\" \
|
|
--target-page Home --status todo --priority high --scheduled \
|
|
\"2026-02-10T08:00:00.000Z\" --deadline \
|
|
\"2026-02-12T18:00:00.000Z\"";
|
|
"logseq upsert task --graph my-graph --page Weekly Plan --status \
|
|
doing";
|
|
"logseq upsert task --graph my-graph --id 123 --no-status \
|
|
--no-priority";
|
|
|];
|
|
entry [| "upsert"; "asset" |]
|
|
[|
|
|
"logseq upsert asset --graph my-graph --path ./assets/logo.png \
|
|
--target-page Home";
|
|
"logseq upsert asset --graph my-graph --id 123 --content \"Updated \
|
|
asset title\"";
|
|
|];
|
|
entry [| "upsert"; "tag" |]
|
|
[|
|
|
"logseq upsert tag --graph my-graph --name project";
|
|
"logseq upsert tag --graph my-graph --id 200 --name Project Renamed";
|
|
"logseq upsert tag --graph my-graph --name project --add-properties \
|
|
'[\"status\" \"owner\"]'";
|
|
|];
|
|
entry [| "upsert"; "property" |]
|
|
[|
|
|
"logseq upsert property --graph my-graph --name status --type \
|
|
default --cardinality one";
|
|
"logseq upsert property --graph my-graph --id 321 --hide true";
|
|
|];
|
|
entry [| "remove"; "block" |]
|
|
[|
|
|
"logseq remove block --graph my-graph --id 123";
|
|
"logseq remove block --graph my-graph --id '[123,456]'";
|
|
"logseq remove block --graph my-graph --uuid \
|
|
7f0f4bb3-2e48-4b46-ae0f-18f52ef0f8be";
|
|
|];
|
|
entry [| "remove"; "page" |]
|
|
[|
|
|
"logseq remove page --graph my-graph --page Home";
|
|
"logseq remove page --graph my-graph --id 123";
|
|
|];
|
|
entry [| "remove"; "tag" |]
|
|
[| "logseq remove tag --graph my-graph --name project" |];
|
|
entry [| "remove"; "property" |]
|
|
[|
|
|
"logseq remove property --graph my-graph --name owner";
|
|
"logseq remove property --graph my-graph --id 321";
|
|
|];
|
|
entry [| "query" |]
|
|
[|
|
|
"logseq query --graph my-graph --name recent-updated --inputs '[30]'";
|
|
"logseq query --graph my-graph --name task-search --inputs \
|
|
'[:logseq.property/status.done \"daily\"]'";
|
|
"logseq query --graph my-graph --query '[:find [?e ...] :where [?e \
|
|
:block/name]]'";
|
|
|];
|
|
entry [| "query"; "list" |]
|
|
[|
|
|
"logseq query list --graph my-graph";
|
|
"logseq query list --graph my-graph --output edn";
|
|
|];
|
|
entry [| "search"; "block" |]
|
|
[| "logseq search block --content \"task\" --graph my-graph" |];
|
|
entry [| "search"; "page" |]
|
|
[| "logseq search page --content \"home\" --graph my-graph" |];
|
|
entry [| "search"; "property" |]
|
|
[| "logseq search property --content \"owner\" --graph my-graph" |];
|
|
entry [| "search"; "tag" |]
|
|
[| "logseq search tag --content \"quote\" --graph my-graph" |];
|
|
entry [| "show" |]
|
|
[|
|
|
"logseq show --graph my-graph --page Home";
|
|
"logseq show --graph my-graph --page Foo --page-hierarchy true";
|
|
"logseq show --graph my-graph --page \"Meeting Notes\" --level 2";
|
|
"logseq show --graph my-graph --id 123 --level 3";
|
|
"logseq show --graph my-graph --id '[123,456,789]'";
|
|
"logseq show --graph my-graph --uuid \
|
|
11111111-1111-1111-1111-111111111111";
|
|
|];
|
|
entry [| "server"; "list" |] [| "logseq server list" |];
|
|
entry [| "server"; "cleanup" |] [| "logseq server cleanup" |];
|
|
entry [| "server"; "start" |] [| "logseq server start --graph my-graph" |];
|
|
entry [| "server"; "stop" |] [| "logseq server stop --graph my-graph" |];
|
|
entry [| "server"; "restart" |]
|
|
[| "logseq server restart --graph my-graph" |];
|
|
entry [| "sync"; "status" |] [| "logseq sync status --graph my-graph" |];
|
|
entry [| "sync"; "start" |]
|
|
[|
|
|
"logseq sync start --graph my-graph";
|
|
"logseq sync start --graph my-graph --e2ee-password \"my-secret\"";
|
|
|];
|
|
entry [| "sync"; "stop" |] [| "logseq sync stop --graph my-graph" |];
|
|
entry [| "sync"; "upload" |]
|
|
[|
|
|
"logseq sync upload --graph my-graph";
|
|
"logseq sync upload --graph my-graph --e2ee-password \"my-secret\"";
|
|
|];
|
|
entry [| "sync"; "download" |]
|
|
[|
|
|
"logseq sync download --graph my-graph";
|
|
"logseq sync download --graph my-graph --progress";
|
|
"logseq sync download --graph my-graph --e2ee-password \"my-secret\"";
|
|
|];
|
|
entry
|
|
[| "sync"; "asset"; "download" |]
|
|
[|
|
|
"logseq sync asset download --graph my-graph --id 123";
|
|
"logseq sync asset download --graph my-graph --uuid <asset-uuid>";
|
|
|];
|
|
entry [| "sync"; "remote-graphs" |] [| "logseq sync remote-graphs" |];
|
|
entry
|
|
[| "sync"; "ensure-keys" |]
|
|
[|
|
|
"logseq sync ensure-keys";
|
|
"logseq sync ensure-keys --e2ee-password \"my-secret\" --upload-keys";
|
|
|];
|
|
entry
|
|
[| "sync"; "grant-access" |]
|
|
[|
|
|
"logseq sync grant-access --graph my-graph --graph-id \
|
|
8b6ecdd0-1fab-4a9f-b3fb-3069c5f76e95 --email teammate@example.com";
|
|
|];
|
|
entry
|
|
[| "sync"; "config"; "set" |]
|
|
[| "logseq sync config set sync-enabled true" |];
|
|
entry
|
|
[| "sync"; "config"; "get" |]
|
|
[| "logseq sync config get sync-enabled" |];
|
|
entry
|
|
[| "sync"; "config"; "unset" |]
|
|
[| "logseq sync config unset sync-enabled" |];
|
|
|]
|
|
|
|
let example_groups =
|
|
Vec.of_array
|
|
[|
|
|
"graph";
|
|
"list";
|
|
"upsert";
|
|
"remove";
|
|
"query";
|
|
"search";
|
|
"show";
|
|
"server";
|
|
"sync";
|
|
|]
|
|
|
|
let label path = Vec.string_concat " " path
|
|
let command_id _ = Command_id.Example
|
|
let validate_parsed _ = Ok ()
|
|
|
|
let registry_entries registry =
|
|
registry.Command_registry.commands
|
|
|> Vec.filter_map (fun (meta : Command_registry.command_meta) ->
|
|
match Vec.nth_opt meta.path 0 with
|
|
| Some group when Vec.mem group example_groups ->
|
|
Some { path = meta.path; examples = meta.examples }
|
|
| _ -> None)
|
|
|
|
let entries_for registry =
|
|
let entries = registry_entries registry in
|
|
if Vec.is_empty entries then default_entries else entries
|
|
|
|
let matching_entries entries selector =
|
|
if Vec.is_empty selector then
|
|
Vec.filter (fun entry -> not (Vec.is_empty entry.examples)) entries
|
|
else if Vec.length selector = 1 then
|
|
let group = Vec.nth selector 0 in
|
|
Vec.filter
|
|
(fun entry ->
|
|
match Vec.nth_opt entry.path 0 with
|
|
| Some first -> first = group
|
|
| None -> false)
|
|
entries
|
|
else
|
|
Vec.filter (fun entry -> Vec.equal String.equal entry.path selector) entries
|
|
|
|
let resolve_selector registry selector =
|
|
match selector with
|
|
| _ ->
|
|
let entries = entries_for registry in
|
|
let matches = matching_entries entries selector in
|
|
let selector_label =
|
|
if Vec.is_empty selector then "all" else label selector
|
|
in
|
|
if Vec.is_empty matches then
|
|
Error
|
|
(Error.unknown_command
|
|
("unknown example selector: " ^ selector_label))
|
|
else
|
|
let missing =
|
|
matches
|
|
|> Vec.filter (fun entry -> Vec.is_empty entry.examples)
|
|
|> Vec.map (fun entry -> label entry.path)
|
|
in
|
|
if not (Vec.is_empty missing) then
|
|
Error
|
|
(Error.make Error.Missing_examples
|
|
("missing examples metadata for: "
|
|
^ Vec.string_concat ", " missing))
|
|
else
|
|
let matched_commands =
|
|
Vec.map (fun entry -> label entry.path) matches
|
|
in
|
|
let examples = Vec.concat_map (fun entry -> entry.examples) matches in
|
|
let count = Vec.length examples in
|
|
Ok
|
|
{
|
|
selector = selector_label;
|
|
selector_path = selector;
|
|
matched_commands;
|
|
examples;
|
|
message =
|
|
"Found "
|
|
^ Humanize_types.format_count count
|
|
^ " "
|
|
^ Humanize_types.pluralize_noun count "example"
|
|
^ " for selector " ^ selector_label;
|
|
}
|
|
|
|
let build ?registry _ _ = function
|
|
| Parsed_example { selector } ->
|
|
resolve_selector
|
|
(Option.value registry ~default:Command_registry.empty)
|
|
selector
|
|
|
|
let value_of_action action =
|
|
Edn_util.map_vec
|
|
(Vec.of_array
|
|
[|
|
|
(Edn_util.keyword "selector", Edn_util.string action.selector);
|
|
( Edn_util.keyword "matched-commands",
|
|
Edn_util.vector_vec
|
|
(action.matched_commands
|
|
|> Vec.map (fun command -> Edn_util.string command)) );
|
|
( Edn_util.keyword "examples",
|
|
Edn_util.vector_vec
|
|
(action.examples
|
|
|> Vec.map (fun example -> Edn_util.string example)) );
|
|
(Edn_util.keyword "message", Edn_util.string action.message);
|
|
|])
|
|
|
|
let execute_with_mode action _config mode =
|
|
Cli_effect.pure
|
|
(Cli_result.ok ~command:Command_id.Example mode
|
|
(Raw (value_of_action action)))
|
|
|
|
let metadata () =
|
|
Vec.singleton
|
|
{
|
|
Command_registry.id = Command_id.Example;
|
|
path = Command_id.to_path Command_id.Example;
|
|
doc = "Show runnable command examples";
|
|
long_doc = None;
|
|
examples = Vec.empty;
|
|
options = Vec.empty;
|
|
category = Command_registry.Utilities;
|
|
requires_graph = Command_id.requires_graph Command_id.Example;
|
|
requires_auth = Command_id.requires_auth Command_id.Example;
|
|
write_command = Command_id.is_write Command_id.Example;
|
|
human_table_headers_order = Vec.empty;
|
|
}
|
|
|
|
let execute action config =
|
|
let (Output.Mode.Packed mode) = Output_mode.for_config config in
|
|
execute_with_mode action config mode
|