enhance(cli): Add import-edn command

Works for both current and local graphs. Addresses CLI and API for
https://discord.com/channels/725182569297215569/1365819617079066744/1365819617079066744
This commit is contained in:
Gabriel Horner
2025-11-06 16:01:35 -05:00
parent 085f9093c3
commit 0bc0892b8a
8 changed files with 75 additions and 4 deletions

View File

@@ -7,3 +7,4 @@ logseq.cli.commands.search/search
logseq.cli.commands.export/export
logseq.cli.commands.append/append
logseq.cli.commands.mcp-server/start
logseq.cli.commands.import-edn/import-edn

8
deps/cli/README.md vendored
View File

@@ -31,6 +31,8 @@ query [options] Query DB graph(s)
export [options] Export DB graph as Markdown
export-edn [options] Export DB graph as EDN
append [options] Appends text to current page
mcp-server [options] Run a MCP server
import-edn [options] Import into DB graph with EDN
help Print a command's help
$ logseq list
@@ -120,7 +122,11 @@ Exported 41 pages to yep_markdown_1756128259.zip
# Export DB graph as EDN
$ logseq export-edn woot -f woot.edn
Exported 16 properties, 16 classes and 36 pages
Exported 16 properties, 1 classes and 36 pages to woot.edn
# Import into current graph with EDN
$ logseq import-edn -f woot-ontology.edn
Imported 16 properties, 1 classes and 0 pages!
# Append text to current page
$ logseq append add this text -a my-token

View File

@@ -103,6 +103,10 @@
:description "Run a MCP server against a local graph if --graph is given or against the current in-app graph. By default the MCP server runs as a HTTP Streamable server. Use --stdio to run it as a stdio server."
:fn (lazy-load-fn 'logseq.cli.commands.mcp-server/start)
:spec cli-spec/mcp-server}
{:cmds ["import-edn"] :desc "Import into DB graph with EDN"
:description "Import with EDN into a local graph or the current in-app graph if --api-server-token is given. See https://github.com/logseq/docs/blob/master/db-version.md#edn-data-export for more about this import type."
:fn (lazy-load-fn 'logseq.cli.commands.import-edn/import-edn)
:spec cli-spec/import-edn}
{:cmds ["help"] :fn help-command :desc "Print a command's help"
:args->opts [:command] :require [:command]}
{:cmds []

View File

@@ -0,0 +1,38 @@
(ns logseq.cli.commands.import-edn
"Import edn command"
(:require ["fs" :as fs]
[clojure.edn :as edn]
[logseq.cli.util :as cli-util]
[logseq.db :as ldb]
[logseq.db.common.sqlite-cli :as sqlite-cli]
[logseq.db.sqlite.export :as sqlite-export]
[logseq.db.sqlite.util :as sqlite-util]
[promesa.core :as p]))
(defn- print-success [import-map]
(println "Imported" (count (:properties import-map)) "properties,"
(count (:classes import-map)) "classes and"
(count (:pages-and-blocks import-map)) "pages!"))
(defn- api-import [api-server-token import-map]
(-> (p/let [resp (cli-util/api-fetch api-server-token "logseq.cli.import_edn" [(sqlite-util/transit-write import-map)])]
(if (= 200 (.-status resp))
(print-success import-map)
(cli-util/api-handle-error-response resp)))
(p/catch cli-util/command-catch-handler)))
(defn- local-import [{:keys [graph]} import-map]
(if (and graph (fs/existsSync (cli-util/get-graph-path graph)))
(let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
{:keys [init-tx block-props-tx misc-tx]}
(sqlite-export/build-import import-map @conn {})
txs (vec (concat init-tx block-props-tx misc-tx))]
(ldb/transact! conn txs)
(print-success import-map))
(cli-util/error "Graph" (pr-str graph) "does not exist")))
(defn import-edn [{{:keys [api-server-token file] :as opts} :opts}]
(let [edn (edn/read-string (str (fs/readFileSync file)))]
(if api-server-token
(api-import api-server-token edn)
(local-import opts edn))))

View File

@@ -25,6 +25,15 @@
:desc "Export type"
:default :graph}})
(def import-edn
{:api-server-token {:alias :a
:desc "API server token to query current graph"}
:graph {:alias :g
:desc "Local graph to import into"}
:file {:alias :f
:require true
:desc "EDN File to import"}})
(def query
{:graphs {:alias :g
:coerce []

View File

@@ -12,7 +12,8 @@
[electron.utils :as utils]
[electron.window :as window]
[logseq.cli.common.mcp.server :as cli-common-mcp-server]
[promesa.core :as p]))
[promesa.core :as p]
[logseq.db.sqlite.util :as sqlite-util]))
(defonce ^:private *win (atom nil))
(defonce ^:private *server (atom nil))

View File

@@ -212,6 +212,7 @@
(def ^:export list_pages cli-based-api/list-pages)
(def ^:export get_page_data cli-based-api/get-page-data)
(def ^:export upsert_nodes cli-based-api/upsert-nodes)
(def ^:export import_edn cli-based-api/import-edn)
;; file based graph APIs
(def ^:export get_current_graph_templates file-based-api/get_current_graph_templates)

View File

@@ -5,7 +5,8 @@
[frontend.modules.outliner.ui :as ui-outliner-tx]
[frontend.state :as state]
[logseq.cli.common.mcp.tools :as cli-common-mcp-tools]
[promesa.core :as p]))
[promesa.core :as p]
[logseq.db.sqlite.util :as sqlite-util]))
(defn list-tags
[options]
@@ -48,4 +49,14 @@
(outliner-op/batch-import-edn! edn-data {})))]
(when error (throw (ex-info error {})))
(ui-handler/re-render-root!)
(cli-common-mcp-tools/summarize-upsert-operations ops options)))
(cli-common-mcp-tools/summarize-upsert-operations ops options)))
(defn import-edn
"Given EDN data as a transitized string, converts to EDN and imports it."
[edn-data*]
(p/let [edn-data (sqlite-util/transit-read edn-data*)
{:keys [error]} (ui-outliner-tx/transact!
{:outliner-op :batch-import-edn}
(outliner-op/batch-import-edn! edn-data {}))]
(when error (throw (ex-info error {})))
(ui-handler/re-render-root!)))