Skip to content

feat: add SiYuan CLI (cli-anything-siyuan)#311

Merged
yuh-yang merged 32 commits into
HKUDS:mainfrom
hito0512:feat/add-siyuan-cli
Jun 11, 2026
Merged

feat: add SiYuan CLI (cli-anything-siyuan)#311
yuh-yang merged 32 commits into
HKUDS:mainfrom
hito0512:feat/add-siyuan-cli

Conversation

@hito0512

@hito0512 hito0512 commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Add SiYuan (思源笔记) CLI tool — cli-anything-siyuan — for managing notebooks, documents, blocks, and searching content via the SiYuan HTTP API.

Key features:

  • Notebook management: list, create, rename, remove notebooks
  • Document operations: list, tree, get, rename documents
  • Content block management: get, insert, update, delete blocks
  • Search & query: full-text search across all blocks, SQL queries against the SiYuan database
  • Export: export documents as Markdown
  • REPL mode: interactive shell with tab-completion
  • JSON output: --json flag on all commands for machine/agent consumption

Test Status

33 tests passing (12 unit + 15 CLI command tests + 6 optional E2E tests against a live instance)

Installation

pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=siyuan/agent-harness

Documentation

  • skills/cli-anything-siyuan/SKILL.md — AI agent integration documentation
  • siyuan/agent-harness/SIYUAN.md — CLI reference and usage guide

Registry

Registered in both registry.json and public_registry.json for CLI-Hub discovery.

Add SiYuan (思源笔记) CLI tool for managing notebooks, documents,
blocks, and searching content via SiYuan HTTP API.

- Full CLI with notebook/doc/block/search/tag/export commands
- REPL mode with tab-completion
- JSON output mode for agent consumption
- 33 passing tests (18 unit + 15 CLI command tests)
- Skill documentation for AI agent integration
- Registry entries for CLI-Hub discovery

Install: pip install git+https://github.com/HKUDS/CLI-Anything.git#subdirectory=siyuan/agent-harness
@github-actions github-actions Bot added new-cli Adds a new CLI or generated harness cli-anything-skill Changes CLI-Anything plugin or skill files cli-anything-hub Changes CLI-Hub, registries, or hub docs labels May 26, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f015c4e8a8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return
sub = parts[1]
if sub == "insert" and len(parts) >= 4:
result = client.insert_block("markdown", parts[3], previous_id=parts[2] if len(parts) > 2 else "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass parent ID to block insert in REPL

Update the REPL block insert path to pass the second argument as parent_id, not previous_id. The help text and command shape (block insert <parent> <data>) indicate users provide a parent block, but this call currently inserts relative to previousID, which places content in the wrong position (or fails when only a parent context is known).

Useful? React with 👍 / 👎.

Comment on lines +247 to +248
[[d.get("id", ""), d.get("name", ""), d.get("type", "")]
for d in docs])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize REPL doc list responses before rendering

The REPL doc list branch iterates docs as if it were a list of document objects, but SiYuan responses are often wrapped as a dict (e.g. with a files array, which the one-shot doc list command already handles). When that dict shape is returned, iteration yields keys (strings), so d.get(...) raises and the REPL command fails instead of listing documents.

Useful? React with 👍 / 👎.

Comment on lines +342 to +343
skin.table(["ID", "Content"],
[[r.get("id", ""), r.get("content", "")[:80]] for r in results])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize REPL search responses before table output

The REPL search handler assumes search_blocks returns a list, but SiYuan commonly returns a dict containing a blocks array (which the one-shot search command already normalizes). If the dict form is returned, iterating results yields string keys and r.get(...) crashes, so search in REPL breaks for real API responses.

Useful? React with 👍 / 👎.

state = session.state

