nexus-cli 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.STATUS +8 -8
  2. nexus_cli-0.7.0/.claude/guard-session-counts +1 -0
  3. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/homebrew-release.yml +6 -0
  4. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/CLAUDE.md +6 -3
  5. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/PKG-INFO +20 -4
  6. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/README.md +17 -1
  7. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/adhd-quick-start.md +1 -1
  8. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/changelog.md +25 -0
  9. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/index.md +1 -1
  10. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/__init__.py +1 -1
  11. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/cli.py +11 -1
  12. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/pyproject.toml +3 -3
  13. nexus_cli-0.7.0/tests/test_vault.py +409 -0
  14. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/uv.lock +1 -1
  15. nexus_cli-0.6.0/tests/test_vault.py +0 -218
  16. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/ci.yml +0 -0
  17. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/docs.yml +0 -0
  18. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/publish.yml +0 -0
  19. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/quality.yml +0 -0
  20. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/release.yml +0 -0
  21. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.github/workflows/test.yml +0 -0
  22. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.gitignore +0 -0
  23. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/.python-version +0 -0
  24. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/CI_CD_SUMMARY.md +0 -0
  25. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/DOCUMENTATION_UPDATE_SUMMARY.md +0 -0
  26. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/FINAL_SUMMARY.md +0 -0
  27. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/HOMEBREW_FORMULA_SETUP.md +0 -0
  28. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/INSTALLATION_TROUBLESHOOTING.md +0 -0
  29. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/ORCHESTRATE-nexus-enhancement.md +0 -0
  30. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/PLANNING.md +0 -0
  31. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/README_BADGES.md +0 -0
  32. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/RELEASE_NOTES_v0.4.0.md +0 -0
  33. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/RELEASE_NOTES_v0.5.0.md +0 -0
  34. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/RELEASE_NOTES_v0.5.1.md +0 -0
  35. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/SESSION_COMPLETE.md +0 -0
  36. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/SESSION_COMPLETE.txt +0 -0
  37. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/SESSION_WRAP_UP.txt +0 -0
  38. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/TEST_SUMMARY.md +0 -0
  39. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/TODO.md +0 -0
  40. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/TUTORIAL_GUIDE.md +0 -0
  41. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/TUTORIAL_QUICK_REF.md +0 -0
  42. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/sessions-2025-12/SESSION_2025-12-25_TUTORIAL_SYSTEM.md +0 -0
  43. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/sessions-2025-12/SESSION_COMPLETE_2025-12-25.md +0 -0
  44. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/sessions-2025-12/SESSION_SUMMARY.md +0 -0
  45. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/sessions-2025-12/TUTORIAL_SYSTEM_COMPLETE.md +0 -0
  46. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/FINAL_SESSION_SUMMARY.md +0 -0
  47. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/Formula-local-test.rb +0 -0
  48. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/QUICK_START_TESTING.md +0 -0
  49. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/QUICK_TEST_GUIDE.md +0 -0
  50. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/TESTING_GUIDE.md +0 -0
  51. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/interactive_dogfood.py +0 -0
  52. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/archive/testing-utilities/test_local_install.sh +0 -0
  53. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/config/nexus.example.yaml +0 -0
  54. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/development/architecture.md +0 -0
  55. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/development/contributing.md +0 -0
  56. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/development/testing.md +0 -0
  57. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/getting-started/configuration.md +0 -0
  58. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/getting-started/installation.md +0 -0
  59. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/getting-started/quickstart.md +0 -0
  60. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/guide/knowledge.md +0 -0
  61. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/guide/overview.md +0 -0
  62. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/guide/research.md +0 -0
  63. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/guide/teaching.md +0 -0
  64. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/guide/writing.md +0 -0
  65. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/reference/api.md +0 -0
  66. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/reference/cli.md +0 -0
  67. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/reference/mcp.md +0 -0
  68. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/reference/refcard.md +0 -0
  69. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/specs/SPEC-bibliography-coverage.md +0 -0
  70. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/specs/SPEC-cli-coverage.md +0 -0
  71. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/specs/SPEC-courses-coverage.md +0 -0
  72. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/specs/SPEC-nexus-doctor.md +0 -0
  73. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/first-steps.md +0 -0
  74. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/graph-viz.md +0 -0
  75. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/tutorial-guide.md +0 -0
  76. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/tutorial-quick-ref.md +0 -0
  77. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/tutorial-system.md +0 -0
  78. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/vault-setup.md +0 -0
  79. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/tutorials/zotero.md +0 -0
  80. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/docs/workflows/index.md +0 -0
  81. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/install.sh +0 -0
  82. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/main.py +0 -0
  83. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/mkdocs.yml +0 -0
  84. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/data/vault-spec.yaml +0 -0
  85. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/integrations/__init__.py +0 -0
  86. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/knowledge/__init__.py +0 -0
  87. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/knowledge/search.py +0 -0
  88. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/knowledge/vault.py +0 -0
  89. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/research/__init__.py +0 -0
  90. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/research/pdf.py +0 -0
  91. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/research/zotero.py +0 -0
  92. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/teaching/__init__.py +0 -0
  93. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/teaching/courses.py +0 -0
  94. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/teaching/quarto.py +0 -0
  95. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/utils/__init__.py +0 -0
  96. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/utils/config.py +0 -0
  97. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/utils/help_text.py +0 -0
  98. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/utils/tutorial.py +0 -0
  99. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/utils/vault_spec.py +0 -0
  100. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/writing/__init__.py +0 -0
  101. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/writing/bibliography.py +0 -0
  102. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/nexus/writing/manuscript.py +0 -0
  103. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/output.json +0 -0
  104. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/.claude-plugin/plugin.json +0 -0
  105. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/README.md +0 -0
  106. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/commands/check.md +0 -0
  107. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/commands/cite.md +0 -0
  108. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/commands/dashboard.md +0 -0
  109. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/commands/manuscripts.md +0 -0
  110. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/commands/search.md +0 -0
  111. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/skills/integration-patterns/skill.md +0 -0
  112. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/skills/knowledge/vault-operations/skill.md +0 -0
  113. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/skills/research/zotero-integration/skill.md +0 -0
  114. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/skills/teaching/course-management/skill.md +0 -0
  115. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/plugin/skills/writing/manuscript-management/skill.md +0 -0
  116. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/__init__.py +0 -0
  117. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/conftest.py +0 -0
  118. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_bibliography.py +0 -0
  119. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli.py +0 -0
  120. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli_commands.py +0 -0
  121. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli_coverage.py +0 -0
  122. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli_integration.py +0 -0
  123. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli_research_commands.py +0 -0
  124. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli_vault_commands.py +0 -0
  125. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_cli_writing_commands.py +0 -0
  126. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_config.py +0 -0
  127. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_courses.py +0 -0
  128. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_dogfooding.py +0 -0
  129. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_edge_cases.py +0 -0
  130. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_graph_export.py +0 -0
  131. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_help_text.py +0 -0
  132. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_manuscript.py +0 -0
  133. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_manuscript_batch.py +0 -0
  134. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_pdf.py +0 -0
  135. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_pdf_extractor.py +0 -0
  136. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_quarto.py +0 -0
  137. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_quarto_manager.py +0 -0
  138. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_search.py +0 -0
  139. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_tutorial.py +0 -0
  140. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_validation.py +0 -0
  141. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_vault_spec.py +0 -0
  142. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_zotero.py +0 -0
  143. {nexus_cli-0.6.0 → nexus_cli-0.7.0}/tests/test_zotero_client.py +0 -0
@@ -1,12 +1,12 @@
1
- status: active
2
- priority: 1
3
- progress: 80
4
- next: Merge dev→main release PR, tag v0.6.0, GitHub release (triggers PyPI + homebrew + docs deploy)
1
+ status: deprecated
2
+ priority: 0
3
+ progress: 100
4
+ next: RETIRED (2026-06-21) absorbed into obsidian-cli-ops (obs) per RFC v2 D1=Option A. Deprecation banner added. Phase 5 remaining: ship v0.7.0 to PyPI with deprecation banner, retire Homebrew formula, archive GitHub repo.
5
5
  type: dev
6
- checkpoint: 2026-06-03 — v0.6.0 release prep: version bumped, CHANGELOG promoted, docs/refcard/tutorials updated. dev→main release PR open.
7
- version: 0.6.0
8
- release_date: 2026-06-02
9
- release_url: https://github.com/Data-Wise/nexus-cli/releases/tag/v0.5.2
6
+ checkpoint: 2026-06-03 — v0.6.1 FULLY SHIPPED (patch: vault.py 78%→98.14%, +34 tests). PR #10 dev→main merged; tagged v0.6.1; PyPI 0.6.1 published (OIDC); GitHub release live; Homebrew formula manually pushed (bc186f9) + brew audit clean; CI green all platforms. Prior: v0.6.0 shipped same day.
7
+ version: 0.6.1
8
+ release_date: 2026-06-03
9
+ release_url: https://github.com/Data-Wise/nexus-cli/releases/tag/v0.6.1
10
10
  docs_url: https://data-wise.github.io/nexus-cli/
11
11
  planning_doc: ORCHESTRATE-nexus-enhancement.md
12
12
 
@@ -0,0 +1 @@
1
+ edit_existing
@@ -59,4 +59,10 @@ jobs:
59
59
  source_type: pypi
60
60
  auto_merge: ${{ github.event.inputs.auto_merge == 'true' || github.event_name == 'workflow_dispatch' }}
61
61
  secrets:
62
+ # Prefer GitHub App auth (short-lived, scoped, no expiry to rotate).
63
+ # The reusable workflow mints an App token and only falls back to
64
+ # tap_token (an expiring PAT) when the App secrets are absent.
65
+ # Matches craft's pattern — see homebrew-tap update-formula.yml.
66
+ app_id: ${{ secrets.APP_ID }}
67
+ app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
62
68
  tap_token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
@@ -13,13 +13,16 @@ Nexus is a **knowledge workflow CLI** for academic researchers. It provides data
13
13
  - Teaching materials
14
14
  - Manuscripts
15
15
 
16
- **Current Version**: 0.6.0
16
+ **Current Version**: 0.7.0 — **DEPRECATED**
17
+
18
+ > [!warning] nexus-cli is retired
19
+ > All functionality has been absorbed into `obsidian-cli-ops` (`obs`) as of Phase 1 (PR #37, 2026-06-21). This is the final release (v0.7.0). See the [migration guide](https://data-wise.github.io/obsidian-cli-ops/migration/) for command mappings. Use `obs research` and `obs config` going forward.
17
20
 
18
21
  ## Quick Start for Claude
19
22
 
20
23
  ```bash
21
24
  # Check installation
22
- nexus --version # Should show 0.6.0
25
+ nexus --version # Should show 0.7.0 (final deprecated release)
23
26
  nexus doctor # Health check
24
27
 
25
28
  # Interactive learning system
@@ -463,7 +466,7 @@ echo $PATH | grep ".local/bin"
463
466
  ✅ Full type safety (0 mypy errors across 18 source files)
464
467
 
465
468
  ### Key Metrics
466
- - **Version**: 0.6.0
469
+ - **Version**: 0.7.0 (DEPRECATED — final release)
467
470
  - **Tests**: 719 passing, 1 skipped (87.5% coverage)
468
471
  - **Mypy**: 0 errors across 20 source files
469
472
  - **Commands**: 50+ across 4 domains
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nexus-cli
3
- Version: 0.6.0
4
- Summary: Knowledge workflow CLI for research, teaching, and writing - Claude's body for academic work
3
+ Version: 0.7.0
4
+ Summary: DEPRECATED absorbed into obsidian-cli-ops (obs). See https://data-wise.github.io/obsidian-cli-ops/migration/
5
5
  Project-URL: Homepage, https://github.com/Data-Wise/nexus-cli
6
6
  Project-URL: Documentation, https://data-wise.github.io/nexus-cli
7
7
  Project-URL: Repository, https://github.com/Data-Wise/nexus-cli
8
8
  Author-email: DT <dt@example.com>
9
9
  License: MIT
10
10
  Keywords: claude,cli,knowledge-management,obsidian,research,zotero
11
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 7 - Inactive
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Intended Audience :: Science/Research
14
14
  Classifier: License :: OSI Approved :: MIT License
@@ -39,7 +39,23 @@ Requires-Dist: mkdocs>=1.5.0; extra == 'docs'
39
39
  Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'docs'
40
40
  Description-Content-Type: text/markdown
41
41
 
42
- # Nexus CLI
42
+ # Nexus CLI — DEPRECATED
43
+
44
+ > **⚠️ nexus-cli has been absorbed into [obsidian-cli-ops](https://github.com/Data-Wise/obsidian-cli-ops) (`obs`).**
45
+ >
46
+ > All research, teaching, writing, and vault commands are now available under `obs research …`.
47
+ > This package will no longer receive updates. Please migrate:
48
+ >
49
+ > ```sh
50
+ > brew upgrade data-wise/tap/obsidian-cli-ops
51
+ > # obs research zotero search "query"
52
+ > # obs research manuscript list
53
+ > # obs research course list
54
+ > ```
55
+ >
56
+ > See the [migration guide](https://data-wise.github.io/obsidian-cli-ops/migration/) for the full command mapping.
57
+
58
+ ---
43
59
 
44
60
  [![CI](https://github.com/Data-Wise/nexus-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Data-Wise/nexus-cli/actions/workflows/ci.yml)
45
61
  [![Documentation](https://github.com/Data-Wise/nexus-cli/actions/workflows/docs.yml/badge.svg)](https://data-wise.github.io/nexus-cli)
@@ -1,4 +1,20 @@
1
- # Nexus CLI
1
+ # Nexus CLI — DEPRECATED
2
+
3
+ > **⚠️ nexus-cli has been absorbed into [obsidian-cli-ops](https://github.com/Data-Wise/obsidian-cli-ops) (`obs`).**
4
+ >
5
+ > All research, teaching, writing, and vault commands are now available under `obs research …`.
6
+ > This package will no longer receive updates. Please migrate:
7
+ >
8
+ > ```sh
9
+ > brew upgrade data-wise/tap/obsidian-cli-ops
10
+ > # obs research zotero search "query"
11
+ > # obs research manuscript list
12
+ > # obs research course list
13
+ > ```
14
+ >
15
+ > See the [migration guide](https://data-wise.github.io/obsidian-cli-ops/migration/) for the full command mapping.
16
+
17
+ ---
2
18
 
3
19
  [![CI](https://github.com/Data-Wise/nexus-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Data-Wise/nexus-cli/actions/workflows/ci.yml)
4
20
  [![Documentation](https://github.com/Data-Wise/nexus-cli/actions/workflows/docs.yml/badge.svg)](https://data-wise.github.io/nexus-cli)
@@ -8,7 +8,7 @@ The absolute essentials:
8
8
 
9
9
  ```bash
10
10
  nexus doctor # Check everything is wired up
11
- nexus --version # Confirm install (should show 0.6.0)
11
+ nexus --version # Confirm install (should show 0.7.0)
12
12
  nexus learn # See what you can learn
13
13
  ```
14
14
 
@@ -1,3 +1,28 @@
1
+ ## [0.7.0] - 2026-06-21
2
+
3
+ ### Changed
4
+
5
+ - **DEPRECATED**: nexus-cli is now inactive. All functionality has been absorbed into
6
+ [obsidian-cli-ops (obs)](https://data-wise.github.io/obsidian-cli-ops/). See the
7
+ [migration guide](https://data-wise.github.io/obsidian-cli-ops/migration/) for the
8
+ full command mapping.
9
+ - Deprecation banner added to all `nexus` invocations.
10
+ - `Development Status :: 7 - Inactive` PyPI classifier applied.
11
+ - This is the final release. The package will remain on PyPI for existing installs.
12
+
13
+ ---
14
+
15
+ ## [0.6.1] - 2026-06-03
16
+
17
+ ### Changed
18
+ - **Test coverage**: `vault.py` raised from 78% → **98.14%** (+34 tests),
19
+ meeting the last outstanding v0.6.0 coverage target. New tests cover the
20
+ ripgrep search fallback (rg-missing, timeout, malformed JSON), `search_files`,
21
+ template loading/creation, daily notes, invalid-YAML frontmatter handling,
22
+ missing-vault guards, and the tag-graph node/edge paths.
23
+
24
+ ---
25
+
1
26
  ## [0.6.0] - 2026-06-03
2
27
 
3
28
  ### Added
@@ -155,7 +155,7 @@ MIT License - see [LICENSE](https://github.com/Data-Wise/nexus-cli/blob/main/LIC
155
155
 
156
156
  ## Project Stats
157
157
 
158
- **Current Version**: 0.6.0
158
+ **Current Version**: 0.7.0 (DEPRECATED — see [migration guide](https://data-wise.github.io/obsidian-cli-ops/migration/))
159
159
 
160
160
  - **Test Coverage**: 87.5% (719 tests passing)
161
161
  - **Type Safety**: 0 mypy errors across all 20 source files ✅
@@ -4,5 +4,5 @@ Nexus CLI - Knowledge workflow for research, teaching, and writing.
4
4
  Claude is the brain, Nexus is the body.
5
5
  """
6
6
 
7
- __version__ = "0.6.0"
7
+ __version__ = "0.7.0"
8
8
  __author__ = "DT"
@@ -99,7 +99,17 @@ def main(
99
99
 
100
100
  [dim]Claude is the brain, Nexus is the body.[/]
101
101
  """
102
- pass
102
+ console.print(
103
+ Panel(
104
+ "[bold yellow]⚠ nexus-cli is deprecated[/]\n\n"
105
+ "All research, teaching, writing, and vault commands have been absorbed\n"
106
+ "into [bold cyan]obs[/] (obsidian-cli-ops) under [cyan]obs research …[/].\n\n"
107
+ "Migration: [dim]brew upgrade data-wise/tap/obsidian-cli-ops[/]\n"
108
+ "Guide: [dim]https://data-wise.github.io/obsidian-cli-ops/migration/[/]",
109
+ title="[bold red]Deprecated — use obs instead[/]",
110
+ border_style="yellow",
111
+ )
112
+ )
103
113
 
104
114
 
105
115
  @app.command()
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "nexus-cli"
3
- version = "0.6.0"
4
- description = "Knowledge workflow CLI for research, teaching, and writing - Claude's body for academic work"
3
+ version = "0.7.0"
4
+ description = "DEPRECATED absorbed into obsidian-cli-ops (obs). See https://data-wise.github.io/obsidian-cli-ops/migration/"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  license = { text = "MIT" }
@@ -10,7 +10,7 @@ authors = [
10
10
  ]
11
11
  keywords = ["cli", "research", "knowledge-management", "zotero", "obsidian", "claude"]
12
12
  classifiers = [
13
- "Development Status :: 3 - Alpha",
13
+ "Development Status :: 7 - Inactive",
14
14
  "Environment :: Console",
15
15
  "Intended Audience :: Science/Research",
16
16
  "License :: OSI Approved :: MIT License",
@@ -0,0 +1,409 @@
1
+ """Tests for vault management module."""
2
+
3
+ import subprocess
4
+ from datetime import date
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+ from nexus.knowledge.vault import VaultManager
10
+
11
+
12
+ class TestVaultManager:
13
+ """Tests for VaultManager class."""
14
+
15
+ def test_vault_exists(self, sample_vault):
16
+ """Test that vault existence check works."""
17
+ manager = VaultManager(sample_vault)
18
+ assert manager.exists() is True
19
+
20
+ def test_vault_not_exists(self, temp_dir):
21
+ """Test non-existent vault."""
22
+ manager = VaultManager(temp_dir / "nonexistent")
23
+ assert manager.exists() is False
24
+
25
+ def test_note_count(self, sample_vault):
26
+ """Test counting notes in vault."""
27
+ manager = VaultManager(sample_vault)
28
+ count = manager.note_count()
29
+
30
+ # Should find the sample notes
31
+ assert count >= 2
32
+
33
+ def test_search_notes(self, sample_vault):
34
+ """Test searching notes."""
35
+ manager = VaultManager(sample_vault)
36
+
37
+ # Search for mediation
38
+ results = manager.search("mediation")
39
+ assert len(results) >= 1
40
+
41
+ # Search for something not in notes
42
+ results = manager.search("xyznonexistent123")
43
+ assert len(results) == 0
44
+
45
+ def test_recent_notes(self, sample_vault):
46
+ """Test getting recent notes."""
47
+ manager = VaultManager(sample_vault)
48
+ recent = manager.recent(limit=5)
49
+
50
+ assert len(recent) >= 2
51
+ assert len(recent) <= 5
52
+
53
+ def test_read_note(self, sample_vault):
54
+ """Test reading a note."""
55
+ manager = VaultManager(sample_vault)
56
+ note = manager.read("10-PROJECTS/test-project.md")
57
+
58
+ assert note is not None
59
+ assert "mediation analysis" in note.content
60
+
61
+ def test_read_nonexistent_note(self, sample_vault):
62
+ """Test reading non-existent note raises error."""
63
+ manager = VaultManager(sample_vault)
64
+
65
+ with pytest.raises(FileNotFoundError):
66
+ manager.read("nonexistent.md")
67
+
68
+ def test_backlinks(self, sample_vault):
69
+ """Test finding backlinks."""
70
+ manager = VaultManager(sample_vault)
71
+ backlinks = manager.backlinks("10-PROJECTS/test-project.md")
72
+
73
+ # causal-inference.md links to test-project
74
+ assert len(backlinks) >= 1
75
+
76
+ def test_orphans(self, sample_vault):
77
+ """Test finding orphan notes."""
78
+ manager = VaultManager(sample_vault)
79
+
80
+ # Create an orphan note
81
+ orphan = sample_vault / "orphan-note.md"
82
+ orphan.write_text("# Orphan\n\nThis note has no links.")
83
+
84
+ orphans = manager.orphans()
85
+
86
+ assert "orphan-note.md" in orphans
87
+
88
+ def test_graph_generation(self, sample_vault):
89
+ """Test generating graph data."""
90
+ manager = VaultManager(sample_vault)
91
+ graph = manager.graph()
92
+
93
+ assert "nodes" in graph
94
+ assert "edges" in graph
95
+ assert len(graph["nodes"]) >= 2
96
+
97
+ # Check node structure
98
+ node = graph["nodes"][0]
99
+ assert "id" in node
100
+ assert "label" in node
101
+ assert "path" in node
102
+ assert "connections" in node
103
+
104
+ def test_graph_with_limit(self, sample_vault):
105
+ """Test graph generation with node limit."""
106
+ manager = VaultManager(sample_vault)
107
+ graph = manager.graph(limit=1)
108
+
109
+ assert len(graph["nodes"]) <= 1
110
+
111
+ def test_graph_with_tags(self, sample_vault):
112
+ """Test graph generation including tags."""
113
+ manager = VaultManager(sample_vault)
114
+ graph = manager.graph(include_tags=True)
115
+
116
+ # May or may not have tag nodes depending on sample data
117
+ tag_nodes = [n for n in graph["nodes"] if n.get("type") == "tag"]
118
+ assert isinstance(tag_nodes, list) # Just verify structure
119
+
120
+ def test_graph_stats(self, sample_vault):
121
+ """Test graph statistics calculation."""
122
+ manager = VaultManager(sample_vault)
123
+ stats = manager.graph_stats()
124
+
125
+ assert "total_notes" in stats
126
+ assert "total_connections" in stats
127
+ assert "avg_connections" in stats
128
+ assert "most_connected" in stats
129
+ assert "density" in stats
130
+
131
+ assert stats["total_notes"] >= 2
132
+ assert isinstance(stats["avg_connections"], float)
133
+ assert isinstance(stats["most_connected"], list)
134
+
135
+ def test_write_note(self, sample_vault):
136
+ """Test writing a new note."""
137
+ manager = VaultManager(sample_vault)
138
+
139
+ content = "# New Note\n\nThis is test content."
140
+ result = manager.write("test-write.md", content)
141
+
142
+ assert result is not None
143
+ assert (sample_vault / "test-write.md").exists()
144
+
145
+ # Verify content
146
+ note = manager.read("test-write.md")
147
+ assert "test content" in note.content
148
+
149
+ def test_write_to_subdirectory(self, sample_vault):
150
+ """Test writing note to subdirectory."""
151
+ manager = VaultManager(sample_vault)
152
+
153
+ content = "# Nested Note\n\nIn a subdirectory."
154
+ result = manager.write("10-PROJECTS/nested-note.md", content)
155
+
156
+ assert result is not None
157
+ assert (sample_vault / "10-PROJECTS" / "nested-note.md").exists()
158
+
159
+ def test_search_with_limit(self, sample_vault):
160
+ """Test search with result limit."""
161
+ manager = VaultManager(sample_vault)
162
+
163
+ results = manager.search("", limit=1)
164
+ assert len(results) <= 1
165
+
166
+ def test_search_in_frontmatter(self, sample_vault):
167
+ """Test searching in frontmatter."""
168
+ manager = VaultManager(sample_vault)
169
+
170
+ results = manager.search("active")
171
+ # Should find notes with status: active in frontmatter
172
+ assert len(results) >= 1
173
+
174
+ def test_parse_note_with_links(self, sample_vault):
175
+ """Test parsing note links."""
176
+ manager = VaultManager(sample_vault)
177
+ note = manager.read("20-AREAS/causal-inference.md")
178
+
179
+ assert len(note.links) >= 1
180
+ assert "test-project" in note.links
181
+
182
+ def test_parse_note_with_tags(self, sample_vault):
183
+ """Test parsing note tags."""
184
+ manager = VaultManager(sample_vault)
185
+ note = manager.read("10-PROJECTS/test-project.md")
186
+
187
+ # Tags may or may not be present depending on content
188
+ assert isinstance(note.tags, list)
189
+
190
+ def test_parse_frontmatter(self, sample_vault):
191
+ """Test frontmatter parsing."""
192
+ manager = VaultManager(sample_vault)
193
+ note = manager.read("10-PROJECTS/test-project.md")
194
+
195
+ assert "type" in note.frontmatter
196
+ assert note.frontmatter["type"] == "project"
197
+
198
+ def test_backlinks_empty(self, sample_vault):
199
+ """Test backlinks for note with no incoming links."""
200
+ manager = VaultManager(sample_vault)
201
+
202
+ # Create isolated note
203
+ isolated = sample_vault / "isolated.md"
204
+ isolated.write_text("# Isolated\n\nNo one links here.")
205
+
206
+ backlinks = manager.backlinks("isolated.md")
207
+ assert len(backlinks) == 0
208
+
209
+ def test_recent_notes_excludes_system(self, sample_vault):
210
+ """Test that recent notes excludes system folders."""
211
+ manager = VaultManager(sample_vault)
212
+
213
+ # Create a system note
214
+ system_dir = sample_vault / "_SYSTEM"
215
+ system_dir.mkdir(exist_ok=True)
216
+ (system_dir / "config.md").write_text("# System\n\nConfig file.")
217
+
218
+ recent = manager.recent(limit=10)
219
+
220
+ # Should not include _SYSTEM files
221
+ assert not any("_SYSTEM" in path for path in recent)
222
+ assert not any(".obsidian" in path for path in recent)
223
+
224
+
225
+ class TestVaultMissing:
226
+ """Operations on a non-existent vault should degrade gracefully."""
227
+
228
+ @pytest.fixture
229
+ def missing(self, temp_dir):
230
+ return VaultManager(temp_dir / "no-such-vault")
231
+
232
+ def test_note_count_zero(self, missing):
233
+ assert missing.note_count() == 0
234
+
235
+ def test_search_empty(self, missing):
236
+ assert missing.search("anything") == []
237
+
238
+ def test_search_files_empty(self, missing):
239
+ assert missing.search_files("anything") == []
240
+
241
+ def test_recent_empty(self, missing):
242
+ assert missing.recent() == []
243
+
244
+ def test_orphans_empty(self, missing):
245
+ assert missing.orphans() == []
246
+
247
+ def test_graph_empty(self, missing):
248
+ assert missing.graph() == {"nodes": [], "edges": []}
249
+
250
+ def test_graph_stats_empty(self, missing):
251
+ stats = missing.graph_stats()
252
+ assert stats["total_notes"] == 0
253
+ assert stats["total_connections"] == 0
254
+ assert stats["most_connected"] == []
255
+
256
+
257
+ class TestVaultSearchFallback:
258
+ """Search fallback and error handling when ripgrep misbehaves."""
259
+
260
+ def test_fallback_search_direct(self, sample_vault):
261
+ manager = VaultManager(sample_vault)
262
+ results = manager._fallback_search("mediation", limit=10)
263
+ assert len(results) >= 1
264
+ assert any("mediation" in r.content.lower() for r in results)
265
+
266
+ def test_fallback_search_respects_limit(self, sample_vault):
267
+ manager = VaultManager(sample_vault)
268
+ results = manager._fallback_search("e", limit=2)
269
+ assert len(results) <= 2
270
+
271
+ def test_search_uses_fallback_when_rg_missing(self, sample_vault):
272
+ manager = VaultManager(sample_vault)
273
+ with patch("subprocess.run", side_effect=FileNotFoundError):
274
+ results = manager.search("mediation")
275
+ assert len(results) >= 1
276
+
277
+ def test_search_returns_empty_on_timeout(self, sample_vault):
278
+ manager = VaultManager(sample_vault)
279
+ with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd="rg", timeout=30)):
280
+ assert manager.search("mediation") == []
281
+
282
+ def test_search_skips_malformed_json(self, sample_vault):
283
+ manager = VaultManager(sample_vault)
284
+ fake = subprocess.CompletedProcess(
285
+ args=["rg"], returncode=0, stdout='not json\n\n{"type":"other"}\n', stderr=""
286
+ )
287
+ with patch("subprocess.run", return_value=fake):
288
+ # Malformed/non-match lines are skipped, yielding no results
289
+ assert manager.search("mediation") == []
290
+
291
+ def test_search_files_matches_name(self, sample_vault):
292
+ manager = VaultManager(sample_vault)
293
+ matches = manager.search_files("causal")
294
+ assert any("causal-inference" in m for m in matches)
295
+
296
+ def test_search_files_respects_limit(self, sample_vault):
297
+ manager = VaultManager(sample_vault)
298
+ matches = manager.search_files(r"\.md", limit=1)
299
+ assert len(matches) <= 1
300
+
301
+
302
+ class TestVaultFrontmatterParsing:
303
+ """Edge cases in frontmatter parsing."""
304
+
305
+ def test_read_invalid_yaml_frontmatter(self, sample_vault):
306
+ manager = VaultManager(sample_vault)
307
+ # Unparseable YAML between --- fences: frontmatter falls back to {}
308
+ (sample_vault / "broken-fm.md").write_text("---\n: : bad: [unclosed\n---\n\n# Body\n")
309
+ note = manager.read("broken-fm.md")
310
+ assert note.frontmatter == {}
311
+ assert "Body" in note.content
312
+
313
+
314
+ class TestVaultTemplates:
315
+ """Template loading and note creation from templates."""
316
+
317
+ def test_list_templates_empty_without_dir(self, sample_vault):
318
+ manager = VaultManager(sample_vault)
319
+ assert manager.list_templates() == []
320
+
321
+ def test_list_templates_strips_tpl_prefix(self, sample_vault):
322
+ manager = VaultManager(sample_vault)
323
+ manager.templates_path.mkdir(parents=True, exist_ok=True)
324
+ (manager.templates_path / "tpl-meeting.md").write_text("# Meeting")
325
+ (manager.templates_path / "note.md").write_text("# Note")
326
+ templates = manager.list_templates()
327
+ assert "meeting" in templates # tpl- stripped
328
+ assert "note" in templates
329
+
330
+ def test_template_creates_note_with_substitution(self, sample_vault):
331
+ manager = VaultManager(sample_vault)
332
+ manager.templates_path.mkdir(parents=True, exist_ok=True)
333
+ (manager.templates_path / "lit.md").write_text("# {{title}}\n\nBy {{author}} on {{date}}")
334
+ out = manager.template("lit", "30-RESOURCES/new-lit.md", variables={"title": "Paper", "author": "Smith"})
335
+ text = out.read_text()
336
+ assert "# Paper" in text
337
+ assert "By Smith" in text
338
+ assert date.today().isoformat() in text
339
+
340
+ def test_template_missing_raises(self, sample_vault):
341
+ manager = VaultManager(sample_vault)
342
+ with pytest.raises(FileNotFoundError):
343
+ manager.template("does-not-exist", "dest.md")
344
+
345
+ def test_load_template_via_alt_path(self, sample_vault):
346
+ manager = VaultManager(sample_vault)
347
+ # Place template in an alternate location (vault/templates/), not _SYSTEM/templates
348
+ alt = sample_vault / "templates"
349
+ alt.mkdir(parents=True, exist_ok=True)
350
+ (alt / "alt-tpl.md").write_text("# Alt template body")
351
+ loaded = manager._load_template("alt-tpl")
352
+ assert loaded is not None
353
+ assert "Alt template body" in loaded
354
+
355
+ def test_daily_note_uses_template(self, sample_vault):
356
+ manager = VaultManager(sample_vault)
357
+ manager.templates_path.mkdir(parents=True, exist_ok=True)
358
+ (manager.templates_path / "daily.md").write_text("# {{date}}\n\nWeekday: {{date:dddd}}")
359
+ target = date(2026, 6, 3)
360
+ out = manager.daily(target)
361
+ text = out.read_text()
362
+ assert "2026-06-03" in text
363
+ assert "Wednesday" in text # 2026-06-03 is a Wednesday
364
+
365
+ def test_daily_note_default_content(self, sample_vault):
366
+ manager = VaultManager(sample_vault)
367
+ target = date(2026, 6, 4)
368
+ out = manager.daily(target)
369
+ text = out.read_text()
370
+ assert "2026-06-04" in text
371
+ assert "Tasks" in text
372
+
373
+
374
+ class TestVaultTagGraph:
375
+ """Graph generation with inline #tags (tag nodes and tag edges)."""
376
+
377
+ @pytest.fixture
378
+ def tagged_vault(self, sample_vault):
379
+ # Two notes sharing an inline #stats tag so the tag node qualifies (>1 connection)
380
+ (sample_vault / "10-PROJECTS" / "a.md").write_text("# A\n\nAbout #stats and #causal methods.\n")
381
+ (sample_vault / "20-AREAS" / "b.md").write_text("# B\n\nMore #stats here, links [[a]].\n")
382
+ return sample_vault
383
+
384
+ def test_graph_includes_tag_nodes(self, tagged_vault):
385
+ manager = VaultManager(tagged_vault)
386
+ graph = manager.graph(include_tags=True)
387
+ tag_nodes = [n for n in graph["nodes"] if n.get("type") == "tag"]
388
+ # #stats appears in 2 notes -> should be a tag node
389
+ assert any(n["id"] == "tag:stats" for n in tag_nodes)
390
+
391
+ def test_graph_includes_tag_edges(self, tagged_vault):
392
+ manager = VaultManager(tagged_vault)
393
+ graph = manager.graph(include_tags=True)
394
+ tag_edges = [e for e in graph["edges"] if e.get("type") == "tag"]
395
+ assert any(e["target"] == "tag:stats" for e in tag_edges)
396
+
397
+ def test_graph_stats_counts_tags(self, tagged_vault):
398
+ manager = VaultManager(tagged_vault)
399
+ stats = manager.graph_stats()
400
+ assert stats["total_notes"] >= 2
401
+ assert "total_tags" in stats
402
+
403
+ def test_graph_skips_system_folders(self, sample_vault):
404
+ manager = VaultManager(sample_vault)
405
+ sys_dir = sample_vault / "_SYSTEM"
406
+ sys_dir.mkdir(exist_ok=True)
407
+ (sys_dir / "secret.md").write_text("# Secret\n\nShould not appear.")
408
+ node_ids = {n["id"] for n in manager.graph()["nodes"]}
409
+ assert "secret" not in node_ids
@@ -695,7 +695,7 @@ wheels = [
695
695
 
696
696
  [[package]]
697
697
  name = "nexus-cli"
698
- version = "0.5.2"
698
+ version = "0.6.1"
699
699
  source = { editable = "." }
700
700
  dependencies = [
701
701
  { name = "pydantic" },