15 KiB
Logseq CLI Tag and Property Management Implementation Plan
Goal: Add first class CLI support for upsert tag, upsert property, remove tag, and remove property, and restructure existing remove behavior into remove block and remove page.
Architecture: Replace current add tag command entry with upsert tag so tag creation and idempotent update semantics are unified under one verb.
Architecture: Expand remove into typed subcommands (block, page, tag, property) to make deletion intent explicit and prevent mixed selector ambiguity.
Architecture: Reuse :thread-api/apply-outliner-ops in db-worker-node so no new HTTP route or transport protocol is introduced.
Tech Stack: ClojureScript, babashka.cli, Promesa, Datascript, db-worker-node, outliner ops.
Related: Builds on docs/agent-guide/042-logseq-cli-add-tag-command.md and docs/agent-guide/029-logseq-cli-show-properties.md.
Problem statement
Current CLI supports tag creation through add tag in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljs, but does not expose an upsert verb for tags and properties.
Current update in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/update.cljs updates tag and property values on blocks, but does not upsert or remove tag/property entities.
Current remove in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/remove.cljs mixes block and page deletion under one command, and has no explicit tag/property deletion path.
db-worker-node already supports required mutation primitives through :thread-api/apply-outliner-ops in /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs and outliner ops in /Users/rcmerci/gh-repos/logseq/deps/outliner/src/logseq/outliner/op.cljs.
The implementation gap is CLI command surface design, parser wiring, validation, and output contract coverage.
Testing Plan
I will follow @test-driven-development and write failing parser, action, executor, formatter, and integration tests before adding behavior.
I will add parser tests for the new upsert verb and the new typed remove subcommands.
I will add action builder tests that verify repo propagation, normalized names, schema coercion, and typed action payloads for each new command.
I will add executor tests that stub transport and assert exact outliner ops emitted for each command path.
I will add formatter tests for human and json output so command responses are stable for scripts.
I will add integration tests against db-worker-node that verify graph state changes through list, show, and entity queries.
I will use @clojure-debug only when failures are caused by test setup rather than behavior.
NOTE: I will write all tests before I add any implementation behavior.
Command contract
The final command surface is top level verbs without a manage group.
| Command | Required options | Optional options | Behavior |
|---|---|---|---|
logseq upsert tag |
--name |
none in v1 | Creates tag when missing, returns existing tag when already present, and errors if same title exists as non-tag page. |
logseq upsert property |
--name |
--type, --cardinality, --hide, --public |
Creates property when missing, updates schema for existing property, and validates type or cardinality compatibility. |
logseq remove tag |
one of --name, --id |
none | Deletes a tag entity after validating target type and removability, and fails with a candidate list when --name matches multiple tags. |
logseq remove property |
one of --name, --id |
none | Deletes a property entity after validating target type and removability, and fails with a candidate list when --name matches multiple properties. |
logseq remove block |
one of --id, --uuid |
none | Deletes one or more blocks with existing block remove behavior. |
logseq remove page |
--name |
none | Deletes a page with existing page remove behavior. |
add tag will be removed from /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljs and replaced by upsert tag.
Bare remove without a subcommand will be rejected with a clear parse error.
No compatibility aliases or deprecation shims will be kept for removed commands.
Integration overview
logseq upsert property --name "owner" --type node --cardinality many
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/upsert.cljs
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/commands.cljs
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/transport.cljs (/v1/invoke)
-> /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs (:thread-api/apply-outliner-ops)
-> /Users/rcmerci/gh-repos/logseq/deps/outliner/src/logseq/outliner/op.cljs (:upsert-property)
logseq remove tag --name "Quote"
-> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/remove.cljs
-> remove tag resolver validates :logseq.class/Tag
-> :thread-api/apply-outliner-ops with [:delete-page [tag-uuid]]
-> outliner page delete flow applies class cleanup and persistence
Detailed implementation plan
- Add failing parse help tests in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljsthat expect top levelupsertandremovesubcommands (block,page,tag,property). - Add failing parse tests for
['upsert' 'tag' '--name' 'Quote']and['upsert' 'property' '--name' 'owner' '--type' 'node' '--cardinality' 'many']in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing parse tests for
['remove' 'tag' '--name' 'Quote']and['remove' 'property' '--id' '123']in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing parse tests for
['remove' 'block' '--id' '1']and['remove' 'page' '--name' 'Home']to preserve old delete behavior in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing parse validation tests that reject bare
removeand oldadd tagcommand usage in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing parse validation tests for invalid property type and cardinality values in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing build action tests for
upsert tagname normalization and#prefix stripping in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing build action tests for
upsert propertyschema coercion to:logseq.property/typeand:db/cardinalityin/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing executor tests that
upsert tagemits[:create-page [name {:class? true}]]only when the tag is missing in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing executor tests that
upsert tagis idempotent for existing tag entities and rejects non-tag title conflicts in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing executor tests that
upsert propertyemits[:upsert-property [property-id schema opts]]and passes{:property-name name}when creating a new property in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing executor tests that
remove tagandremove propertyresolve entities by--nameor--idand emit[:delete-page [uuid]]in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing executor tests that
remove tag --nameandremove property --namefail on multiple matches, return all matched candidates in output, and require rerun with--idin/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing executor tests that built in or hidden tag or property targets are rejected with explicit error codes in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add failing formatter tests for
upsert tag,upsert property,remove tag, andremove propertyoutputs in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs. - Add failing formatter tests that
remove blockandremove pageoutputs remain backward compatible in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs. - Add failing integration tests for
upsert tagcreate and idempotent behavior in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs. - Add failing integration tests for
upsert propertycreate and schema update behavior in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs. - Add failing integration tests for
remove tagandremove propertyensuring entities disappear fromlist tagandlist propertyin/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs. - Add failing integration tests for
remove tag --nameandremove property --nameambiguous matches to assert candidate list output and--idguidance in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs. - Add failing integration tests for
remove blockandremove pagecommand migration behavior in/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs. - Run focused tests and confirm all new tests fail for behavior reasons before implementation.
- Create
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/upsert.cljswith command specs, entries, validation helpers, action builders, and executors for tag and property upsert. - Refactor shared tag resolver helpers from
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljsintoupsert.cljsor a shared helper namespace to avoid duplication. - Remove
add tagcommand entry and related build or execute dispatch from/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljsand/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/commands.cljs. - Extend
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/remove.cljsto registerremove block,remove page,remove tag, andremove propertyentries. - Keep existing block and page delete execution logic and map it behind
remove blockandremove pagesubcommands. - Implement new remove tag and remove property resolver and execution paths in
remove.cljsusing:delete-pageoutliner op after entity type validation and ambiguity failure behavior for--name. - Update
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/commands.cljstable, parse validation, action builder, context propagation, and execute dispatch for new upsert and remove contracts. - Update
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/format.cljsto format results forupsert tag,upsert property,remove tag, andremove property, including ambiguous candidate lists. - Run focused unit and integration tests, then run
bb dev:lint-and-test, and keep only behavior preserving refactors. - Update CLI help text snapshots and any docs references to remove
add tagand bareremoveusage.
Edge cases to cover
Tag names with leading # should normalize consistently in upsert tag.
Tag and property names that collide by title or case must fail resolution for --name, list all candidate matches, and require explicit --id.
Remove selectors must reject ambiguous name matches with a clear error, include all candidate ids and names in output, and require --id.
Built in tags and built in properties must not be removable.
Property type and cardinality updates must reject invalid transitions already enforced by outliner validation.
remove block multi id behavior must stay unchanged from current implementation.
remove page must preserve current not found and built in page deletion behavior.
Commands must reject blank names after trim.
Verification commands
| Command | Expected result |
|---|---|
bb dev:test -v logseq.cli.commands-test/test-parse-args-help |
Help output includes upsert and typed remove subcommands. |
bb dev:test -v logseq.cli.commands-test/test-verb-subcommand-parse-upsert-remove |
Parse and validation tests for new command contracts pass. |
bb dev:test -v logseq.cli.commands-test/test-build-action-upsert-remove |
Action payload tests pass for all new paths. |
bb dev:test -v logseq.cli.commands-test/test-execute-upsert-remove-tag-property |
Outliner op emission tests pass for upsert and entity removal flows. |
bb dev:test -v logseq.cli.format-test/test-human-output-upsert-remove |
Human output formatting for new commands is stable. |
bb dev:test -v logseq.cli.integration-test/test-cli-upsert-and-remove-tag-property |
End to end behavior through db-worker-node passes for all four new commands. |
bb dev:test -v logseq.cli.integration-test/test-cli-remove-block-page-subcommands |
Block and page deletion still work through new remove subcommands. |
bb dev:lint-and-test |
Full lint and unit suite pass. |
Migration and compatibility
No db-worker-node route migration is required because this feature reuses /v1/invoke and existing thread-api methods.
No schema migration is required because tag and property entities are created and deleted through existing outliner operations.
This is a CLI breaking change because add tag is removed and bare remove is replaced by typed remove block and remove page commands.
This breaking change will be applied directly with no compatibility alias period.
Testing Details
Tests validate parser, action, execution, formatter, and integration behavior for upsert tag, upsert property, remove tag, and remove property.
Tests also validate migration behavior for remove block and remove page so previous block and page deletion semantics are preserved.
Tests focus on command contracts and graph outcomes instead of helper internals.
Implementation Details
- Add new upsert command module at
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/upsert.cljs. - Register upsert entries and dispatch hooks in
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/commands.cljs. - Remove
add tagcommand entry and related dispatch from/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljsand/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/commands.cljs. - Refactor remove command entries in
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/remove.cljstoremove block,remove page,remove tag, andremove property. - Reuse
:thread-api/apply-outliner-opswith:create-page,:upsert-property, and:delete-pagefor all entity mutations. - Add formatter branches in
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/format.cljsfor new commands. - Add parser and executor unit tests in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs. - Add formatter tests in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs. - Add end to end coverage in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs. - Use
@clojure-debugonly when failures indicate fixture or async wiring issues.
Question
No open questions.