wealthbox-cli 1.0.4__tar.gz → 1.1.1__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 (87) hide show
  1. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/PKG-INFO +27 -53
  2. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/README.md +26 -52
  3. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/mkdocs.yml +0 -1
  4. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/pyproject.toml +1 -1
  5. wealthbox_cli-1.1.1/src/wealthbox_tools/cli/_skill_bootstrap.py +253 -0
  6. wealthbox_cli-1.1.1/src/wealthbox_tools/cli/_skill_platforms.py +96 -0
  7. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/config.py +15 -3
  8. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/main.py +2 -0
  9. wealthbox_cli-1.1.1/src/wealthbox_tools/cli/skills.py +407 -0
  10. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/SKILL.md +47 -3
  11. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md +105 -0
  12. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/contacts.md +21 -0
  13. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/events.md +9 -0
  14. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/notes.md +5 -0
  15. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/opportunities.md +12 -0
  16. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/projects.md +5 -0
  17. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/tasks.md +14 -0
  18. wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/workflows.md +31 -0
  19. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/notes.md +1 -1
  20. wealthbox_cli-1.0.4/docs/skills/wealthbox-crm/firm-config.example.md +0 -111
  21. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.env.example +0 -0
  22. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.github/workflows/ci.yml +0 -0
  23. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.github/workflows/docs.yml +0 -0
  24. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.gitignore +0 -0
  25. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/CONTRIBUTING.md +0 -0
  26. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/LICENSE +0 -0
  27. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/SECURITY.md +0 -0
  28. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/changelog.md +0 -0
  29. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/cli-reference.md +0 -0
  30. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/getting-started.md +0 -0
  31. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/index.md +0 -0
  32. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/overrides/main.html +0 -0
  33. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/promotion.md +0 -0
  34. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/__init__.py +0 -0
  35. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/__init__.py +0 -0
  36. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/_config.py +0 -0
  37. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/_util.py +0 -0
  38. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/activity.py +0 -0
  39. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/categories.py +0 -0
  40. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/contacts.py +0 -0
  41. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/events.py +0 -0
  42. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/households.py +0 -0
  43. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/me.py +0 -0
  44. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/notes.py +0 -0
  45. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/opportunities.py +0 -0
  46. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/projects.py +0 -0
  47. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/tasks.py +0 -0
  48. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/users.py +0 -0
  49. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/workflows.py +0 -0
  50. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/__init__.py +0 -0
  51. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/activity.py +0 -0
  52. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/base.py +0 -0
  53. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/categories.py +0 -0
  54. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/comments.py +0 -0
  55. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/contacts.py +0 -0
  56. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/events.py +0 -0
  57. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/households.py +0 -0
  58. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/me.py +0 -0
  59. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/notes.py +0 -0
  60. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/opportunities.py +0 -0
  61. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/projects.py +0 -0
  62. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/tasks.py +0 -0
  63. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/users.py +0 -0
  64. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/workflows.py +0 -0
  65. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/__init__.py +0 -0
  66. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/activity.py +0 -0
  67. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/comments.py +0 -0
  68. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/common.py +0 -0
  69. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/contacts.py +0 -0
  70. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/custom_fields.py +0 -0
  71. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/enums.py +0 -0
  72. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/events.py +0 -0
  73. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/households.py +0 -0
  74. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/notes.py +0 -0
  75. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/opportunities.py +0 -0
  76. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/projects.py +0 -0
  77. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/tasks.py +0 -0
  78. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/workflows.py +0 -0
  79. {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/py.typed +0 -0
  80. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/contacts.md +0 -0
  81. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/events.md +0 -0
  82. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/households.md +0 -0
  83. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/lookups.md +0 -0
  84. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/opportunities.md +0 -0
  85. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/projects.md +0 -0
  86. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/tasks.md +0 -0
  87. {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/workflows.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wealthbox-cli
3
- Version: 1.0.4
3
+ Version: 1.1.1
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/
@@ -123,83 +123,57 @@ For the full command list, see the [CLI Reference](https://massive-value.github.
123
123
 
124
124
  ## Use with AI Coding Agents
125
125
 
126
- wealthbox-cli ships with a [Claude Code](https://claude.ai/download) skill that lets AI agents manage your CRM through natural language. Instead of memorizing CLI flags, just describe what you want:
126
+ wealthbox-cli ships with a skill for [Claude Code](https://claude.ai/download) and [Codex](https://openai.com/codex) that lets AI agents manage your CRM through natural language. Instead of memorizing CLI flags, just describe what you want:
127
127
 
128
128
  ```
129
- /wealthbox-crm create a contact for Jane Doe, she's a new prospect
130
- /wealthbox-crm list my tasks due this week
131
- /wealthbox-crm add a note to contact 123 about today's meeting
132
- /wealthbox-crm find all contacts tagged "VIP" and export to CSV
129
+ create a contact for Jane Doe, she's a new prospect
130
+ list my tasks due this week
131
+ add a note to contact 123 about today's meeting
132
+ find all contacts tagged "VIP" and export to CSV
133
133
  ```
134
134
 
135
- The skill translates your intent into the correct `wbox` commands, handles flag construction, and validates inputs — making it ideal for advisors who want CRM automation without learning CLI syntax.
135
+ The skill translates your intent into the correct `wbox` commands, handles flag construction, and validates inputs — ideal for advisors who want CRM automation without learning CLI syntax.
136
136
 
137
- ### Install the skill
137
+ ### Install
138
138
 
139
- Download the skill directly from GitHub into your Claude Code skills directory:
139
+ One command, same on every OS:
140
140
 
141
- **macOS/Linux:**
142
141
  ```bash
143
- git clone --depth 1 https://github.com/massive-value/wealthbox-cli.git /tmp/wealthbox-cli \
144
- && cp -r /tmp/wealthbox-cli/docs/skills/wealthbox-crm ~/.claude/skills/wealthbox-crm \
145
- && rm -rf /tmp/wealthbox-cli
142
+ pip install wealthbox-cli
143
+ wbox config set-token # paste your Wealthbox API token
144
+ wbox skills install # interactive: pick Claude Code, Codex, or both
146
145
  ```
147
146
 
148
- **Windows (PowerShell):**
149
- ```powershell
150
- git clone --depth 1 https://github.com/massive-value/wealthbox-cli.git $env:TEMP\wealthbox-cli
151
- Copy-Item -Recurse $env:TEMP\wealthbox-cli\docs\skills\wealthbox-crm $env:USERPROFILE\.claude\skills\wealthbox-crm
152
- Remove-Item -Recurse -Force $env:TEMP\wealthbox-cli
153
- ```
147
+ The installer asks which platforms to target, copies the skill into the right directory (`~/.claude/skills/` for Claude Code, `~/.codex/skills/` for Codex — with `SKILL.md` renamed to `AGENTS.md` automatically), and offers to bootstrap your firm's customizations from the Wealthbox API.
154
148
 
155
- **Windows (Command Prompt):**
156
- ```cmd
157
- git clone --depth 1 https://github.com/massive-value/wealthbox-cli.git %TEMP%\wealthbox-cli
158
- xcopy /E /I %TEMP%\wealthbox-cli\docs\skills\wealthbox-crm %USERPROFILE%\.claude\skills\wealthbox-crm
159
- rmdir /S /Q %TEMP%\wealthbox-cli
160
- ```
149
+ ### First agent run bootstraps itself
161
150
 
162
- If you already have the repo cloned, just copy from your local checkout:
151
+ The first time an agent invokes the skill, it walks you through populating firm-specific conventions — category values, custom fields, users, defaults, required fields, household naming, named workflows. No further CLI commands needed. After that first run, the bootstrap artifacts self-delete from the skill.
163
152
 
164
- **macOS/Linux:**
165
- ```bash
166
- cp -r docs/skills/wealthbox-crm ~/.claude/skills/wealthbox-crm
167
- ```
153
+ ### Refresh after firm changes
168
154
 
169
- **Windows (PowerShell):**
170
- ```powershell
171
- Copy-Item -Recurse docs\skills\wealthbox-crm $env:USERPROFILE\.claude\skills\wealthbox-crm
172
- ```
155
+ If your firm adds or renames category values, custom fields, or users:
173
156
 
174
- **Windows (Command Prompt):**
175
- ```cmd
176
- xcopy /E /I docs\skills\wealthbox-crm %USERPROFILE%\.claude\skills\wealthbox-crm
157
+ ```bash
158
+ wbox skills refresh
177
159
  ```
178
160
 
179
- ### Firm-specific configuration
161
+ This updates the generated half of the skill's firm files; hand-edited policy is preserved.
180
162
 
181
- Customize the skill for your firm's defaults, required fields, and naming conventions:
163
+ ### Other commands
182
164
 
183
- **macOS/Linux:**
184
165
  ```bash
185
- cp ~/.claude/skills/wealthbox-crm/firm-config.example.md ~/.claude/skills/wealthbox-crm/firm-config.md
186
- ```
187
-
188
- **Windows (PowerShell):**
189
- ```powershell
190
- Copy-Item $env:USERPROFILE\.claude\skills\wealthbox-crm\firm-config.example.md $env:USERPROFILE\.claude\skills\wealthbox-crm\firm-config.md
191
- ```
192
-
193
- **Windows (Command Prompt):**
194
- ```cmd
195
- copy %USERPROFILE%\.claude\skills\wealthbox-crm\firm-config.example.md %USERPROFILE%\.claude\skills\wealthbox-crm\firm-config.md
166
+ wbox skills list # show where the skill is installed + last bootstrap time
167
+ wbox skills doctor # diagnose install state + token
168
+ wbox skills sync # copy firm/ from one install to others (Claude Code <-> Codex)
169
+ wbox skills uninstall # remove the skill
196
170
  ```
197
171
 
198
- Edit `firm-config.md` with your firm's conventions. The agent will then apply them automatically — for example, always tagging new contacts with your firm name, setting default contact types, or running multi-step onboarding workflows.
172
+ `wbox skills sync` is useful if you maintain firm conventions in one platform's install (e.g., your Claude Code user scope) and want the same `firm/` files mirrored to Codex or to a project-scoped `.claude/skills/` folder. The command runs as a wizard by default; pass `--source`, `--target`, and `--yes` for non-interactive use.
199
173
 
200
174
  ### Works with other agents too
201
175
 
202
- The `wbox` CLI is a standard command-line tool. Any AI coding agent that can execute shell commands — [Claude Code](https://claude.ai/download), [GitHub Copilot CLI](https://githubnext.com/projects/copilot-cli), [Cursor](https://cursor.sh/), or custom agent frameworks — can use it to read and write Wealthbox data. The included skill just makes Claude Code aware of the full command surface.
176
+ The `wbox` CLI is a standard command-line tool. Any AI coding agent that can execute shell commands — [Claude Code](https://claude.ai/download), [GitHub Copilot CLI](https://githubnext.com/projects/copilot-cli), [Cursor](https://cursor.sh/), or custom agent frameworks — can use it to read and write Wealthbox data. The bundled skill just makes Claude Code and Codex aware of the full command surface.
203
177
 
204
178
  ------------------------------------------------------------------------
205
179
 
@@ -82,83 +82,57 @@ For the full command list, see the [CLI Reference](https://massive-value.github.
82
82
 
83
83
  ## Use with AI Coding Agents
84
84
 
85
- wealthbox-cli ships with a [Claude Code](https://claude.ai/download) skill that lets AI agents manage your CRM through natural language. Instead of memorizing CLI flags, just describe what you want:
85
+ wealthbox-cli ships with a skill for [Claude Code](https://claude.ai/download) and [Codex](https://openai.com/codex) that lets AI agents manage your CRM through natural language. Instead of memorizing CLI flags, just describe what you want:
86
86
 
87
87
  ```
88
- /wealthbox-crm create a contact for Jane Doe, she's a new prospect
89
- /wealthbox-crm list my tasks due this week
90
- /wealthbox-crm add a note to contact 123 about today's meeting
91
- /wealthbox-crm find all contacts tagged "VIP" and export to CSV
88
+ create a contact for Jane Doe, she's a new prospect
89
+ list my tasks due this week
90
+ add a note to contact 123 about today's meeting
91
+ find all contacts tagged "VIP" and export to CSV
92
92
  ```
93
93
 
94
- The skill translates your intent into the correct `wbox` commands, handles flag construction, and validates inputs — making it ideal for advisors who want CRM automation without learning CLI syntax.
94
+ The skill translates your intent into the correct `wbox` commands, handles flag construction, and validates inputs — ideal for advisors who want CRM automation without learning CLI syntax.
95
95
 
96
- ### Install the skill
96
+ ### Install
97
97
 
98
- Download the skill directly from GitHub into your Claude Code skills directory:
98
+ One command, same on every OS:
99
99
 
100
- **macOS/Linux:**
101
100
  ```bash
102
- git clone --depth 1 https://github.com/massive-value/wealthbox-cli.git /tmp/wealthbox-cli \
103
- && cp -r /tmp/wealthbox-cli/docs/skills/wealthbox-crm ~/.claude/skills/wealthbox-crm \
104
- && rm -rf /tmp/wealthbox-cli
101
+ pip install wealthbox-cli
102
+ wbox config set-token # paste your Wealthbox API token
103
+ wbox skills install # interactive: pick Claude Code, Codex, or both
105
104
  ```
106
105
 
107
- **Windows (PowerShell):**
108
- ```powershell
109
- git clone --depth 1 https://github.com/massive-value/wealthbox-cli.git $env:TEMP\wealthbox-cli
110
- Copy-Item -Recurse $env:TEMP\wealthbox-cli\docs\skills\wealthbox-crm $env:USERPROFILE\.claude\skills\wealthbox-crm
111
- Remove-Item -Recurse -Force $env:TEMP\wealthbox-cli
112
- ```
106
+ The installer asks which platforms to target, copies the skill into the right directory (`~/.claude/skills/` for Claude Code, `~/.codex/skills/` for Codex — with `SKILL.md` renamed to `AGENTS.md` automatically), and offers to bootstrap your firm's customizations from the Wealthbox API.
113
107
 
114
- **Windows (Command Prompt):**
115
- ```cmd
116
- git clone --depth 1 https://github.com/massive-value/wealthbox-cli.git %TEMP%\wealthbox-cli
117
- xcopy /E /I %TEMP%\wealthbox-cli\docs\skills\wealthbox-crm %USERPROFILE%\.claude\skills\wealthbox-crm
118
- rmdir /S /Q %TEMP%\wealthbox-cli
119
- ```
108
+ ### First agent run bootstraps itself
120
109
 
121
- If you already have the repo cloned, just copy from your local checkout:
110
+ The first time an agent invokes the skill, it walks you through populating firm-specific conventions — category values, custom fields, users, defaults, required fields, household naming, named workflows. No further CLI commands needed. After that first run, the bootstrap artifacts self-delete from the skill.
122
111
 
123
- **macOS/Linux:**
124
- ```bash
125
- cp -r docs/skills/wealthbox-crm ~/.claude/skills/wealthbox-crm
126
- ```
112
+ ### Refresh after firm changes
127
113
 
128
- **Windows (PowerShell):**
129
- ```powershell
130
- Copy-Item -Recurse docs\skills\wealthbox-crm $env:USERPROFILE\.claude\skills\wealthbox-crm
131
- ```
114
+ If your firm adds or renames category values, custom fields, or users:
132
115
 
133
- **Windows (Command Prompt):**
134
- ```cmd
135
- xcopy /E /I docs\skills\wealthbox-crm %USERPROFILE%\.claude\skills\wealthbox-crm
116
+ ```bash
117
+ wbox skills refresh
136
118
  ```
137
119
 
138
- ### Firm-specific configuration
120
+ This updates the generated half of the skill's firm files; hand-edited policy is preserved.
139
121
 
140
- Customize the skill for your firm's defaults, required fields, and naming conventions:
122
+ ### Other commands
141
123
 
142
- **macOS/Linux:**
143
124
  ```bash
144
- cp ~/.claude/skills/wealthbox-crm/firm-config.example.md ~/.claude/skills/wealthbox-crm/firm-config.md
145
- ```
146
-
147
- **Windows (PowerShell):**
148
- ```powershell
149
- Copy-Item $env:USERPROFILE\.claude\skills\wealthbox-crm\firm-config.example.md $env:USERPROFILE\.claude\skills\wealthbox-crm\firm-config.md
150
- ```
151
-
152
- **Windows (Command Prompt):**
153
- ```cmd
154
- copy %USERPROFILE%\.claude\skills\wealthbox-crm\firm-config.example.md %USERPROFILE%\.claude\skills\wealthbox-crm\firm-config.md
125
+ wbox skills list # show where the skill is installed + last bootstrap time
126
+ wbox skills doctor # diagnose install state + token
127
+ wbox skills sync # copy firm/ from one install to others (Claude Code <-> Codex)
128
+ wbox skills uninstall # remove the skill
155
129
  ```
156
130
 
157
- Edit `firm-config.md` with your firm's conventions. The agent will then apply them automatically — for example, always tagging new contacts with your firm name, setting default contact types, or running multi-step onboarding workflows.
131
+ `wbox skills sync` is useful if you maintain firm conventions in one platform's install (e.g., your Claude Code user scope) and want the same `firm/` files mirrored to Codex or to a project-scoped `.claude/skills/` folder. The command runs as a wizard by default; pass `--source`, `--target`, and `--yes` for non-interactive use.
158
132
 
159
133
  ### Works with other agents too
160
134
 
161
- The `wbox` CLI is a standard command-line tool. Any AI coding agent that can execute shell commands — [Claude Code](https://claude.ai/download), [GitHub Copilot CLI](https://githubnext.com/projects/copilot-cli), [Cursor](https://cursor.sh/), or custom agent frameworks — can use it to read and write Wealthbox data. The included skill just makes Claude Code aware of the full command surface.
135
+ The `wbox` CLI is a standard command-line tool. Any AI coding agent that can execute shell commands — [Claude Code](https://claude.ai/download), [GitHub Copilot CLI](https://githubnext.com/projects/copilot-cli), [Cursor](https://cursor.sh/), or custom agent frameworks — can use it to read and write Wealthbox data. The bundled skill just makes Claude Code and Codex aware of the full command surface.
162
136
 
163
137
  ------------------------------------------------------------------------
164
138
 
@@ -40,7 +40,6 @@ plugins:
40
40
 
41
41
  exclude_docs: |
42
42
  promotion.md
43
- skills/
44
43
 
45
44
  markdown_extensions:
46
45
  - attr_list
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wealthbox-cli"
7
- version = "1.0.4"
7
+ version = "1.1.1"
8
8
  description = "CLI and client library for the Wealthbox CRM API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def _escape(s: str) -> str:
10
+ """Escape characters that would break a markdown table cell."""
11
+ return s.replace("|", "\\|").replace("\n", " ")
12
+
13
+
14
+ def _md_table(headers: list[str], rows: list[list[str]]) -> str:
15
+ if not rows:
16
+ return "(no values)\n"
17
+ out = ["| " + " | ".join(headers) + " |"]
18
+ out.append("| " + " | ".join("---" for _ in headers) + " |")
19
+ for row in rows:
20
+ out.append("| " + " | ".join(_escape(str(c)) for c in row) + " |")
21
+ return "\n".join(out) + "\n"
22
+
23
+
24
+ def render_categories_md(fetched: dict[str, list[dict[str, Any]]]) -> str:
25
+ """Render every category type + its values as one markdown doc."""
26
+ sections = [
27
+ "# Firm Categories\n",
28
+ "_Generated by `wbox skills bootstrap`. Refresh with `wbox skills refresh`._\n",
29
+ ]
30
+ for type_name in sorted(fetched):
31
+ sections.append(f"## {type_name}\n")
32
+ values = fetched[type_name]
33
+ rows = [[str(v.get("id", "")), str(v.get("name", ""))] for v in values]
34
+ sections.append(_md_table(["id", "name"], rows))
35
+ return "\n".join(sections)
36
+
37
+
38
+ def render_custom_fields_md(fetched: dict[str, list[dict[str, Any]]]) -> str:
39
+ """Render custom fields grouped by document type with valid options inline."""
40
+ sections = [
41
+ "# Firm Custom Fields\n",
42
+ "_Generated by `wbox skills bootstrap`. Refresh with `wbox skills refresh`._\n",
43
+ ]
44
+ for doc_type in sorted(fetched):
45
+ sections.append(f"## {doc_type}\n")
46
+ fields = fetched[doc_type]
47
+ rows: list[list[str]] = []
48
+ for f in fields:
49
+ options = f.get("options") or []
50
+ option_names = (
51
+ ", ".join(o.get("label") or o.get("name", "") for o in options)
52
+ if options else "-"
53
+ )
54
+ rows.append(
55
+ [
56
+ str(f.get("id", "")),
57
+ str(f.get("name", "")),
58
+ str(f.get("field_type", "")),
59
+ option_names,
60
+ ]
61
+ )
62
+ sections.append(_md_table(["id", "name", "type", "options"], rows))
63
+ return "\n".join(sections)
64
+
65
+
66
+ def render_users_md(users: list[dict[str, Any]]) -> str:
67
+ sections = [
68
+ "# Firm Users\n",
69
+ "_Generated by `wbox skills bootstrap`. Refresh with `wbox skills refresh`._\n",
70
+ ]
71
+ rows = [
72
+ [str(u.get("id", "")), str(u.get("name", "")), str(u.get("email", ""))]
73
+ for u in users
74
+ ]
75
+ if not rows:
76
+ sections.append("(no users)\n")
77
+ else:
78
+ sections.append(_md_table(["id", "name", "email"], rows))
79
+ return "\n".join(sections)
80
+
81
+
82
+ # --------------------------------------------------------------------------- #
83
+ # Stubs + meta.json #
84
+ # --------------------------------------------------------------------------- #
85
+
86
+ FIRM_README = """# firm/ — Firm Customizations
87
+
88
+ This folder holds everything the agent needs to know about your firm's
89
+ Wealthbox conventions.
90
+
91
+ ## Generated files (overwritten by `wbox skills refresh`)
92
+
93
+ - `_meta.json` — refresh timestamps, firm identity, CLI version
94
+ - `categories.md` — every category type + valid values
95
+ - `custom-fields.md` — custom fields per document type + valid values
96
+ - `users.md` — user id → name → email
97
+
98
+ ## Hand-edited files (never overwritten)
99
+
100
+ - `contacts.md`, `tasks.md`, `notes.md`, `events.md`,
101
+ `opportunities.md`, `projects.md`, `workflows.md`
102
+
103
+ Each hand-edited file has a matching example at `../firm-examples/<name>`.
104
+ """
105
+
106
+
107
+ def _stub(resource: str) -> str:
108
+ return (
109
+ f"# firm/{resource} — Firm Policy\n\n"
110
+ f"_Stub — first-run bootstrap will populate this via the agent._\n\n"
111
+ f"See `firm-examples/{resource}` for a filled-in reference.\n\n"
112
+ f"TODO: capture firm policy for {resource[:-3]} here.\n"
113
+ )
114
+
115
+
116
+ STUB_CONTENTS: dict[str, str] = {
117
+ "contacts.md": _stub("contacts.md"),
118
+ "tasks.md": _stub("tasks.md"),
119
+ "notes.md": _stub("notes.md"),
120
+ "events.md": _stub("events.md"),
121
+ "opportunities.md": _stub("opportunities.md"),
122
+ "projects.md": _stub("projects.md"),
123
+ "workflows.md": _stub("workflows.md"),
124
+ }
125
+
126
+
127
+ def write_stubs(firm_dir: Path) -> list[str]:
128
+ """Write stub files that don't already exist. Returns list of names written."""
129
+ written: list[str] = []
130
+ for name, body in STUB_CONTENTS.items():
131
+ path = firm_dir / name
132
+ if path.exists():
133
+ continue
134
+ path.write_text(body, encoding="utf-8")
135
+ written.append(name)
136
+ readme = firm_dir / "_README.md"
137
+ if not readme.exists():
138
+ readme.write_text(FIRM_README, encoding="utf-8")
139
+ return written
140
+
141
+
142
+ def write_meta_json(
143
+ firm_dir: Path,
144
+ *,
145
+ generated_files: list[str],
146
+ firm_identity: dict[str, Any],
147
+ cli_version: str,
148
+ ) -> None:
149
+ now = datetime.now(timezone.utc).isoformat()
150
+ meta = {
151
+ "firm": firm_identity,
152
+ "cli_version": cli_version,
153
+ "files": {name: now for name in generated_files},
154
+ }
155
+ (firm_dir / "_meta.json").write_text(
156
+ json.dumps(meta, indent=2) + "\n", encoding="utf-8"
157
+ )
158
+
159
+
160
+ # --------------------------------------------------------------------------- #
161
+ # Orchestrator #
162
+ # --------------------------------------------------------------------------- #
163
+
164
+ import asyncio # noqa: E402
165
+ from dataclasses import dataclass # noqa: E402
166
+ from importlib.metadata import version as _pkg_version # noqa: E402
167
+
168
+ from wealthbox_tools.client import WealthboxClient # noqa: E402
169
+ from wealthbox_tools.models import CategoryListQuery, CategoryType, DocumentType # noqa: E402
170
+
171
+
172
+ @dataclass
173
+ class BootstrapResult:
174
+ skill_dir: Path
175
+ wrote_generated: list[str]
176
+ wrote_stubs: int
177
+ firm_identity: dict[str, Any]
178
+
179
+
180
+ async def _fetch_all(
181
+ client: WealthboxClient,
182
+ ) -> tuple[dict[str, list[dict[str, Any]]], dict[str, list[dict[str, Any]]], list[dict[str, Any]], dict[str, Any]]:
183
+ categories: dict[str, list[dict[str, Any]]] = {}
184
+ for ct in CategoryType:
185
+ if ct is CategoryType.CUSTOM_FIELDS:
186
+ continue
187
+ resp = await client.list_categories(ct)
188
+ categories[ct.value] = resp.get(ct.value, [])
189
+
190
+ custom_fields: dict[str, list[dict[str, Any]]] = {}
191
+ for dt in DocumentType:
192
+ resp = await client.list_categories(
193
+ CategoryType.CUSTOM_FIELDS,
194
+ CategoryListQuery(document_type=dt),
195
+ )
196
+ custom_fields[dt.value] = resp.get("custom_fields", [])
197
+
198
+ users_resp = await client.list_users()
199
+ users = users_resp.get("users", [])
200
+
201
+ me = await client.get_me()
202
+ accounts = me.get("accounts") or []
203
+ primary_account = accounts[0] if accounts else {}
204
+ firm = {
205
+ "id": primary_account.get("id"),
206
+ "name": primary_account.get("name"),
207
+ "user_id": me.get("id"),
208
+ "user_name": me.get("name"),
209
+ }
210
+ return categories, custom_fields, users, firm
211
+
212
+
213
+ def bootstrap_skill_dir(
214
+ skill_dir: Path,
215
+ *,
216
+ token: str | None,
217
+ generated_only: bool,
218
+ ) -> BootstrapResult:
219
+ firm_dir = skill_dir / "firm"
220
+ firm_dir.mkdir(parents=True, exist_ok=True)
221
+
222
+ async def _run() -> tuple[dict, dict, list, dict]:
223
+ async with WealthboxClient(token=token) as client:
224
+ return await _fetch_all(client)
225
+
226
+ categories, custom_fields, users, firm_identity = asyncio.run(_run())
227
+
228
+ (firm_dir / "categories.md").write_text(render_categories_md(categories), encoding="utf-8")
229
+ (firm_dir / "custom-fields.md").write_text(render_custom_fields_md(custom_fields), encoding="utf-8")
230
+ (firm_dir / "users.md").write_text(render_users_md(users), encoding="utf-8")
231
+
232
+ generated = ["categories.md", "custom-fields.md", "users.md"]
233
+ write_meta_json(
234
+ firm_dir,
235
+ generated_files=generated,
236
+ firm_identity=firm_identity,
237
+ cli_version=_pkg_version("wealthbox-cli"),
238
+ )
239
+
240
+ wrote_stubs = 0
241
+ if not generated_only:
242
+ wrote_stubs = len(write_stubs(firm_dir))
243
+ else:
244
+ readme = firm_dir / "_README.md"
245
+ if not readme.exists():
246
+ readme.write_text(FIRM_README, encoding="utf-8")
247
+
248
+ return BootstrapResult(
249
+ skill_dir=skill_dir,
250
+ wrote_generated=generated,
251
+ wrote_stubs=wrote_stubs,
252
+ firm_identity=firm_identity,
253
+ )
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Platform:
10
+ id: str
11
+ label: str
12
+ root_dir: Path
13
+ skill_filename: str
14
+ requires_project_cwd: bool
15
+
16
+
17
+ def _home() -> Path:
18
+ return Path.home()
19
+
20
+
21
+ def detect_platforms() -> list[Platform]:
22
+ home = _home()
23
+ cwd = Path.cwd()
24
+ return [
25
+ Platform(
26
+ id="claude-code-user",
27
+ label="Claude Code (user)",
28
+ root_dir=home / ".claude" / "skills",
29
+ skill_filename="SKILL.md",
30
+ requires_project_cwd=False,
31
+ ),
32
+ Platform(
33
+ id="claude-code-project",
34
+ label="Claude Code (current project)",
35
+ root_dir=cwd / ".claude" / "skills",
36
+ skill_filename="SKILL.md",
37
+ requires_project_cwd=True,
38
+ ),
39
+ Platform(
40
+ id="codex",
41
+ label="Codex",
42
+ root_dir=home / ".codex" / "skills",
43
+ skill_filename="AGENTS.md",
44
+ requires_project_cwd=False,
45
+ ),
46
+ ]
47
+
48
+
49
+ def skill_dir(platform: Platform) -> Path:
50
+ return platform.root_dir / "wealthbox-crm"
51
+
52
+
53
+ class SkillInstallError(Exception):
54
+ """Raised when a skill install/uninstall cannot proceed safely."""
55
+
56
+
57
+ def is_installed(platform: Platform) -> bool:
58
+ dest = skill_dir(platform)
59
+ return dest.is_dir() and (dest / platform.skill_filename).is_file()
60
+
61
+
62
+ def install_skill(platform: Platform, src: Path, *, force: bool) -> None:
63
+ dest = skill_dir(platform)
64
+ if dest.exists() and not force:
65
+ raise SkillInstallError(
66
+ f"Skill already installed at {dest} (use --force to overwrite)"
67
+ )
68
+ if dest.exists():
69
+ _safe_rmtree(platform, dest)
70
+ dest.parent.mkdir(parents=True, exist_ok=True)
71
+ shutil.copytree(src, dest)
72
+ if platform.skill_filename != "SKILL.md":
73
+ skill_md = dest / "SKILL.md"
74
+ if skill_md.exists():
75
+ skill_md.rename(dest / platform.skill_filename)
76
+
77
+
78
+ def uninstall_skill(platform: Platform) -> None:
79
+ dest = skill_dir(platform)
80
+ if not dest.exists():
81
+ return
82
+ _safe_rmtree(platform, dest)
83
+
84
+
85
+ def _safe_rmtree(platform: Platform, target: Path) -> None:
86
+ root = platform.root_dir.resolve()
87
+ try:
88
+ resolved = target.resolve(strict=True)
89
+ except FileNotFoundError:
90
+ return
91
+ # Guard: resolved target must sit inside resolved root AND end with "wealthbox-crm".
92
+ if resolved.name != "wealthbox-crm" or root not in resolved.parents:
93
+ raise SkillInstallError(
94
+ f"refusing to remove {resolved}: not a standard skill path under {root}"
95
+ )
96
+ shutil.rmtree(resolved)
@@ -11,13 +11,25 @@ app = typer.Typer(
11
11
  )
12
12
 
13
13
 
14
- @app.command("set-token", help="Store your Wealthbox API token.")
14
+ @app.command(
15
+ "set-token",
16
+ help=(
17
+ "Store your Wealthbox API token. Get one at https://dev.wealthbox.com "
18
+ "(Settings -> API Access -> Access Tokens)."
19
+ ),
20
+ )
15
21
  def set_token(
16
- token: str = typer.Option(
17
- ..., prompt="Wealthbox API token", hide_input=True,
22
+ token: str | None = typer.Option(
23
+ None, "--token",
18
24
  help="API token (will prompt if not provided)",
19
25
  ),
20
26
  ) -> None:
27
+ if token is None:
28
+ typer.echo(
29
+ "Find your token at https://dev.wealthbox.com "
30
+ "(Settings -> API Access -> Access Tokens)"
31
+ )
32
+ token = typer.prompt("Wealthbox API token", hide_input=True)
21
33
  config = load_config()
22
34
  config["token"] = token
23
35
  save_config(config)
@@ -14,6 +14,7 @@ from .me import app as me_app
14
14
  from .notes import app as notes_app
15
15
  from .opportunities import app as opportunities_app
16
16
  from .projects import app as projects_app
17
+ from .skills import app as skills_app
17
18
  from .tasks import app as tasks_app
18
19
  from .users import app as users_app
19
20
  from .workflows import app as workflows_app
@@ -44,6 +45,7 @@ app.add_typer(me_app, name="me")
44
45
  app.add_typer(notes_app, name="notes")
45
46
  app.add_typer(opportunities_app, name="opportunities")
46
47
  app.add_typer(projects_app, name="projects")
48
+ app.add_typer(skills_app, name="skills")
47
49
  app.add_typer(tasks_app, name="tasks")
48
50
  app.add_typer(users_app, name="users")
49
51
  app.add_typer(workflows_app, name="workflows")