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.
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/PKG-INFO +27 -53
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/README.md +26 -52
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/mkdocs.yml +0 -1
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/pyproject.toml +1 -1
- wealthbox_cli-1.1.1/src/wealthbox_tools/cli/_skill_bootstrap.py +253 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/cli/_skill_platforms.py +96 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/config.py +15 -3
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/main.py +2 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/cli/skills.py +407 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/SKILL.md +47 -3
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md +105 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/contacts.md +21 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/events.md +9 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/notes.md +5 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/opportunities.md +12 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/projects.md +5 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/tasks.md +14 -0
- wealthbox_cli-1.1.1/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/workflows.md +31 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/notes.md +1 -1
- wealthbox_cli-1.0.4/docs/skills/wealthbox-crm/firm-config.example.md +0 -111
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.env.example +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.github/workflows/ci.yml +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.github/workflows/docs.yml +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/.gitignore +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/CONTRIBUTING.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/LICENSE +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/SECURITY.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/changelog.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/cli-reference.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/getting-started.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/index.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/overrides/main.html +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/docs/promotion.md +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/__init__.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/__init__.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/_config.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/_util.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/activity.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/categories.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/contacts.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/events.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/households.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/me.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/notes.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/opportunities.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/projects.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/tasks.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/users.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/cli/workflows.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/__init__.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/activity.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/base.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/categories.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/comments.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/contacts.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/events.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/households.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/me.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/notes.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/opportunities.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/projects.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/tasks.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/users.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/client/workflows.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/__init__.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/activity.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/comments.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/common.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/contacts.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/custom_fields.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/enums.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/events.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/households.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/notes.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/opportunities.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/projects.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/tasks.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/models/workflows.py +0 -0
- {wealthbox_cli-1.0.4 → wealthbox_cli-1.1.1}/src/wealthbox_tools/py.typed +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/contacts.md +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/events.md +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/households.md +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/lookups.md +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/opportunities.md +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/projects.md +0 -0
- {wealthbox_cli-1.0.4/docs → wealthbox_cli-1.1.1/src/wealthbox_tools}/skills/wealthbox-crm/references/tasks.md +0 -0
- {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.
|
|
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)
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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 —
|
|
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
|
|
137
|
+
### Install
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
One command, same on every OS:
|
|
140
140
|
|
|
141
|
-
**macOS/Linux:**
|
|
142
141
|
```bash
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
```bash
|
|
166
|
-
cp -r docs/skills/wealthbox-crm ~/.claude/skills/wealthbox-crm
|
|
167
|
-
```
|
|
153
|
+
### Refresh after firm changes
|
|
168
154
|
|
|
169
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
xcopy /E /I docs\skills\wealthbox-crm %USERPROFILE%\.claude\skills\wealthbox-crm
|
|
157
|
+
```bash
|
|
158
|
+
wbox skills refresh
|
|
177
159
|
```
|
|
178
160
|
|
|
179
|
-
|
|
161
|
+
This updates the generated half of the skill's firm files; hand-edited policy is preserved.
|
|
180
162
|
|
|
181
|
-
|
|
163
|
+
### Other commands
|
|
182
164
|
|
|
183
|
-
**macOS/Linux:**
|
|
184
165
|
```bash
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 —
|
|
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
|
|
96
|
+
### Install
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
One command, same on every OS:
|
|
99
99
|
|
|
100
|
-
**macOS/Linux:**
|
|
101
100
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
```bash
|
|
125
|
-
cp -r docs/skills/wealthbox-crm ~/.claude/skills/wealthbox-crm
|
|
126
|
-
```
|
|
112
|
+
### Refresh after firm changes
|
|
127
113
|
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
xcopy /E /I docs\skills\wealthbox-crm %USERPROFILE%\.claude\skills\wealthbox-crm
|
|
116
|
+
```bash
|
|
117
|
+
wbox skills refresh
|
|
136
118
|
```
|
|
137
119
|
|
|
138
|
-
|
|
120
|
+
This updates the generated half of the skill's firm files; hand-edited policy is preserved.
|
|
139
121
|
|
|
140
|
-
|
|
122
|
+
### Other commands
|
|
141
123
|
|
|
142
|
-
**macOS/Linux:**
|
|
143
124
|
```bash
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -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(
|
|
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
|
-
|
|
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")
|