wealthbox-cli 1.3.0__tar.gz → 1.4.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 (127) hide show
  1. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/.claude-plugin/marketplace.json +1 -1
  2. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/.codex-plugin/plugin.json +1 -1
  3. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/PKG-INFO +1 -1
  4. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/.claude-plugin/plugin.json +1 -1
  5. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/pyproject.toml +1 -1
  6. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/_skill_platforms.py +68 -13
  7. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/_util.py +26 -0
  8. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/doctor.py +5 -1
  9. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/skills.py +4 -2
  10. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/tasks.py +28 -7
  11. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/tasks.md +4 -1
  12. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/.env.example +0 -0
  13. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/.github/workflows/ci.yml +0 -0
  14. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/.github/workflows/docs.yml +0 -0
  15. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/.gitignore +0 -0
  16. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/CONTRIBUTING.md +0 -0
  17. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/LICENSE +0 -0
  18. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/README.md +0 -0
  19. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/SECURITY.md +0 -0
  20. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/SKILL.md +0 -0
  21. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/bootstrap.md +0 -0
  22. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/contacts.md +0 -0
  23. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/events.md +0 -0
  24. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/notes.md +0 -0
  25. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/opportunities.md +0 -0
  26. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/projects.md +0 -0
  27. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/tasks.md +0 -0
  28. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/firm-examples/workflows.md +0 -0
  29. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/contacts.md +0 -0
  30. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/events.md +0 -0
  31. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/households.md +0 -0
  32. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/lookups.md +0 -0
  33. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/notes.md +0 -0
  34. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/opportunities.md +0 -0
  35. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/projects.md +0 -0
  36. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/tasks.md +0 -0
  37. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/codex-skill/wealthbox-crm/references/workflows.md +0 -0
  38. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/docs/changelog.md +0 -0
  39. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/docs/cli-reference.md +0 -0
  40. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/docs/getting-started.md +0 -0
  41. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/docs/index.md +0 -0
  42. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/docs/overrides/main.html +0 -0
  43. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/docs/promotion.md +0 -0
  44. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/mkdocs.yml +0 -0
  45. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/SKILL.md +0 -0
  46. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/bootstrap.md +0 -0
  47. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/contacts.md +0 -0
  48. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/events.md +0 -0
  49. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/notes.md +0 -0
  50. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/opportunities.md +0 -0
  51. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/projects.md +0 -0
  52. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/tasks.md +0 -0
  53. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/firm-examples/workflows.md +0 -0
  54. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/contacts.md +0 -0
  55. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/events.md +0 -0
  56. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/households.md +0 -0
  57. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/lookups.md +0 -0
  58. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/notes.md +0 -0
  59. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/opportunities.md +0 -0
  60. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/projects.md +0 -0
  61. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/tasks.md +0 -0
  62. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/plugins/wealthbox-crm/skills/wealthbox-crm/references/workflows.md +0 -0
  63. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/__init__.py +0 -0
  64. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/__init__.py +0 -0
  65. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/_config.py +0 -0
  66. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/_skill_bootstrap.py +0 -0
  67. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/_skill_paths.py +0 -0
  68. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/activity.py +0 -0
  69. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/categories.py +0 -0
  70. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/config.py +0 -0
  71. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/contacts.py +0 -0
  72. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/events.py +0 -0
  73. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/households.py +0 -0
  74. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/main.py +0 -0
  75. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/me.py +0 -0
  76. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/notes.py +0 -0
  77. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/opportunities.py +0 -0
  78. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/projects.py +0 -0
  79. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/users.py +0 -0
  80. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/cli/workflows.py +0 -0
  81. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/__init__.py +0 -0
  82. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/activity.py +0 -0
  83. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/base.py +0 -0
  84. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/categories.py +0 -0
  85. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/comments.py +0 -0
  86. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/contacts.py +0 -0
  87. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/events.py +0 -0
  88. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/households.py +0 -0
  89. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/me.py +0 -0
  90. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/notes.py +0 -0
  91. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/opportunities.py +0 -0
  92. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/projects.py +0 -0
  93. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/tasks.py +0 -0
  94. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/users.py +0 -0
  95. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/client/workflows.py +0 -0
  96. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/__init__.py +0 -0
  97. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/activity.py +0 -0
  98. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/comments.py +0 -0
  99. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/common.py +0 -0
  100. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/contacts.py +0 -0
  101. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/custom_fields.py +0 -0
  102. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/enums.py +0 -0
  103. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/events.py +0 -0
  104. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/households.py +0 -0
  105. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/notes.py +0 -0
  106. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/opportunities.py +0 -0
  107. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/projects.py +0 -0
  108. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/tasks.py +0 -0
  109. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/models/workflows.py +0 -0
  110. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/py.typed +0 -0
  111. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/SKILL.md +0 -0
  112. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md +0 -0
  113. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/contacts.md +0 -0
  114. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/events.md +0 -0
  115. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/notes.md +0 -0
  116. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/opportunities.md +0 -0
  117. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/projects.md +0 -0
  118. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/tasks.md +0 -0
  119. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/workflows.md +0 -0
  120. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/contacts.md +0 -0
  121. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/events.md +0 -0
  122. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/households.md +0 -0
  123. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/lookups.md +0 -0
  124. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/notes.md +0 -0
  125. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/opportunities.md +0 -0
  126. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/projects.md +0 -0
  127. {wealthbox_cli-1.3.0 → wealthbox_cli-1.4.0}/src/wealthbox_tools/skills/wealthbox-crm/references/workflows.md +0 -0