info = {
"connected": state.connected,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report live connectivity in status output

Use a live connectivity value in status instead of session.state.connected. That session field is initialized to False and is never updated anywhere in this commit, so status reports connected: false even after successfully calling get_version(), which makes machine-readable status output inaccurate.

Useful? React with 👍 / 👎.

- REPL search: handle dict response (fixes KeyError when API returns {blocks: [...]})
- REPL doc list/tree: normalize dict responses before table output
- REPL block insert: use parent_id instead of previous_id
- status command: use live ping result instead of stale session state
@hito0512

Copy link
Copy Markdown
Contributor Author

@codex review

- setup.py referenced tests/TEST.md which doesn't exist, causing
  package build warnings
- SKILL.md test count was incorrect (18 unit → 12 unit)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a651bf39d0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


def _handle_repl_command(skin: Any, cmd: str) -> None:
"""Parse and execute a REPL command."""
parts = cmd.strip().split()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Parse REPL input with shell-style tokenization

The REPL parser uses cmd.strip().split(), which loses quoting and breaks multi-word arguments. As a result, commands that rely on positional tokens (for example block insert, block update, and doc create --md) only receive a fragment of the intended payload or include literal quote characters, so markdown with spaces is inserted/updated incorrectly. This is a functional data-loss bug for normal note content entered in REPL.

Useful? React with 👍 / 👎.

url = f"{self.config.base_url}{endpoint}"
try:
resp = self._session.post(url, json=data or {}, timeout=30)
except requests.ConnectionError as e:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Catch request timeouts and transport errors uniformly

_post only converts requests.ConnectionError into SiYuanClientError, but other common transport failures (for example timeouts/read errors/SSL errors) bubble up as raw exceptions. In those cases callers that handle SiYuanClientError won't catch the failure, so users get unhandled errors instead of the intended CLI error message path.

Useful? React with 👍 / 👎.

- REPL: replace str.split() with shlex.split() to handle quoted
  multi-word arguments (fixes block insert/update with spaces)
- Client: catch Timeout, SSL, and generic RequestException in
  addition to ConnectionError for better error messages

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9627bc6a4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +117 to +118
def create_notebook(self, name: str) -> dict[str, Any]:
return self._post("/api/notebook/createNotebook", {"name": name})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize createNotebook payload before returning

create_notebook() currently returns the raw data object from /api/notebook/createNotebook, but SiYuan’s API shape is {"notebook": {...}} for that endpoint. Downstream code paths (notebook create and REPL notebook creation) read nb["id"]/nb["name"] directly, so real API responses trigger a KeyError instead of creating and reporting the notebook.

Useful? React with 👍 / 👎.

Comment on lines +527 to +529
def block_insert(ctx: SiYuanContext, data: str, previous: str, parent: str, data_type: str):
"""Insert a block."""
result = ctx.client.insert_block(data_type, data, parent_id=parent, previous_id=previous)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require an anchor for block insert

This command allows block insert <data> with both --parent and --previous empty, then calls insertBlock without any position anchor. The SiYuan API requires at least one of nextID, previousID, or parentID for /api/block/insertBlock, so the default invocation path fails at runtime unless users discover and pass extra flags.

Useful? React with 👍 / 👎.

hito0512 and others added 2 commits May 26, 2026 10:58
- create_notebook: unwrap {notebook: {...}} response so callers
  can access nb["id"] and nb["name"] directly
- block insert: require --parent or --previous, error if both empty
  (SiYuan API requires at least one position anchor)
Add --next option for insertBlock anchor support.
Validate that at least one anchor (--parent/--previous/--next) is provided.
Add REPL usage error for missing anchor.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a06d2046e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +157 to +158
if user_input in ("quit", "exit", "q"):
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist REPL session state before exit

The REPL loop breaks immediately on quit/exit/q without flushing the session, but multiple REPL commands call get_session().update(...) and only mark the state dirty. Because SessionManager persists changes only on flush()/save(), notebook/document context changes made during a REPL session are lost after exiting and are not available in the next run.

Useful? React with 👍 / 👎.

config_file = Path.home() / ".siyuan-cli.json"

if config_file.is_file():
data = json.loads(config_file.read_text(encoding="utf-8"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle malformed config JSON without crashing

load_config() parses ~/.siyuan-cli.json with json.loads(...) and does not catch decode/read errors. If a user has an invalid or partially written config file, every CLI command crashes during startup instead of reporting a friendly config error or falling back to env/defaults, which effectively bricks the CLI until the file is manually fixed.

Useful? React with 👍 / 👎.

- Flush session state before REPL quit/exit/q so context is preserved
- Gracefully handle malformed config JSON (fall back to env/defaults)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@yuh-yang

Copy link
Copy Markdown
Collaborator

Thanks for the SiYuan harness. Before merge, please address the current functional review items and tighten the registry integration:

  • Fix block insert semantics in both REPL and one-shot paths. The REPL help says block insert <parent> <data>, so it should pass a parent ID, and the one-shot command should require either --parent or --previous instead of calling SiYuan without an insertion anchor.
  • Persist REPL session state before exit so selected notebook/document context survives the next run.
  • Handle malformed ~/.siyuan-cli.json with a clear CLI error instead of a startup traceback.
  • Since this is an in-repo harness, please remove the public_registry.json addition unless there is an explicit reason to list it as a public third-party CLI too.
  • After fixes, please run the focused non-backend tests and python3 .github/scripts/validate_root_skills.py.

…urce

- Remove siyuan entry from public_registry.json (in-repo harness
  should not be listed as a public CLI)
- Sync skills/cli-anything-siyuan/SKILL.md from harness source via
  sync_root_skills.py

Addresses yuh-yang review feedback.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1f81e99b3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +157 to +158
if user_input in ("quit", "exit", "q"):
ctx.session.flush()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Flush the REPL session via the context object

When a user exits the REPL with quit, exit, or q, ctx is the Click context and does not have a session attribute; the session manager is available as the local session variable or ctx.obj.session. Because this branch is outside the command handler's try, normal REPL exit raises AttributeError instead of saving dirty state and printing the goodbye message.

Useful? React with 👍 / 👎.

Comment on lines +38 to +41
try:
data = json.loads(config_file.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
data = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fall back to environment after invalid config

The current malformed-config handler avoids the JSON crash, but it sets data = {} and then immediately returns default host/port/token values from the file branch, so a user with a bad or partially written ~/.siyuan-cli.json cannot recover by setting SIYUAN_TOKEN/SIYUAN_HOST as documented. In that scenario the CLI silently drops the env credentials and continues unauthenticated/defaulted until the file is manually fixed.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c74c29723a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@click.pass_obj
def notebook_list(ctx: SiYuanContext):
"""List all notebooks."""
notebooks = ctx.client.list_notebooks()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Convert API errors before leaving one-shot commands

When a one-shot command hits a normal SiYuan API failure such as a bad token or non-zero API code, _post() raises SiYuanClientError, but this command path has no handler and SiYuanClientError is not a Click exception. I verified the one-shot path with CliRunner: notebook list exits with the raw exception and no CLI error output, so users running documented commands get a traceback instead of the intended friendly error handling that the REPL already provides.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: abcf19b5d9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

def doc_tree(ctx: SiYuanContext, notebook_id: str, path: str, depth: int):
"""List document tree."""
tree = ctx.client.list_doc_tree(notebook_id, path=path, max_depth=depth)
items = tree.get("tree", []) if isinstance(tree, dict) else tree

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read doc-tree entries from the files key

When SiYuan returns the documented /api/filetree/listDocTree shape, data is a dict containing a files array, not a tree array; I checked the filetree/listDocTree API docs/source links. In that common response path this line turns the result into [], so cli-anything-siyuan doc tree <notebook> reports an empty tree even when documents exist. The REPL branch has the same normalization pattern, so both paths should read files (and render the returned name field rather than title).

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe27d44825

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +291 to +292
def get_tags(self) -> list[dict[str, Any]]:
return self._post("/api/tag/getTag", {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Request the uncapped tag list

When a workspace has more tags than SiYuan's Conf.FileTree.MaxListCount, /api/tag/getTag stops building the response unless the request includes ignoreMaxListHint: true; this makes both tag list and --json tag list silently omit the remaining tags even though the command is documented as listing all tags. Pass that flag here before rendering the result so large/tag-heavy workspaces are complete.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a94fc1e4f0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread skills/cli-anything-siyuan/SKILL.md Outdated
cli-anything-siyuan --json notebook list

# Create a document with Markdown
cli-anything-siyuan doc create nb1 /projects/new "## Title\n\nContent"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Put Markdown content behind --md in the skill example

When agents follow this skill example to create a document with initial content, Click receives the Markdown as a third positional argument even though doc create only declares notebook_id and path plus the --md option, so the command exits with an unexpected-extra-argument error before creating anything. Use the documented shape, e.g. doc create nb1 /projects/new --md ..., so the installed skill does not teach a failing workflow.

Useful? React with 👍 / 👎.

hito0512 added 3 commits May 28, 2026 17:17
- notebook_open now looks up the notebook name via list_notebooks()
  and updates current_notebook_name to avoid stale display
- tag list recursively prints child tags with indentation
_dev fallback in _resolve_cli now uses the package path
(e.g. cli_anything.siyuan) which routes through __main__.py,
instead of directly targeting the non-executable cli module.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5a51f8e7e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +43 to +48
if data is not None:
return SiYuanConfig(
host=data.get("host", "127.0.0.1"),
port=data.get("port", 6806),
token=data.get("token", ""),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor environment overrides when config exists

When a valid ~/.siyuan-cli.json exists, this early return ignores SIYUAN_HOST, SIYUAN_PORT, and SIYUAN_TOKEN entirely. In environments where users rely on the documented env vars to override a stale local config or to supply a token that is omitted from the file, the CLI continues with the file/default credentials and fails to authenticate; read the file as defaults but apply env values before returning.

Useful? React with 👍 / 👎.

hito0512 added 2 commits May 28, 2026 17:34
Env vars (SIYUAN_HOST/PORT/TOKEN) now take precedence over
config file values, so CI/testing can override without
modifying the config file.
Markdown with backticks, parentheses, quotes gets mangled by shell
argument parsing. Now `--md -` reads from stdin, bypassing the shell:
  echo "## Title" | sy doc create nb1 /path --md -

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7a067c59c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +467 to +468
if not md or md == "-":
md = click.get_text_stream("stdin").read()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid blocking empty doc creation on stdin

When users follow the documented doc create <notebook-id> <path> form from an interactive shell and omit --md, this branch reads from stdin and waits for EOF instead of creating an empty document. SiYuan accepts an empty markdown value for this API, so only the explicit --md - case should consume stdin; otherwise the basic create command can appear hung.

Useful? React with 👍 / 👎.

PowerShell pipes text using system code page (e.g. GBK), then Python's
text-mode stdin reading may mangle CJK characters.  Reading raw bytes
via sys.stdin.buffer.read() preserves the original bytes before any
encoding conversion, then decode('utf-8-sig') handles BOM + UTF-8.

Also fixed doc_create, block_insert, block_update to use the new
_read_stdin() helper instead of click.get_text_stream('stdin').

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5124adce6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

def doc_tree(ctx: SiYuanContext, notebook_id: str, path: str, depth: int):
"""List document tree."""
tree = ctx.client.list_doc_tree(notebook_id, path=path, max_depth=depth)
items = tree.get("files", []) if isinstance(tree, dict) else tree

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support tree-shaped doc tree responses

When SiYuan returns /api/filetree/listDocTree data under the tree key, this normalization drops the entire result because it only accepts files from dict responses. I checked the SiYuan v3.4.2 kernel api/filetree.go, where listDocTree sets ret.Data = {"tree": doctree}, so cli-anything-siyuan doc tree ... will print no documents for those kernels even though the API succeeded; please accept both files and tree before falling back to an empty list.

Useful? React with 👍 / 👎.

SiYuan v3.4.2+ returns doc tree under 'tree' key instead of 'files'.
Accept either before falling back to empty list.
@yuh-yang

Copy link
Copy Markdown
Collaborator

Thanks for the fixes here. The important block insert issue is mostly addressed now: REPL insert uses parent_id, one-shot insert requires an anchor, and the public registry change is gone.

A few items still need cleanup before this can merge:

  • python3 .github/scripts/validate_root_skills.py fails because the root skill is out of sync with the harness skill. Please regenerate/sync skills/cli-anything-siyuan/SKILL.md.
  • doc create still reads stdin when --md is omitted. That makes normal empty-document creation look like it hangs in interactive use. It should only read stdin for an explicit --md -.
  • registry.json should follow the in-repo harness convention: source_url should be null, not the repo homepage.
  • Please remove the unrelated Draw.io changes from this PR so the SiYuan harness can be reviewed independently.

Focused SiYuan tests pass locally, but the root skill validation failure and unrelated diff make this not merge-ready yet.

@
fix: address review feedback — doc create stdin, registry, drawio cleanup

- doc create only reads stdin with --md - (not when --md is omitted)
- registry.json source_url set to null (in-repo harness convention)
- sync root skills/cli-anything-siyuan/SKILL.md with harness version
- revert unrelated drawio border changes

@

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 469ef1d91d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +526 to +528
for t in items:
indent = " " * t.get("depth", 0)
click.echo(f"{indent}{t.get('name', '')} ({t.get('id', '')})")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse doc-tree children before rendering

When /api/filetree/listDocTree returns the tree-shaped response, nested documents are stored under each node's children field, but this text renderer only iterates the top-level items. In any notebook with documents inside subdocuments/folders, doc tree <notebook> omits all descendants even though the command is meant to show the full document tree; flatten or recursively print children here (and in the REPL path) before rendering.

Useful? React with 👍 / 👎.

@
fix: code quality — tty guard, refactor REPL, remove globals, stdin support

- _read_stdin() checks isatty() before blocking, raises UsageError if no pipe
- Split _handle_repl_command (200 lines) into _dispatch_repl + 6 sub-handlers
- Remove module-level _config/_client/_session_mgr globals; use SiYuanContext
- REPL block insert/update/prepend/append support "-" token for stdin
- Remove unused SessionManager.auto_save decorator

@

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3aaa0aa0e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return self._post("/api/search/fullTextSearchBlock", {"query": query})

def search_tag(self, tag: str = "") -> list[dict[str, Any]]:
return self._post("/api/search/searchTag", {"tag": tag})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Send the tag query as k

When callers use this client helper, SiYuan's searchTag handler reads the query from arg["k"] and returns a dict containing tags; sending tag leaves the required key unset, so real kernels fail before searching instead of returning matching tags. Use the k payload key and unwrap the returned tags array.

Useful? React with 👍 / 👎.

Comment on lines +272 to +276
data = self._post("/api/search/findReplace", {
"keyword": keyword, "replacement": replacement,
"notebookID": notebook_id, "path": path, "maxCount": max_count,
})
return data.get("count", 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use SiYuan's findReplace payload shape

When this helper is used, SiYuan's /api/search/findReplace handler expects k, r, and an ids array (plus the usual search filters), and successful calls return null data rather than a count object. This payload sends unrelated key names and then calls .get() on the null success data, so the method cannot perform a normal find/replace call against a real kernel.

Useful? React with 👍 / 👎.

hito0512 added 4 commits June 1, 2026 19:33
@
fix: traverse doc-tree children recursively before rendering

- Add _walk_tree() helper to flatten tree nodes recursively with depth
- Use _walk_tree in both CLI doc tree and REPL doc tree handlers
- Non-JSON output now shows all nested children with proper indentation
- Update test to verify 3-level nested children all appear in output

@
@
fix: use utf-8-sig encoding when reading config file

> SiYuan writes ~/.siyuan-cli.json with a UTF-8 BOM on some platforms.
  json.loads with plain utf-8 chokes on the BOM byte, falling through to
  defaults (no token) and causing API 404s.  utf-8-sig strips the BOM
  transparently.

@
@
fix: disable proxy for SiYuan local HTTP client

> requests respects system HTTP_PROXY/HTTPS_PROXY by default, which can
  intercept localhost connections and cause API 404s when a local proxy
  (e.g. Clash) is running.  Set trust_env=False since SiYuan is always
  localhost — there is never a need to route through a proxy.

@
@
fix: match searchTag and findReplace payloads to SiYuan API shape

> searchTag reads arg["k"] not arg["tag"]; returns {"tags": [...]}
  findReplace reads k/r/ids not keyword/replacement/notebookID;
  on success ret.Data is nil (not a count), so return None

@

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22eeff4eba

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return self._post("/api/filetree/getIDsByHPath", {"notebook": notebook_id, "path": path})

def list_docs_by_path(self, notebook_id: str, path: str) -> list[dict[str, Any]]:
return self._post("/api/filetree/listDocsByPath", {"notebook": notebook_id, "path": path})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Request uncapped document listings

When a path contains more children than SiYuan's FileTree.MaxListCount, /api/filetree/listDocsByPath defaults to that cap unless maxListCount is supplied (with 0/negative disabling the cap). Because this helper sends only notebook and path, both doc list and the REPL doc list can silently render only the first capped subset while the command is described as listing the directory; pass maxListCount: 0 or expose a limit option for the list-all behavior.

Useful? React with 👍 / 👎.

})

def search_docs(self, keyword: str) -> list[dict[str, Any]]:
return self._post("/api/filetree/searchDocs", {"keyword": keyword})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Send the document search term as k

When this public helper is used against SiYuan's /api/filetree/searchDocs, the kernel reads the search string from arg["k"]; sending keyword leaves the required key absent, so real kernels fail the request instead of returning matching documents. Use the payload key k here to make search_docs() usable.

Useful? React with 👍 / 👎.

@
fix: uncapped doc listing and correct searchDocs payload key

- listDocsByPath: pass maxListCount=0 to disable SiYuan file-tree cap
- searchDocs: use key "k" instead of "keyword" to match SiYuan API

@
@yuh-yang

yuh-yang commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Thanks for continuing to iterate on this. The earlier structural items are much closer, but the latest review round still shows several real SiYuan API-shape issues that should be fixed before merge:

  • doc tree should recursively render nested children, not just top-level entries.
  • listDocsByPath should request uncapped listings, or expose a documented limit option, so doc list does not silently truncate larger directories.
  • searchDocs and tag search should use SiYuan's expected k payload key and unwrap the returned arrays correctly.
  • findReplace should use SiYuan's expected k/r/ids payload shape and handle successful null data without calling .get() on it.

Please add focused tests with mocked SiYuan responses for these API shapes. After that, please rerun the root skill validation as well.

yuh-yang commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the latest focused tests. I reran the SiYuan API-shape checks, and the earlier functional blockers now look addressed: doc tree recursion, uncapped listDocsByPath, searchDocs / tag payloads, findReplace, and doc create --md - behavior all pass focused validation.

One repo-integration blocker remains:

  • python3 .github/scripts/validate_root_skills.py still fails because skills/cli-anything-siyuan/SKILL.md is out of sync with siyuan/agent-harness/cli_anything/siyuan/skills/SKILL.md.

Please run python3 .github/scripts/sync_root_skills.py, commit the updated root skill, and rerun root skill validation. After that this should be ready for another pass.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59e9f7bc2d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +171 to +176
user_input = skin.get_input(
pt_session,
project_name=state.current_doc_path or "",
modified=False,
context=ctx_str,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle prompt interrupts before leaving REPL

When users exit the REPL with Ctrl-D or Ctrl-C, skin.get_input()/input() raises EOFError or KeyboardInterrupt here before the later quit branch and before the _dispatch_repl try, so the loop unwinds without obj.session.flush() or the goodbye path. In sessions where commands updated the current notebook/document, those dirty session changes are lost and users see an exception-style exit; catch these prompt interrupts and route them through the same flush/break path as quit.

Useful? React with 👍 / 👎.

@yuh-yang yuh-yang merged commit a71c672 into HKUDS:main Jun 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli-anything-hub Changes CLI-Hub, registries, or hub docs cli-anything-skill Changes CLI-Anything plugin or skill files new-cli Adds a new CLI or generated harness

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants