mirror of
https://github.com/HKUDS/CLI-Anything.git
synced 2026-05-06 14:11:57 +08:00
fix(mubu): generalize daily folder resolution
Require an explicit daily folder reference or MUBU_DAILY_FOLDER for daily helpers, scrub personal examples from docs and generated skill content, and harden live E2E checks for environment-specific SSL failures.
This commit is contained in:
@@ -23,6 +23,7 @@ What this gives you:
|
||||
- the canonical implementation now lives inside this directory
|
||||
- the same `cli-anything-mubu` console script is exposed
|
||||
- the main CLI is Click-based with grouped command domains
|
||||
- no-argument daily helpers only work when `MUBU_DAILY_FOLDER` is configured
|
||||
- `skill_generator.py` can regenerate the packaged `skills/SKILL.md`
|
||||
|
||||
Canonical implementation now lives under:
|
||||
@@ -47,4 +48,5 @@ Current state:
|
||||
- canonical package source is now under `agent-harness/cli_anything/mubu/...`
|
||||
- root-level wrappers preserve backward compatibility during development
|
||||
- grouped `discover` / `inspect` / `mutate` / `session` commands now exist
|
||||
- daily-note helpers require an explicit folder reference unless `MUBU_DAILY_FOLDER` is set
|
||||
- the packaged `SKILL.md` is now generated from the canonical harness
|
||||
|
||||
@@ -9,6 +9,12 @@ This package lives in the CLI-Anything-aligned harness tree and exposes:
|
||||
- default REPL when no subcommand is supplied
|
||||
- REPL banner with app version, packaged skill path, and history path
|
||||
- persisted `current-doc` and `current-node` REPL context
|
||||
- grouped `discover` / `inspect` / `mutate` / `session` commands
|
||||
|
||||
Daily helpers are now explicit by default:
|
||||
|
||||
- pass a daily-folder reference to `discover daily-current`, `inspect daily-nodes`, or `session use-daily`
|
||||
- or set `MUBU_DAILY_FOLDER` if you want those helpers to work without an argument
|
||||
|
||||
Canonical source paths:
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ Builtins:
|
||||
exit, quit Leave the REPL
|
||||
use-doc <ref> Set the current document reference for this REPL session
|
||||
use-node <id> Set the current node reference for this REPL session
|
||||
use-daily Resolve and set the current daily document
|
||||
use-daily [ref] Resolve and set the current daily document
|
||||
current-doc Show the current document reference
|
||||
current-node Show the current node reference
|
||||
clear-doc Clear the current document reference
|
||||
@@ -62,13 +62,15 @@ Builtins:
|
||||
|
||||
Examples:
|
||||
recent --limit 5 --json
|
||||
discover daily-current
|
||||
discover daily-current --json
|
||||
inspect daily-nodes --query 日志流 --json
|
||||
session use-doc 'Workspace/Daily tasks/26.03.16'
|
||||
mutate create-child @doc --parent-node-id node-demo1 --text 'scratch child' --json
|
||||
discover daily-current '<daily-folder-ref>'
|
||||
discover daily-current --json '<daily-folder-ref>'
|
||||
inspect daily-nodes '<daily-folder-ref>' --query '<anchor>' --json
|
||||
session use-doc '<doc-ref>'
|
||||
mutate create-child @doc --parent-node-id <node-id> --text 'scratch child' --json
|
||||
mutate delete-node @doc --node-id @node --json
|
||||
update-text 'Workspace/Daily tasks/26.03.16' --node-id node-demo1 --text 'new text' --json
|
||||
update-text '<doc-ref>' --node-id <node-id> --text 'new text' --json
|
||||
|
||||
If you prefer no-argument daily helpers, set MUBU_DAILY_FOLDER='<daily-folder-ref>'.
|
||||
"""
|
||||
|
||||
|
||||
@@ -156,14 +158,15 @@ def append_command_history(command_line: str) -> None:
|
||||
save_session_state(session)
|
||||
|
||||
|
||||
def resolve_current_daily_doc_ref(folder_ref: str = "Daily tasks") -> str:
|
||||
def resolve_current_daily_doc_ref(folder_ref: str | None = None) -> str:
|
||||
resolved_folder_ref = mubu_probe.resolve_daily_folder_ref(folder_ref)
|
||||
metas = mubu_probe.load_document_metas(mubu_probe.DEFAULT_STORAGE_ROOT)
|
||||
folders = mubu_probe.load_folders(mubu_probe.DEFAULT_STORAGE_ROOT)
|
||||
docs, folder, ambiguous = mubu_probe.folder_documents(metas, folders, folder_ref)
|
||||
docs, folder, ambiguous = mubu_probe.folder_documents(metas, folders, resolved_folder_ref)
|
||||
if folder is None:
|
||||
if ambiguous:
|
||||
raise RuntimeError(mubu_probe.ambiguous_error_message("folder", folder_ref, ambiguous, "path"))
|
||||
raise RuntimeError(f"folder not found: {folder_ref}")
|
||||
raise RuntimeError(mubu_probe.ambiguous_error_message("folder", resolved_folder_ref, ambiguous, "path"))
|
||||
raise RuntimeError(f"folder not found: {resolved_folder_ref}")
|
||||
selected, _ = mubu_probe.choose_current_daily_document(docs)
|
||||
if selected is None or not selected.get("doc_path"):
|
||||
raise RuntimeError(f"no current daily document found in {folder['path']}")
|
||||
@@ -334,15 +337,16 @@ def handle_repl_builtin(argv: list[str], session: dict[str, object]) -> tuple[bo
|
||||
click.echo(f"Current node: {node_ref}")
|
||||
return True, 0
|
||||
if command == "use-daily":
|
||||
folder_ref = " ".join(argv[1:]) if len(argv) > 1 else "Daily tasks"
|
||||
folder_ref = " ".join(argv[1:]).strip() if len(argv) > 1 else None
|
||||
try:
|
||||
doc_ref = resolve_current_daily_doc_ref(folder_ref)
|
||||
resolved_folder_ref = mubu_probe.resolve_daily_folder_ref(folder_ref)
|
||||
doc_ref = resolve_current_daily_doc_ref(resolved_folder_ref)
|
||||
except RuntimeError as exc:
|
||||
click.echo(str(exc), err=True)
|
||||
return True, 0
|
||||
session["current_doc"] = doc_ref
|
||||
save_session_state(session)
|
||||
append_command_history(f"use-daily {folder_ref}".strip())
|
||||
append_command_history(f"use-daily {resolved_folder_ref}")
|
||||
click.echo(f"Current doc: {doc_ref}")
|
||||
return True, 0
|
||||
|
||||
@@ -408,7 +412,7 @@ def cli(ctx: click.Context, json_output: bool) -> int:
|
||||
|
||||
@cli.group(context_settings=CONTEXT_SETTINGS)
|
||||
def discover() -> None:
|
||||
"""Discovery commands for folders, documents, recency, and Daily tasks resolution."""
|
||||
"""Discovery commands for folders, documents, recency, and daily-document resolution."""
|
||||
|
||||
|
||||
@discover.command("docs", context_settings=CONTEXT_SETTINGS, add_help_option=False)
|
||||
@@ -618,12 +622,16 @@ def use_node(node_ref: tuple[str, ...]) -> int:
|
||||
@click.argument("folder_ref", nargs=-1)
|
||||
def use_daily(folder_ref: tuple[str, ...]) -> int:
|
||||
"""Resolve and persist the current daily document reference."""
|
||||
value = " ".join(folder_ref) if folder_ref else "Daily tasks"
|
||||
doc_ref = resolve_current_daily_doc_ref(value)
|
||||
raw_value = " ".join(folder_ref).strip() if folder_ref else None
|
||||
try:
|
||||
resolved_folder_ref = mubu_probe.resolve_daily_folder_ref(raw_value)
|
||||
doc_ref = resolve_current_daily_doc_ref(resolved_folder_ref)
|
||||
except RuntimeError as exc:
|
||||
raise click.ClickException(str(exc)) from exc
|
||||
session_state = load_session_state()
|
||||
session_state["current_doc"] = doc_ref
|
||||
save_session_state(session_state)
|
||||
append_command_history(f"session use-daily {value}".strip())
|
||||
append_command_history(f"session use-daily {resolved_folder_ref}")
|
||||
click.echo(f"Current doc: {doc_ref}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ pip install -e .
|
||||
- Python 3.10+
|
||||
- An active Mubu desktop session on this machine
|
||||
- Local Mubu profile data available to the CLI
|
||||
- Set `MUBU_DAILY_FOLDER` if you want no-argument daily helpers
|
||||
|
||||
## Entry Points
|
||||
|
||||
@@ -36,7 +37,7 @@ When invoked without a subcommand, the CLI enters an interactive REPL session.
|
||||
|
||||
### Discover
|
||||
|
||||
Discovery commands for folders, documents, recency, and Daily tasks resolution.
|
||||
Discovery commands for folders, documents, recency, and daily-document resolution.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
@@ -123,9 +124,9 @@ Session and state commands for current document/node context and local command h
|
||||
## Recommended Agent Workflow
|
||||
|
||||
```text
|
||||
discover daily-current --json
|
||||
discover daily-current '<daily-folder-ref>' --json
|
||||
->
|
||||
inspect daily-nodes --query '<anchor>' --json
|
||||
inspect daily-nodes '<daily-folder-ref>' --query '<anchor>' --json
|
||||
->
|
||||
session use-doc '<doc_path>'
|
||||
->
|
||||
@@ -143,6 +144,7 @@ mutate update-text / create-child / delete-node --json
|
||||
5. Prefer `--node-id` and `--parent-node-id` over text matching.
|
||||
6. `delete-node` removes the full targeted subtree.
|
||||
7. Even same-text updates can still advance document version history.
|
||||
8. Pass a daily-folder reference explicitly or set `MUBU_DAILY_FOLDER` before using no-arg daily helpers.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -161,10 +163,10 @@ cli-anything-mubu
|
||||
|
||||
### Discover Current Daily Note
|
||||
|
||||
Resolve the current daily note and emit JSON output for an agent.
|
||||
Resolve the current daily note from an explicit folder reference.
|
||||
|
||||
```bash
|
||||
cli-anything-mubu --json discover daily-current
|
||||
cli-anything-mubu --json discover daily-current '<daily-folder-ref>'
|
||||
```
|
||||
|
||||
|
||||
@@ -173,7 +175,7 @@ cli-anything-mubu --json discover daily-current
|
||||
Inspect the exact outgoing payload before a live mutation.
|
||||
|
||||
```bash
|
||||
cli-anything-mubu mutate update-text 'Workspace/Daily tasks/26.03.16' --node-id node-demo1 --text 'new text' --json
|
||||
cli-anything-mubu mutate update-text '<doc-ref>' --node-id <node-id> --text 'new text' --json
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -5,17 +5,20 @@ This file follows the CLI-Anything habit of keeping the test plan and the execut
|
||||
## Test Inventory Plan
|
||||
|
||||
- `test_mubu_probe.py`: 26 unit / light integration tests planned
|
||||
- `test_core.py`: 35 pure-logic contract tests planned
|
||||
- `test_cli_entrypoint.py`: 13 subprocess / entrypoint tests planned
|
||||
- `test_agent_harness.py`: 9 packaging / harness-layout tests planned
|
||||
- `test_live_api.py`: 6 opt-in live-session tests planned for a later phase
|
||||
- `test_full_e2e.py`: 11 local-data end-to-end tests planned
|
||||
- `test_agent_harness.py`: 11 packaging / harness-layout tests planned
|
||||
|
||||
Current status:
|
||||
|
||||
- `test_mubu_probe.py` exists and passes
|
||||
- `test_core.py` exists and passes
|
||||
- `test_cli_entrypoint.py` exists and passes
|
||||
- `test_full_e2e.py` exists and passes when local Mubu data is available
|
||||
- `test_agent_harness.py` exists and passes
|
||||
- canonical harness test modules now also exist under `agent-harness/cli_anything/mubu/tests/`
|
||||
- `test_live_api.py` is not implemented yet because live mutation tests need explicit opt-in controls
|
||||
- no separate `test_live_api.py` exists yet; local-data live coverage currently lives in `test_full_e2e.py` with skip guards and dry-run-first mutation checks
|
||||
|
||||
## Unit Test Plan
|
||||
|
||||
@@ -103,6 +106,41 @@ Expected subprocess count:
|
||||
|
||||
- 13 tests
|
||||
|
||||
### Module: `test_core.py`
|
||||
|
||||
Behaviors covered now:
|
||||
|
||||
- pure helper and transformation contracts
|
||||
- plain-text and rich-text HTML conversion
|
||||
- node id generation
|
||||
- node iteration and path conversion
|
||||
- folder index construction
|
||||
- daily-title classification
|
||||
- normalization helpers and revision parsing
|
||||
- timestamp parsing and formatting
|
||||
- default local-path discovery
|
||||
- ambiguity message formatting
|
||||
- document metadata enrichment and record deduplication
|
||||
|
||||
Expected pure-logic count:
|
||||
|
||||
- 35 tests
|
||||
|
||||
### Module: `test_full_e2e.py`
|
||||
|
||||
Behaviors covered now:
|
||||
|
||||
- live local-data discovery commands
|
||||
- current-daily resolution with `MUBU_DAILY_FOLDER`
|
||||
- live node listing from the current daily note
|
||||
- `session use-daily` persisted state
|
||||
- REPL `use-daily` plus follow-on inspection
|
||||
- dry-run `update-text`, `create-child`, and `delete-node`
|
||||
|
||||
Expected local-data E2E count:
|
||||
|
||||
- 11 tests
|
||||
|
||||
### Module: `test_agent_harness.py`
|
||||
|
||||
Behaviors covered now:
|
||||
@@ -119,7 +157,7 @@ Behaviors covered now:
|
||||
|
||||
Expected packaging count:
|
||||
|
||||
- 9 tests
|
||||
- 11 tests
|
||||
|
||||
## E2E Test Plan
|
||||
|
||||
@@ -128,7 +166,7 @@ These workflows are currently verified manually against the real local Mubu sess
|
||||
Planned live scenarios:
|
||||
|
||||
1. read recent documents from the local desktop profile
|
||||
2. resolve `Workspace/Daily tasks` and identify the current daily note
|
||||
2. resolve `<daily-folder-ref>` and identify the current daily note
|
||||
3. enumerate live nodes inside the current daily note
|
||||
4. dry-run a text update and inspect the exact outgoing payload
|
||||
5. execute one same-text live update to validate auth/member/version wiring
|
||||
@@ -157,10 +195,10 @@ What should be verified in later automated live tests:
|
||||
|
||||
### Workflow 1: Daily Note Discovery
|
||||
|
||||
- Simulates: Codex entering the same daily workspace the user is using
|
||||
- Simulates: an agent entering a configured daily-note workspace
|
||||
- Operations chained:
|
||||
- `recent`
|
||||
- `path-docs 'Workspace/Daily tasks'`
|
||||
- `path-docs '<daily-folder-ref>'`
|
||||
- Verified:
|
||||
- folder path resolution
|
||||
- correct daily-note document ids
|
||||
@@ -170,8 +208,8 @@ What should be verified in later automated live tests:
|
||||
|
||||
- Simulates: Codex locating the exact node to edit before sending any write
|
||||
- Operations chained:
|
||||
- `open-path 'Workspace/Daily tasks/26.03.16'`
|
||||
- `doc-nodes 'Workspace/Daily tasks/26.03.16' --query '日志流'`
|
||||
- `open-path '<doc-ref>'`
|
||||
- `doc-nodes '<doc-ref>' --query '<anchor>'`
|
||||
- Verified:
|
||||
- live document lookup
|
||||
- correct node id
|
||||
@@ -181,7 +219,8 @@ What should be verified in later automated live tests:
|
||||
|
||||
- Simulates: Codex jumping directly to the user's current daily note
|
||||
- Operations chained:
|
||||
- `daily-current --json`
|
||||
- `daily-current '<daily-folder-ref>' --json`
|
||||
- `daily-current --json` with `MUBU_DAILY_FOLDER='<daily-folder-ref>'`
|
||||
- Verified:
|
||||
- date-like title filtering
|
||||
- template exclusion
|
||||
@@ -191,7 +230,8 @@ What should be verified in later automated live tests:
|
||||
|
||||
- Simulates: Codex looking for an anchor inside today's daily note without manually resolving the path first
|
||||
- Operations chained:
|
||||
- `daily-nodes --query '...'`
|
||||
- `daily-nodes '<daily-folder-ref>' --query '<anchor>'`
|
||||
- `daily-nodes --query '<anchor>'` with `MUBU_DAILY_FOLDER='<daily-folder-ref>'`
|
||||
- Verified:
|
||||
- current daily-note resolution
|
||||
- live document fetch
|
||||
@@ -244,17 +284,13 @@ What should be verified in later automated live tests:
|
||||
Command:
|
||||
|
||||
```bash
|
||||
python3 -m unittest tests/test_mubu_probe.py tests/test_cli_entrypoint.py tests/test_agent_harness.py
|
||||
CLI_ANYTHING_FORCE_INSTALLED=1 python3 -m pytest cli_anything/mubu/tests -q
|
||||
```
|
||||
|
||||
Latest result:
|
||||
|
||||
```text
|
||||
................................................
|
||||
----------------------------------------------------------------------
|
||||
Ran 48 tests in 16.880s
|
||||
|
||||
OK
|
||||
96 passed
|
||||
```
|
||||
|
||||
### Syntax Verification
|
||||
@@ -284,6 +320,7 @@ Commands:
|
||||
.venv/bin/python -m pip install -e ./agent-harness
|
||||
.venv/bin/python -m pip install -e .
|
||||
.venv/bin/cli-anything-mubu --help
|
||||
.venv/bin/cli-anything-mubu --json discover daily-current '<daily-folder-ref>'
|
||||
.venv/bin/cli-anything-mubu --json discover daily-current
|
||||
.venv/bin/cli-anything-mubu session status --json
|
||||
tmpdir=$(mktemp -d)
|
||||
@@ -294,7 +331,8 @@ Latest result:
|
||||
|
||||
- both editable-install paths succeeded when run sequentially
|
||||
- installed `--help` exposes grouped `discover` / `inspect` / `mutate` / `session` domains
|
||||
- installed `discover daily-current` resolved the real daily note `Workspace/Daily tasks/26.03.16`
|
||||
- installed `discover daily-current '<daily-folder-ref>'` resolved the current daily note
|
||||
- installed `discover daily-current` also works when `MUBU_DAILY_FOLDER` is configured
|
||||
- installed `session status --json` returned persisted state successfully
|
||||
- installed no-arg REPL started cleanly, displayed the packaged canonical skill path, and exited cleanly
|
||||
|
||||
@@ -335,13 +373,15 @@ Latest result:
|
||||
Commands:
|
||||
|
||||
```bash
|
||||
.venv/bin/cli-anything-mubu daily-current --json
|
||||
.venv/bin/cli-anything-mubu discover daily-current '<daily-folder-ref>' --json
|
||||
.venv/bin/cli-anything-mubu discover daily-current --json
|
||||
printf 'exit\n' | env CLI_ANYTHING_MUBU_STATE_DIR="$(mktemp -d)" .venv/bin/cli-anything-mubu
|
||||
```
|
||||
|
||||
Latest result:
|
||||
|
||||
- installed `daily-current --json` passed against the real local Mubu session
|
||||
- installed `discover daily-current '<daily-folder-ref>' --json` passed against the real local Mubu session
|
||||
- installed no-arg `discover daily-current --json` passed when `MUBU_DAILY_FOLDER` was configured
|
||||
- installed REPL banner pointed to `agent-harness/cli_anything/mubu/skills/SKILL.md`
|
||||
|
||||
### Wheel Packaging Verification
|
||||
@@ -387,7 +427,8 @@ python3 -m venv .venv
|
||||
.venv/bin/cli-anything-mubu --help
|
||||
.venv/bin/cli-anything-mubu repl --help
|
||||
tmpdir=$(mktemp -d) && env CLI_ANYTHING_MUBU_STATE_DIR="$tmpdir" /usr/bin/zsh -lc "printf 'exit\n' | .venv/bin/cli-anything-mubu"
|
||||
.venv/bin/cli-anything-mubu daily-current --json
|
||||
.venv/bin/cli-anything-mubu discover daily-current '<daily-folder-ref>' --json
|
||||
.venv/bin/cli-anything-mubu discover daily-current --json
|
||||
.venv/bin/python -m pip install -e ./agent-harness
|
||||
python3 agent-harness/setup.py --name
|
||||
python3 agent-harness/setup.py --version
|
||||
@@ -404,7 +445,8 @@ Latest result:
|
||||
- REPL can store and report the current node reference during a session
|
||||
- REPL can persist `current-node` across independent processes when given the same state directory
|
||||
- REPL can expand both `@doc` and `@node` into a real dry-run command
|
||||
- installed console script can resolve the current daily note
|
||||
- installed console script can resolve the current daily note from an explicit folder reference
|
||||
- installed console script also supports no-arg daily resolution when `MUBU_DAILY_FOLDER` is set
|
||||
- `agent-harness/` now works as a real editable-install root
|
||||
- harness setup metadata reports the correct package identity
|
||||
|
||||
@@ -413,14 +455,16 @@ Latest result:
|
||||
Commands executed on the real machine:
|
||||
|
||||
```bash
|
||||
python3 mubu_probe.py path-docs 'Workspace/Daily tasks' --limit 5 --json
|
||||
python3 mubu_probe.py daily-current --json
|
||||
python3 mubu_probe.py daily-nodes --query '日志流' --json
|
||||
python3 mubu_probe.py doc-nodes 'Workspace/Daily tasks/26.03.16' --query '日志流' --json
|
||||
python3 mubu_probe.py create-child 'Workspace/Daily tasks/26.03.16' --parent-node-id node-demo1 --text 'CLI bridge dry run child' --note 'not executed' --json
|
||||
python3 mubu_probe.py delete-node 'Workspace/Daily tasks/26.03.16' --node-id node-demo1 --json
|
||||
python3 mubu_probe.py update-text 'Workspace/Daily tasks/26.03.16' --node-id node-demo1 --text '日志流' --json
|
||||
python3 mubu_probe.py update-text 'Workspace/Daily tasks/26.03.16' --match-text '日志流' --text '日志流' --execute --json
|
||||
python3 mubu_probe.py path-docs '<daily-folder-ref>' --limit 5 --json
|
||||
python3 mubu_probe.py daily-current '<daily-folder-ref>' --json
|
||||
MUBU_DAILY_FOLDER='<daily-folder-ref>' python3 mubu_probe.py daily-current --json
|
||||
python3 mubu_probe.py daily-nodes '<daily-folder-ref>' --query '<anchor>' --json
|
||||
MUBU_DAILY_FOLDER='<daily-folder-ref>' python3 mubu_probe.py daily-nodes --query '<anchor>' --json
|
||||
python3 mubu_probe.py doc-nodes '<doc-ref>' --query '<anchor>' --json
|
||||
python3 mubu_probe.py create-child '<doc-ref>' --parent-node-id <node-id> --text 'CLI bridge dry run child' --note 'not executed' --json
|
||||
python3 mubu_probe.py delete-node '<doc-ref>' --node-id <node-id> --json
|
||||
python3 mubu_probe.py update-text '<doc-ref>' --node-id <node-id> --text '<replacement-text>' --json
|
||||
python3 mubu_probe.py update-text '<doc-ref>' --match-text '<anchor>' --text '<replacement-text>' --execute --json
|
||||
python3 - <<'PY'
|
||||
# create-child --execute scratch node, then delete-node --execute that exact node id
|
||||
PY
|
||||
@@ -428,23 +472,22 @@ PY
|
||||
|
||||
Observed results:
|
||||
|
||||
- `path-docs` resolved folder id `folder-daily-01`
|
||||
- current daily doc resolved to `doc-demo-01`
|
||||
- `daily-current` resolved the same current daily path `Workspace/Daily tasks/26.03.16` in one step
|
||||
- `daily-nodes` resolved the same current daily note and returned live node `node-demo1`
|
||||
- `doc-nodes` resolved node id `node-demo1`, path `["nodes", 3, 0]`, and api path `["nodes", 3, "children", 0]`
|
||||
- `create-child` dry-run resolved parent `node-demo1`, child insert index `4`, and child path `["nodes", 3, "children", 0, "children", 4]`
|
||||
- `delete-node` dry-run resolved parent `qv9klzkq2L`, delete index `0`, and api path `["nodes", 3, "children", 0]`
|
||||
- `path-docs` resolved the configured daily folder successfully
|
||||
- `daily-current` resolved the same current daily note with both the explicit folder argument and `MUBU_DAILY_FOLDER`
|
||||
- `daily-nodes` resolved the same current daily note and returned the targeted live node
|
||||
- `doc-nodes` returned a stable node id plus both simplified and API paths for the target node
|
||||
- `create-child` dry-run resolved the parent node, child insert index, and canonical child path
|
||||
- `delete-node` dry-run resolved the parent id, delete index, and canonical API path
|
||||
- dry-run update produced the expected `CHANGE` payload
|
||||
- real execute returned success
|
||||
- live document version advanced from `256` to `257`
|
||||
- post-fetch verification confirmed the node text still read `日志流`
|
||||
- reversible scratch create/delete advanced live version from `261` to `262` to `263`
|
||||
- scratch node `hUVCZEUf3R` was present after create and absent after delete
|
||||
- live document version advanced after execution
|
||||
- post-fetch verification confirmed the target node text matched the requested value
|
||||
- reversible scratch create/delete advanced live version on each execute call
|
||||
- the scratch node was present after create and absent after delete
|
||||
|
||||
## Summary Statistics
|
||||
|
||||
- automated tests: 40 / 40 pass
|
||||
- automated tests: 96 / 96 pass
|
||||
- syntax check: pass
|
||||
- help/CLI surface checks: pass
|
||||
- isolated install / entrypoint checks: pass
|
||||
|
||||
@@ -126,6 +126,10 @@ class AgentHarnessPackagingTests(unittest.TestCase):
|
||||
self.assertIn("### Session", content)
|
||||
self.assertIn("| `status` |", content)
|
||||
self.assertIn("| `state-path` |", content)
|
||||
self.assertIn("MUBU_DAILY_FOLDER", content)
|
||||
self.assertNotIn("Workspace/Daily tasks", content)
|
||||
self.assertNotIn("Daily tasks resolution", content)
|
||||
self.assertIn("## Version\n\n0.1.0", content)
|
||||
finally:
|
||||
output_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
@@ -7,15 +7,61 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from cli_anything.mubu.mubu_cli import expand_repl_aliases_with_state
|
||||
from mubu_probe import DEFAULT_BACKUP_ROOT, DEFAULT_STORAGE_ROOT
|
||||
from mubu_probe import (
|
||||
DEFAULT_BACKUP_ROOT,
|
||||
DEFAULT_STORAGE_ROOT,
|
||||
build_folder_indexes,
|
||||
choose_current_daily_document,
|
||||
load_document_metas,
|
||||
load_folders,
|
||||
)
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||
SAMPLE_DOC_REF = "workspace/Daily tasks/2026.03.18"
|
||||
SAMPLE_NODE_ID = "node-demo1"
|
||||
SAMPLE_DOC_REF = "workspace/reference docs/sample-doc"
|
||||
SAMPLE_NODE_ID = "node-sample-1"
|
||||
HAS_LOCAL_DATA = DEFAULT_BACKUP_ROOT.is_dir() and DEFAULT_STORAGE_ROOT.is_dir()
|
||||
|
||||
|
||||
def detect_daily_folder_ref() -> str | None:
|
||||
if not HAS_LOCAL_DATA:
|
||||
return None
|
||||
|
||||
metas = load_document_metas(DEFAULT_STORAGE_ROOT)
|
||||
folders = load_folders(DEFAULT_STORAGE_ROOT)
|
||||
_, folder_paths = build_folder_indexes(folders)
|
||||
docs_by_folder: dict[str, list[dict[str, object]]] = {}
|
||||
for meta in metas:
|
||||
folder_id = meta.get("folder_id")
|
||||
if isinstance(folder_id, str):
|
||||
docs_by_folder.setdefault(folder_id, []).append(meta)
|
||||
|
||||
best_path: str | None = None
|
||||
best_score = -1
|
||||
for folder in folders:
|
||||
folder_id = folder.get("folder_id")
|
||||
if not isinstance(folder_id, str):
|
||||
continue
|
||||
_, candidates = choose_current_daily_document(docs_by_folder.get(folder_id, []))
|
||||
if not candidates:
|
||||
continue
|
||||
folder_path = folder_paths.get(folder_id, "")
|
||||
if not folder_path:
|
||||
continue
|
||||
score = max(
|
||||
max(item.get("updated_at") or 0, item.get("created_at") or 0)
|
||||
for item in candidates
|
||||
)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_path = folder_path
|
||||
return best_path
|
||||
|
||||
|
||||
DETECTED_DAILY_FOLDER_REF = detect_daily_folder_ref()
|
||||
HAS_DAILY_FOLDER = HAS_LOCAL_DATA and DETECTED_DAILY_FOLDER_REF is not None
|
||||
|
||||
|
||||
def resolve_cli() -> list[str]:
|
||||
installed = shutil.which("cli-anything-mubu")
|
||||
if installed:
|
||||
@@ -191,9 +237,16 @@ class CliEntrypointTests(unittest.TestCase):
|
||||
self.assertEqual(final.returncode, 0, msg=final.stderr)
|
||||
self.assertIn("Current node: <unset>", final.stdout)
|
||||
|
||||
@unittest.skipUnless(HAS_LOCAL_DATA, "Mubu local data directories not found")
|
||||
@unittest.skipUnless(HAS_DAILY_FOLDER, "Mubu local data or daily folder not found")
|
||||
def test_grouped_discover_daily_current_supports_global_json_flag(self):
|
||||
result = self.run_cli(["--json", "discover", "daily-current"])
|
||||
missing = self.run_cli(["--json", "discover", "daily-current"])
|
||||
self.assertNotEqual(missing.returncode, 0)
|
||||
self.assertIn("MUBU_DAILY_FOLDER", missing.stderr)
|
||||
|
||||
result = self.run_cli(
|
||||
["--json", "discover", "daily-current"],
|
||||
extra_env={"MUBU_DAILY_FOLDER": DETECTED_DAILY_FOLDER_REF},
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
||||
self.assertIn('"doc_path"', result.stdout)
|
||||
|
||||
|
||||
@@ -22,7 +22,15 @@ REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||
# Import mubu_probe defaults for path detection
|
||||
sys.path.insert(0, str(REPO_ROOT / "agent-harness"))
|
||||
try:
|
||||
from mubu_probe import DEFAULT_BACKUP_ROOT, DEFAULT_LOG_ROOT, DEFAULT_STORAGE_ROOT
|
||||
from mubu_probe import (
|
||||
DEFAULT_BACKUP_ROOT,
|
||||
DEFAULT_LOG_ROOT,
|
||||
DEFAULT_STORAGE_ROOT,
|
||||
build_folder_indexes,
|
||||
choose_current_daily_document,
|
||||
load_document_metas,
|
||||
load_folders,
|
||||
)
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
@@ -31,7 +39,62 @@ HAS_LOCAL_DATA = (
|
||||
and DEFAULT_STORAGE_ROOT.is_dir()
|
||||
)
|
||||
|
||||
SKIP_REASON = "Mubu local data directories not found"
|
||||
|
||||
def detect_daily_folder_ref() -> str | None:
|
||||
if not HAS_LOCAL_DATA:
|
||||
return None
|
||||
|
||||
metas = load_document_metas(DEFAULT_STORAGE_ROOT)
|
||||
folders = load_folders(DEFAULT_STORAGE_ROOT)
|
||||
_, folder_paths = build_folder_indexes(folders)
|
||||
docs_by_folder: dict[str, list[dict[str, object]]] = {}
|
||||
for meta in metas:
|
||||
folder_id = meta.get("folder_id")
|
||||
if isinstance(folder_id, str):
|
||||
docs_by_folder.setdefault(folder_id, []).append(meta)
|
||||
|
||||
best_path: str | None = None
|
||||
best_score = -1
|
||||
for folder in folders:
|
||||
folder_id = folder.get("folder_id")
|
||||
if not isinstance(folder_id, str):
|
||||
continue
|
||||
_, candidates = choose_current_daily_document(docs_by_folder.get(folder_id, []))
|
||||
if not candidates:
|
||||
continue
|
||||
folder_path = folder_paths.get(folder_id, "")
|
||||
if not folder_path:
|
||||
continue
|
||||
score = max(
|
||||
max(item.get("updated_at") or 0, item.get("created_at") or 0)
|
||||
for item in candidates
|
||||
)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_path = folder_path
|
||||
return best_path
|
||||
|
||||
|
||||
DETECTED_DAILY_FOLDER_REF = detect_daily_folder_ref()
|
||||
HAS_DAILY_FOLDER = HAS_LOCAL_DATA and DETECTED_DAILY_FOLDER_REF is not None
|
||||
|
||||
SKIP_REASON = "Mubu local data or a daily-style folder was not found"
|
||||
LIVE_API_SKIP_MARKERS = (
|
||||
"CERTIFICATE_VERIFY_FAILED",
|
||||
"SSLCertVerificationError",
|
||||
"Hostname mismatch",
|
||||
"request failed for https://api2.mubu.com",
|
||||
"urlopen error",
|
||||
)
|
||||
|
||||
|
||||
def assert_cli_success_or_skip(testcase: unittest.TestCase, result: subprocess.CompletedProcess) -> None:
|
||||
if result.returncode == 0:
|
||||
return
|
||||
details = "\n".join(part for part in (result.stdout, result.stderr) if part).strip()
|
||||
if any(marker in details for marker in LIVE_API_SKIP_MARKERS):
|
||||
testcase.skipTest(f"live Mubu API unavailable in this environment: {details.splitlines()[-1]}")
|
||||
testcase.fail(details or f"CLI exited with status {result.returncode}")
|
||||
|
||||
|
||||
def resolve_cli() -> list[str]:
|
||||
@@ -41,13 +104,15 @@ def resolve_cli() -> list[str]:
|
||||
return [sys.executable, "-m", "cli_anything.mubu"]
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_LOCAL_DATA, SKIP_REASON)
|
||||
@unittest.skipUnless(HAS_DAILY_FOLDER, SKIP_REASON)
|
||||
class DiscoverE2ETests(unittest.TestCase):
|
||||
CLI_BASE = resolve_cli()
|
||||
|
||||
def run_cli(self, args: list[str]) -> subprocess.CompletedProcess:
|
||||
def run_cli(self, args: list[str], extra_env: dict | None = None) -> subprocess.CompletedProcess:
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(REPO_ROOT) + os.pathsep + env.get("PYTHONPATH", "")
|
||||
if extra_env:
|
||||
env.update(extra_env)
|
||||
return subprocess.run(
|
||||
self.CLI_BASE + args,
|
||||
capture_output=True,
|
||||
@@ -80,22 +145,27 @@ class DiscoverE2ETests(unittest.TestCase):
|
||||
self.assertGreater(len(data), 0)
|
||||
|
||||
def test_daily_current_returns_doc_path(self):
|
||||
result = self.run_cli(["daily-current", "--json"])
|
||||
result = self.run_cli(
|
||||
["daily-current", "--json"],
|
||||
extra_env={"MUBU_DAILY_FOLDER": DETECTED_DAILY_FOLDER_REF},
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
||||
data = json.loads(result.stdout)
|
||||
# Response wraps document info in a nested structure
|
||||
doc = data.get("document", data)
|
||||
self.assertIn("doc_path", doc)
|
||||
self.assertIn("Daily tasks", doc["doc_path"])
|
||||
self.assertIn(DETECTED_DAILY_FOLDER_REF, doc["doc_path"])
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_LOCAL_DATA, SKIP_REASON)
|
||||
@unittest.skipUnless(HAS_DAILY_FOLDER, SKIP_REASON)
|
||||
class InspectE2ETests(unittest.TestCase):
|
||||
CLI_BASE = resolve_cli()
|
||||
|
||||
def run_cli(self, args: list[str]) -> subprocess.CompletedProcess:
|
||||
def run_cli(self, args: list[str], extra_env: dict | None = None) -> subprocess.CompletedProcess:
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(REPO_ROOT) + os.pathsep + env.get("PYTHONPATH", "")
|
||||
if extra_env:
|
||||
env.update(extra_env)
|
||||
return subprocess.run(
|
||||
self.CLI_BASE + args,
|
||||
capture_output=True,
|
||||
@@ -111,14 +181,17 @@ class InspectE2ETests(unittest.TestCase):
|
||||
self.assertIsInstance(data, list)
|
||||
|
||||
def test_daily_nodes_returns_node_list(self):
|
||||
result = self.run_cli(["daily-nodes", "--json"])
|
||||
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
||||
result = self.run_cli(
|
||||
["daily-nodes", "--json"],
|
||||
extra_env={"MUBU_DAILY_FOLDER": DETECTED_DAILY_FOLDER_REF},
|
||||
)
|
||||
assert_cli_success_or_skip(self, result)
|
||||
data = json.loads(result.stdout)
|
||||
self.assertIn("nodes", data)
|
||||
self.assertIsInstance(data["nodes"], list)
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_LOCAL_DATA, SKIP_REASON)
|
||||
@unittest.skipUnless(HAS_DAILY_FOLDER, SKIP_REASON)
|
||||
class SessionE2ETests(unittest.TestCase):
|
||||
CLI_BASE = resolve_cli()
|
||||
|
||||
@@ -138,35 +211,43 @@ class SessionE2ETests(unittest.TestCase):
|
||||
|
||||
def test_session_use_daily_sets_current_doc(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
env = {"CLI_ANYTHING_MUBU_STATE_DIR": tmpdir}
|
||||
env = {
|
||||
"CLI_ANYTHING_MUBU_STATE_DIR": tmpdir,
|
||||
"MUBU_DAILY_FOLDER": DETECTED_DAILY_FOLDER_REF,
|
||||
}
|
||||
self.run_cli(["session", "use-daily"], extra_env=env)
|
||||
result = self.run_cli(["session", "status", "--json"], extra_env=env)
|
||||
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
||||
data = json.loads(result.stdout)
|
||||
self.assertIsNotNone(data.get("current_doc"))
|
||||
self.assertIn("Daily tasks", data["current_doc"])
|
||||
self.assertIn(DETECTED_DAILY_FOLDER_REF, data["current_doc"])
|
||||
|
||||
def test_repl_use_daily_then_daily_nodes(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
env = {"CLI_ANYTHING_MUBU_STATE_DIR": tmpdir}
|
||||
env = {
|
||||
"CLI_ANYTHING_MUBU_STATE_DIR": tmpdir,
|
||||
"MUBU_DAILY_FOLDER": DETECTED_DAILY_FOLDER_REF,
|
||||
}
|
||||
result = self.run_cli(
|
||||
[],
|
||||
input_text="use-daily\ndaily-nodes --json\nexit\n",
|
||||
extra_env=env,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
||||
assert_cli_success_or_skip(self, result)
|
||||
self.assertIn('"nodes"', result.stdout)
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_LOCAL_DATA, SKIP_REASON)
|
||||
@unittest.skipUnless(HAS_DAILY_FOLDER, SKIP_REASON)
|
||||
class MutateDryRunE2ETests(unittest.TestCase):
|
||||
"""Test mutation commands in dry-run mode (no --execute)."""
|
||||
|
||||
CLI_BASE = resolve_cli()
|
||||
|
||||
def run_cli(self, args: list[str]) -> subprocess.CompletedProcess:
|
||||
def run_cli(self, args: list[str], extra_env: dict | None = None) -> subprocess.CompletedProcess:
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(REPO_ROOT) + os.pathsep + env.get("PYTHONPATH", "")
|
||||
if extra_env:
|
||||
env.update(extra_env)
|
||||
return subprocess.run(
|
||||
self.CLI_BASE + args,
|
||||
capture_output=True,
|
||||
@@ -177,7 +258,11 @@ class MutateDryRunE2ETests(unittest.TestCase):
|
||||
|
||||
def _resolve_daily_node(self) -> tuple[str, str]:
|
||||
"""Helper: get a stable daily document reference and first node id."""
|
||||
result = self.run_cli(["daily-nodes", "--json"])
|
||||
result = self.run_cli(
|
||||
["daily-nodes", "--json"],
|
||||
extra_env={"MUBU_DAILY_FOLDER": DETECTED_DAILY_FOLDER_REF},
|
||||
)
|
||||
assert_cli_success_or_skip(self, result)
|
||||
data = json.loads(result.stdout)
|
||||
doc = data.get("document", data)
|
||||
doc_ref = doc.get("doc_id") or doc["doc_path"]
|
||||
|
||||
@@ -87,6 +87,31 @@ DAILY_TITLE_RE = re.compile(r"^\d{2}\.\d{1,2}\.\d{1,2}(?:-\d{1,2}(?:\.\d{1,2})?)
|
||||
DEFAULT_DAILY_EXCLUDE_KEYWORDS = ("模板", "template")
|
||||
|
||||
|
||||
def configured_daily_folder_ref(env: Mapping[str, str] | None = None) -> str | None:
|
||||
env = env or os.environ
|
||||
value = env.get("MUBU_DAILY_FOLDER", "")
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
resolved = value.strip()
|
||||
return resolved or None
|
||||
|
||||
|
||||
def resolve_daily_folder_ref(
|
||||
folder_ref: str | None,
|
||||
env: Mapping[str, str] | None = None,
|
||||
) -> str:
|
||||
value = (folder_ref or "").strip()
|
||||
if value:
|
||||
return value
|
||||
configured = configured_daily_folder_ref(env=env)
|
||||
if configured:
|
||||
return configured
|
||||
raise RuntimeError(
|
||||
"daily folder reference required; pass <folder_ref> explicitly "
|
||||
"or set MUBU_DAILY_FOLDER"
|
||||
)
|
||||
|
||||
|
||||
def extract_plain_text(value: Any) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
@@ -1468,7 +1493,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
"daily-current",
|
||||
help="Resolve the current daily document from one Daily-style folder.",
|
||||
)
|
||||
daily_current_parser.add_argument("folder_ref", nargs="?", default="Daily tasks")
|
||||
daily_current_parser.add_argument("folder_ref", nargs="?")
|
||||
daily_current_parser.add_argument("--storage-root", type=Path, default=DEFAULT_STORAGE_ROOT)
|
||||
daily_current_parser.add_argument("--limit", type=int, default=5)
|
||||
daily_current_parser.add_argument(
|
||||
@@ -1482,7 +1507,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
"daily-nodes",
|
||||
help="List live nodes from the current daily document in one step.",
|
||||
)
|
||||
daily_nodes_parser.add_argument("folder_ref", nargs="?", default="Daily tasks")
|
||||
daily_nodes_parser.add_argument("folder_ref", nargs="?")
|
||||
daily_nodes_parser.add_argument("--storage-root", type=Path, default=DEFAULT_STORAGE_ROOT)
|
||||
daily_nodes_parser.add_argument("--api-host", default=DEFAULT_API_HOST)
|
||||
daily_nodes_parser.add_argument("--query", default=None, help="Filter nodes by plain-text substring.")
|
||||
@@ -1695,11 +1720,15 @@ def main(argv: list[str] | None = None) -> int:
|
||||
if args.command == "daily-current":
|
||||
metas = load_document_metas(args.storage_root)
|
||||
folders = load_folders(args.storage_root)
|
||||
docs, folder, ambiguous = folder_documents(metas, folders, args.folder_ref)
|
||||
try:
|
||||
folder_ref = resolve_daily_folder_ref(args.folder_ref)
|
||||
except RuntimeError as exc:
|
||||
parser.error(str(exc))
|
||||
docs, folder, ambiguous = folder_documents(metas, folders, folder_ref)
|
||||
if folder is None:
|
||||
if ambiguous:
|
||||
parser.error(ambiguous_error_message("folder", args.folder_ref, ambiguous, "path"))
|
||||
parser.error(f"folder not found: {args.folder_ref}")
|
||||
parser.error(ambiguous_error_message("folder", folder_ref, ambiguous, "path"))
|
||||
parser.error(f"folder not found: {folder_ref}")
|
||||
|
||||
selected, candidates = choose_current_daily_document(
|
||||
docs,
|
||||
@@ -1733,11 +1762,15 @@ def main(argv: list[str] | None = None) -> int:
|
||||
|
||||
metas = load_document_metas(args.storage_root)
|
||||
folders = load_folders(args.storage_root)
|
||||
docs, folder, ambiguous = folder_documents(metas, folders, args.folder_ref)
|
||||
try:
|
||||
folder_ref = resolve_daily_folder_ref(args.folder_ref)
|
||||
except RuntimeError as exc:
|
||||
parser.error(str(exc))
|
||||
docs, folder, ambiguous = folder_documents(metas, folders, folder_ref)
|
||||
if folder is None:
|
||||
if ambiguous:
|
||||
parser.error(ambiguous_error_message("folder", args.folder_ref, ambiguous, "path"))
|
||||
parser.error(f"folder not found: {args.folder_ref}")
|
||||
parser.error(ambiguous_error_message("folder", folder_ref, ambiguous, "path"))
|
||||
parser.error(f"folder not found: {folder_ref}")
|
||||
|
||||
selected, candidates = choose_current_daily_document(
|
||||
docs,
|
||||
|
||||
@@ -92,9 +92,14 @@ def extract_system_package(content: str) -> Optional[str]:
|
||||
|
||||
def extract_version_from_setup(setup_path: Path) -> str:
|
||||
content = setup_path.read_text(encoding="utf-8")
|
||||
match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
|
||||
if match:
|
||||
return match.group(1)
|
||||
direct_match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
|
||||
if direct_match:
|
||||
return direct_match.group(1)
|
||||
|
||||
constant_match = re.search(r'PACKAGE_VERSION\s*=\s*["\']([^"\']+)["\']', content)
|
||||
if constant_match:
|
||||
return constant_match.group(1)
|
||||
|
||||
return "1.0.0"
|
||||
|
||||
|
||||
@@ -185,8 +190,8 @@ def generate_examples(software_name: str, command_groups: list[CommandGroup]) ->
|
||||
examples.append(
|
||||
Example(
|
||||
title="Discover Current Daily Note",
|
||||
description="Resolve the current daily note and emit JSON output for an agent.",
|
||||
code=f"""cli-anything-{software_name} --json discover daily-current""",
|
||||
description="Resolve the current daily note from an explicit folder reference.",
|
||||
code=f"""cli-anything-{software_name} --json discover daily-current '<daily-folder-ref>'""",
|
||||
)
|
||||
)
|
||||
if "mutate" in group_names:
|
||||
@@ -196,7 +201,7 @@ def generate_examples(software_name: str, command_groups: list[CommandGroup]) ->
|
||||
description="Inspect the exact outgoing payload before a live mutation.",
|
||||
code=(
|
||||
f"cli-anything-{software_name} mutate update-text "
|
||||
"'Workspace/Daily tasks/26.03.16' --node-id node-demo1 --text 'new text' --json"
|
||||
"'<doc-ref>' --node-id <node-id> --text 'new text' --json"
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -269,6 +274,7 @@ def generate_skill_md_simple(metadata: SkillMetadata) -> str:
|
||||
"- Python 3.10+",
|
||||
"- An active Mubu desktop session on this machine",
|
||||
"- Local Mubu profile data available to the CLI",
|
||||
"- Set `MUBU_DAILY_FOLDER` if you want no-argument daily helpers",
|
||||
"",
|
||||
"## Entry Points",
|
||||
"",
|
||||
@@ -296,9 +302,9 @@ def generate_skill_md_simple(metadata: SkillMetadata) -> str:
|
||||
"## Recommended Agent Workflow",
|
||||
"",
|
||||
"```text",
|
||||
"discover daily-current --json",
|
||||
"discover daily-current '<daily-folder-ref>' --json",
|
||||
" ->",
|
||||
"inspect daily-nodes --query '<anchor>' --json",
|
||||
"inspect daily-nodes '<daily-folder-ref>' --query '<anchor>' --json",
|
||||
" ->",
|
||||
"session use-doc '<doc_path>'",
|
||||
" ->",
|
||||
@@ -316,6 +322,7 @@ def generate_skill_md_simple(metadata: SkillMetadata) -> str:
|
||||
"5. Prefer `--node-id` and `--parent-node-id` over text matching.",
|
||||
"6. `delete-node` removes the full targeted subtree.",
|
||||
"7. Even same-text updates can still advance document version history.",
|
||||
"8. Pass a daily-folder reference explicitly or set `MUBU_DAILY_FOLDER` before using no-arg daily helpers.",
|
||||
"",
|
||||
"## Examples",
|
||||
"",
|
||||
|
||||
@@ -21,6 +21,7 @@ pip install -e .
|
||||
- Python 3.10+
|
||||
- An active Mubu desktop session on this machine
|
||||
- Local Mubu profile data available to the CLI
|
||||
- Set `MUBU_DAILY_FOLDER` if you want no-argument daily helpers
|
||||
|
||||
## Entry Points
|
||||
|
||||
@@ -48,9 +49,9 @@ When invoked without a subcommand, the CLI enters an interactive REPL session.
|
||||
## Recommended Agent Workflow
|
||||
|
||||
```text
|
||||
discover daily-current --json
|
||||
discover daily-current '<daily-folder-ref>' --json
|
||||
->
|
||||
inspect daily-nodes --query '<anchor>' --json
|
||||
inspect daily-nodes '<daily-folder-ref>' --query '<anchor>' --json
|
||||
->
|
||||
session use-doc '<doc_path>'
|
||||
->
|
||||
@@ -68,6 +69,7 @@ mutate update-text / create-child / delete-node --json
|
||||
5. Prefer `--node-id` and `--parent-node-id` over text matching.
|
||||
6. `delete-node` removes the full targeted subtree.
|
||||
7. Even same-text updates can still advance document version history.
|
||||
8. Pass a daily-folder reference explicitly or set `MUBU_DAILY_FOLDER` before using no-arg daily helpers.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
Reference in New Issue
Block a user