@@ -10,7 +10,7 @@
10
10
  "name": "wealthbox-crm",
11
11
  "source": "./plugins/wealthbox-crm",
12
12
  "description": "Wealthbox CRM agent skill — natural-language CRM automation backed by the `wbox` CLI.",
13
- "version": "1.2.0",
13
+ "version": "1.3.0",
14
14
  "category": "productivity",
15
15
  "tags": [
16
16
  "wealthbox",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wealthbox-crm",
3
3
  "description": "Wealthbox CRM agent skill — drive contacts, tasks, notes, events, and households via natural language. Bundles SKILL.md, references, and firm-bootstrap workflow. Requires the `wbox` CLI (auto-install instructions in SKILL.md).",
4
- "version": "1.2.0",
4
+ "version": "1.4.0",
5
5
  "author": "Kadin Bullock",
6
6
  "homepage": "https://github.com/massive-value/wealthbox-cli",
7
7
  "repository": "https://github.com/massive-value/wealthbox-cli",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wealthbox-cli
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: CLI and client library for the Wealthbox CRM API
5
5
  Project-URL: Homepage, https://github.com/massive-value/wealthbox-cli
6
6
  Project-URL: Documentation, https://massive-value.github.io/wealthbox-cli/
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wealthbox-crm",
3
3
  "description": "Wealthbox CRM agent skill — drive contacts, tasks, notes, events, and households via natural language. Bundles SKILL.md, references, and firm-bootstrap workflow. Requires the `wbox` CLI (auto-install instructions in SKILL.md).",
4
- "version": "1.2.0",
4
+ "version": "1.4.0",
5
5
  "author": {
6
6
  "name": "Kadin Bullock"
7
7
  },
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wealthbox-cli"
7
- version = "1.3.0"
7
+ version = "1.4.0"
8
8
  description = "CLI and client library for the Wealthbox CRM API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  import shutil
4
5
  from dataclasses import dataclass
5
6
  from pathlib import Path
@@ -23,18 +24,59 @@ class PluginInstall:
23
24
  These are managed by the host CLI (`claude plugin install ...` or the
24
25
  Codex equivalent) — `wbox skills install` does not write here, and
25
26
  `wbox skills uninstall` does not touch them.
27
+
28
+ `active` is True when the host's `installed_plugins.json` has a record
29
+ pointing at this version's installPath. Old/cached versions kept around
30
+ by the host for rollback report active=False. If the host has no
31
+ `installed_plugins.json` (e.g., older Claude Code, or a Codex setup that
32
+ hasn't shipped one yet), `active` defaults to True since we can't tell.
26
33
  """
27
34
  host: str # 'claude-code-user' | 'claude-code-project' | 'codex'
28
35
  marketplace: str
29
36
  version: str
30
37
  skill_dir: Path
38
+ active: bool = True
31
39
 
32
40
 
33
41
  def _home() -> Path:
34
42
  return Path.home()
35
43
 
36
44
 
37
- def _scan_plugin_cache(cache_root: Path, host: str) -> list[PluginInstall]:
45
+ def _read_active_install_paths(plugins_root: Path) -> set[Path] | None:
46
+ """Read <plugins_root>/installed_plugins.json and return the set of
47
+ install-path roots the host considers active across every scope.
48
+
49
+ Returns None when no state file exists — caller should treat every
50
+ discovered plugin as active in that case (we don't have enough info
51
+ to differentiate cached vs active).
52
+ """
53
+ state_file = plugins_root / "installed_plugins.json"
54
+ if not state_file.is_file():
55
+ return None
56
+ try:
57
+ data = json.loads(state_file.read_text(encoding="utf-8"))
58
+ except (json.JSONDecodeError, OSError):
59
+ return None
60
+ active: set[Path] = set()
61
+ for entries in (data.get("plugins") or {}).values():
62
+ if not isinstance(entries, list):
63
+ continue
64
+ for entry in entries:
65
+ install_path = entry.get("installPath")
66
+ if not install_path:
67
+ continue
68
+ try:
69
+ active.add(Path(install_path).resolve())
70
+ except OSError:
71
+ active.add(Path(install_path))
72
+ return active
73
+
74
+
75
+ def _scan_plugin_cache(
76
+ cache_root: Path,
77
+ host: str,
78
+ active_paths: set[Path] | None,
79
+ ) -> list[PluginInstall]:
38
80
  """Find any `<marketplace>/<plugin-name>/<version>/skills/wealthbox-crm/`
39
81
  matches under a Claude Code- or Codex-style plugin cache root."""
40
82
  if not cache_root.is_dir():
@@ -50,13 +92,23 @@ def _scan_plugin_cache(cache_root: Path, host: str) -> list[PluginInstall]:
50
92
  if not version_dir.is_dir():
51
93
  continue
52
94
  skill_dir = version_dir / "skills" / SKILL_NAME
53
- if (skill_dir / "SKILL.md").is_file():
54
- out.append(PluginInstall(
55
- host=host,
56
- marketplace=marketplace_dir.name,
57
- version=version_dir.name,
58
- skill_dir=skill_dir,
59
- ))
95
+ if not (skill_dir / "SKILL.md").is_file():
96
+ continue
97
+ try:
98
+ version_dir_resolved = version_dir.resolve()
99
+ except OSError:
100
+ version_dir_resolved = version_dir
101
+ if active_paths is None:
102
+ is_active = True
103
+ else:
104
+ is_active = version_dir_resolved in active_paths
105
+ out.append(PluginInstall(
106
+ host=host,
107
+ marketplace=marketplace_dir.name,
108
+ version=version_dir.name,
109
+ skill_dir=skill_dir,
110
+ active=is_active,
111
+ ))
60
112
  return out
61
113
 
62
114
 
@@ -72,14 +124,17 @@ def detect_plugin_installs() -> list[PluginInstall]:
72
124
  home = _home()
73
125
  cwd = Path.cwd()
74
126
  candidates = [
75
- (home / ".claude" / "plugins" / "cache", "claude-code-user"),
76
- (cwd / ".claude" / "plugins" / "cache", "claude-code-project"),
77
- (home / ".codex" / "plugins" / "cache", "codex"),
127
+ (home / ".claude" / "plugins", "claude-code-user"),
128
+ (cwd / ".claude" / "plugins", "claude-code-project"),
129
+ (home / ".codex" / "plugins", "codex"),
78
130
  ]
79
131
  installs: list[PluginInstall] = []
80
132
  seen: set[Path] = set()
81
- for cache_root, host in candidates:
82
- for pi in _scan_plugin_cache(cache_root, host=host):
133
+ for plugins_root, host in candidates:
134
+ active_paths = _read_active_install_paths(plugins_root)
135
+ for pi in _scan_plugin_cache(
136
+ plugins_root / "cache", host=host, active_paths=active_paths,
137
+ ):
83
138
  try:
84
139
  key = pi.skill_dir.resolve()
85
140
  except OSError:
@@ -416,6 +416,32 @@ def build_resource_filter(
416
416
  return result
417
417
 
418
418
 
419
+ async def resolve_category_id(
420
+ client: WealthboxClient, category_type: CategoryType, value: str
421
+ ) -> int:
422
+ """Resolve a category name or numeric string to an integer ID.
423
+
424
+ If `value` is purely digits, returns int(value) without an API call.
425
+ Otherwise fetches all categories of the given type and case-insensitively
426
+ matches `name`. Raises typer.BadParameter on miss with available names.
427
+ """
428
+ if value.isdigit():
429
+ return int(value)
430
+ data = await client.list_all_categories(category_type)
431
+ items = data.get(category_type.value, [])
432
+ target = value.strip().casefold()
433
+ for item in items:
434
+ if str(item.get("name", "")).casefold() == target:
435
+ return int(item["id"])
436
+ available = sorted(str(i["name"]) for i in items if i.get("name"))
437
+ label = category_type.value.replace("_", " ")
438
+ if available:
439
+ raise typer.BadParameter(
440
+ f"Unknown {label} value '{value}'. Available: {', '.join(available)}"
441
+ )
442
+ raise typer.BadParameter(f"No {label} configured in this workspace.")
443
+
444
+
419
445
  def parse_more_fields(more_fields: str, reserved: set[str]) -> dict[str, Any]:
420
446
  """Parse --more-fields JSON and validate against reserved keys.
421
447
 
@@ -122,10 +122,14 @@ def run_doctor(token: str | None = None) -> int:
122
122
  if not plugin_installs:
123
123
  typer.echo(" (none detected)")
124
124
  else:
125
+ # Active first, then cached (descending version) so the live
126
+ # install is the first thing the eye lands on.
127
+ plugin_installs.sort(key=lambda p: (not p.active, p.version), reverse=False)
125
128
  for pi in plugin_installs:
129
+ status_label = "active" if pi.active else "cached"
126
130
  typer.echo(
127
131
  f" {pi.host:<22} plugin@{pi.marketplace:<18} "
128
- f"version={pi.version:<10} {pi.skill_dir}"
132
+ f"version={pi.version:<8} {status_label:<7} {pi.skill_dir}"
129
133
  )
130
134
 
131
135
  # --- Firm data --------------------------------------------------------
@@ -70,11 +70,13 @@ def list_platforms() -> None:
70
70
  "installed" if installed else "not installed",
71
71
  template_version,
72
72
  ))
73
- for pi in detect_plugin_installs():
73
+ plugin_installs = detect_plugin_installs()
74
+ plugin_installs.sort(key=lambda p: (not p.active, p.version))
75
+ for pi in plugin_installs:
74
76
  rows.append((
75
77
  f"plugin:{pi.host}",
76
78
  str(pi.skill_dir),
77
- f"plugin@{pi.marketplace}",
79
+ f"plugin@{pi.marketplace} ({'active' if pi.active else 'cached'})",
78
80
  pi.version,
79
81
  ))
80
82
 
@@ -25,6 +25,7 @@ from ._util import (
25
25
  make_resource_app,
26
26
  output_result,
27
27
  parse_more_fields,
28
+ resolve_category_id,
28
29
  run_client,
29
30
  run_client_with_comments,
30
31
  slim_comments,
@@ -47,7 +48,9 @@ _GET_JSON_FIELDS = [
47
48
  "category", "linked_to", "comments",
48
49
  ]
49
50
 
50
- _TASK_CREATE_RESERVED = {"name", "due_date", "frame", "priority", "assigned_to", "linked_to"}
51
+ _TASK_CREATE_RESERVED = {
52
+ "name", "due_date", "frame", "priority", "assigned_to", "linked_to", "category", "description",
53
+ }
51
54
 
52
55
 
53
56
  @app.command(
@@ -130,13 +133,18 @@ def add_task(
130
133
  ),
131
134
  frame: TaskFrame | None = typer.Option(None, "--frame", help="Friendly due timeframe"),
132
135
  priority: TaskPriority | None = typer.Option(None, "--priority", help="Low, Medium, or High"),
136
+ category: str | None = typer.Option(
137
+ None, "--category",
138
+ help="Task category by name or ID — see: wbox categories task-categories",
139
+ ),
140
+ description: str | None = typer.Option(None, "--description", help="Task description"),
133
141
  assigned_to: int | None = typer.Option(None, "--assigned-to", help="Assign to a user by ID"),
134
142
  contact: int | None = typer.Option(None, "--contact", help="Link to a Contact by ID"),
135
143
  project: int | None = typer.Option(None, "--project", help="Link to a Project by ID"),
136
144
  opportunity: int | None = typer.Option(None, "--opportunity", help="Link to an Opportunity by ID"),
137
145
  more_fields: str | None = typer.Option(
138
146
  None, "--more-fields",
139
- help='JSON: {"category": 123, "complete": false, "assigned_to_team": 456, "description": "..."}',
147
+ help='JSON: {"complete": false, "assigned_to_team": 456}',
140
148
  ),
141
149
  token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
142
150
  fmt: OutputFormat = typer.Option(OutputFormat.JSON, "--format"),
@@ -150,6 +158,7 @@ def add_task(
150
158
  "due_date": due_date,
151
159
  "frame": frame,
152
160
  "priority": priority,
161
+ "description": description,
153
162
  "assigned_to": assigned_to,
154
163
  "linked_to": build_linked_to(contact, project, opportunity),
155
164
  }
@@ -157,10 +166,14 @@ def add_task(
157
166
  if more_fields:
158
167
  payload.update(parse_more_fields(more_fields, _TASK_CREATE_RESERVED))
159
168
 
160
- # Strip None before model construction (due_date XOR frame validator needs clean input)
161
- input_model = TaskCreateInput(**{k: v for k, v in payload.items() if v is not None})
169
+ async def _create(client): # type: ignore[no-untyped-def]
170
+ if category is not None:
171
+ payload["category"] = await resolve_category_id(client, CategoryType.TASK_CATEGORIES, category)
172
+ # Strip None before model construction (due_date XOR frame validator needs clean input)
173
+ clean = {k: v for k, v in payload.items() if v is not None}
174
+ return await client.create_task(TaskCreateInput(**clean))
162
175
 
163
- output_result(run_client(token, lambda c: c.create_task(input_model)), fmt)
176
+ output_result(run_client(token, _create), fmt)
164
177
 
165
178
 
166
179
  @app.command("update", help="Update an existing task. Pass only the fields you want to change.")
@@ -171,6 +184,10 @@ def update_task(
171
184
  due_date: str | None = typer.Option(None, "--due-date", help="ISO 8601 datetime, e.g. '2026-04-01T09:00:00-07:00'"),
172
185
  frame: TaskFrame | None = typer.Option(None, "--frame", help="Friendly due timeframe"),
173
186
  priority: TaskPriority | None = typer.Option(None, "--priority", help="Low, Medium, or High"),
187
+ category: str | None = typer.Option(
188
+ None, "--category",
189
+ help="Task category by name or ID — see: wbox categories task-categories",
190
+ ),
174
191
  assigned_to: int | None = typer.Option(None, "--assigned-to", help="Reassign to a user by ID"),
175
192
  complete: bool | None = typer.Option(None, "--complete/--no-complete", help="Mark as complete or incomplete"),
176
193
  description: str | None = typer.Option(None, "--description"),
@@ -193,9 +210,13 @@ def update_task(
193
210
  linked = build_linked_to(contact, project, opportunity)
194
211
  if linked is not None:
195
212
  payload["linked_to"] = linked
196
- input_model = TaskUpdateInput(**payload)
197
213
 
198
- output_result(run_client(token, lambda c: c.update_task(task_id, input_model)), fmt)
214
+ async def _update(client): # type: ignore[no-untyped-def]
215
+ if category is not None:
216
+ payload["category"] = await resolve_category_id(client, CategoryType.TASK_CATEGORIES, category)
217
+ return await client.update_task(task_id, TaskUpdateInput(**payload))
218
+
219
+ output_result(run_client(token, _update), fmt)
199
220
 
200
221
 
201
222
  @app.command("delete", help="Delete a task by ID.")
@@ -46,11 +46,13 @@ wbox tasks add <NAME> [OPTIONS]
46
46
  | `--due-date` | STR | Due date (XOR with --frame) |
47
47
  | `--frame` | today\|tomorrow\|this-week\|next-week\|this-month\|next-month\|... | Relative due date (XOR with --due-date) |
48
48
  | `--priority` | Low\|Medium\|High | Priority level |
49
+ | `--category` | STR | Task category by name or ID (e.g. "Follow-up"). See `wbox categories task-categories`. |
50
+ | `--description` | STR | Task description |
49
51
  | `--assigned-to` | INT | Assign to user ID |
50
52
  | `--contact` | INT | Link to contact |
51
53
  | `--project` | INT | Link to project |
52
54
  | `--opportunity` | INT | Link to opportunity |
53
- | `--more-fields` | JSON | e.g. `{"category": 123, "description": "..."}` |
55
+ | `--more-fields` | JSON | e.g. `{"complete": false, "assigned_to_team": 456}` |
54
56
  | `--format` | json\|table\|csv\|tsv | Output format |
55
57
 
56
58
  **Note:** `--due-date` and `--frame` are mutually exclusive. Use `--frame` for relative dates.
@@ -67,6 +69,7 @@ wbox tasks update <ID> [OPTIONS]
67
69
  | `--due-date` | STR | Change due date |
68
70
  | `--frame` | STR | Change relative due date |
69
71
  | `--priority` | Low\|Medium\|High | Change priority |
72
+ | `--category` | STR | Task category by name or ID. See `wbox categories task-categories`. |
70
73
  | `--assigned-to` | INT | Reassign |
71
74
  | `--complete` / `--no-complete` | flag | Mark complete/incomplete |
72
75
  | `--description` | STR | Update description |
File without changes
File without changes
File without changes
File without changes
File without changes