wealthbox-cli 2.2.0__tar.gz → 2.3.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.
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/CHANGELOG.md +16 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/PKG-INFO +22 -1
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/README.md +21 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/getting-started.md +7 -1
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/pyproject.toml +1 -1
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/_util.py +74 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/contacts.py +48 -12
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/self_cmd.py +21 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/common.py +10 -2
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/self_upgrade.py +30 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/SKILL.md +2 -0
- wealthbox_cli-2.3.0/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/contacts.md +34 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/contacts.md +59 -2
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/households.md +5 -0
- wealthbox_cli-2.2.0/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/contacts.md +0 -21
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/.github/workflows/ci.yml +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/.github/workflows/docs.yml +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/.github/workflows/release-binaries.yml +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/.gitignore +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/CONTRIBUTING.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/LICENSE +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/SECURITY.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/agents/domain.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/agents/issue-tracker.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/agents/triage-labels.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/cli-reference.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/index.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/overrides/main.html +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/docs/promotion.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/mkdocs.yml +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/__init__.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/__init__.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/_config.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/_skill_bootstrap.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/_skill_paths.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/_skill_platforms.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/activity.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/categories.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/config.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/doctor.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/events.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/firm.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/households.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/internals.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/main.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/me.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/notes.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/opportunities.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/prefs.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/projects.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/skills.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/tasks.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/users.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/cli/workflows.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/__init__.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/activity.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/base.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/categories.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/comments.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/contacts.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/events.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/households.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/me.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/notes.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/opportunities.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/projects.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/tasks.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/users.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/client/workflows.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/firm/__init__.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/firm/archive.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/firm/diff.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/internals/__init__.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/internals/skill_ref_gen.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/__init__.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/activity.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/comments.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/contacts.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/custom_fields.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/enums.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/events.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/households.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/notes.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/opportunities.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/projects.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/tasks.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/models/workflows.py +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/py.typed +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/events.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/notes.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/opportunities.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/projects.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/tasks.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/workflows.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/events.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/lookups.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/notes.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/opportunities.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/projects.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/tasks.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/references/workflows.md +0 -0
- {wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/uv.lock +0 -0
|
@@ -4,6 +4,22 @@ All notable changes to `wealthbox-cli` are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.2.1] - 2026-05-13
|
|
8
|
+
|
|
9
|
+
Bug-fix release. `wbox self upgrade` was silently no-op'ing for users who
|
|
10
|
+
installed `wbox` via `uv tool install` or `pipx`: it computed the install root
|
|
11
|
+
from `sys.executable` (the venv's `python.exe`) and tried to swap the
|
|
12
|
+
console-script shim there, but `~/.local/bin/wbox.exe` (or pipx's equivalent)
|
|
13
|
+
is a separate copy uv/pipx made — replacing the venv-side shim never changed
|
|
14
|
+
what `PATH` resolved.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `wbox self upgrade` now detects the install kind (`bundle` / `uv-tool` /
|
|
18
|
+
`pipx` / `pip`) up front. Non-bundle installs exit `1` with the correct
|
|
19
|
+
follow-up command (`uv tool upgrade wealthbox-cli`, `pipx upgrade
|
|
20
|
+
wealthbox-cli`, or `pip install --upgrade wealthbox-cli`) instead of
|
|
21
|
+
scheduling a swap that would never take effect on `PATH`.
|
|
22
|
+
|
|
7
23
|
## [2.2.0] - 2026-05-13
|
|
8
24
|
|
|
9
25
|
Small feature + correctness release. Disambiguates the two IDs returned by
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wealthbox-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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/
|
|
@@ -60,6 +60,13 @@ The installer:
|
|
|
60
60
|
2. Installs the AI agent skill into [Claude Code](https://claude.ai/download) (and Codex, if you have it).
|
|
61
61
|
3. Asks for your **Wealthbox API token**. *Where to find it:* in Wealthbox, click your initials in the top right → **Settings** → **API Access** → **Create Access Token**. Paste the token when prompted.
|
|
62
62
|
|
|
63
|
+
Re-running is idempotent: it keeps your stored token, refreshes the skill template, and leaves PATH alone if already correct. To install just the binary without the AI agent skill, on Windows download install.ps1 and run with `-SkipSkills`:
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
iwr https://raw.githubusercontent.com/massive-value/wealthbox-cli/main/scripts/install.ps1 -OutFile install.ps1
|
|
67
|
+
.\install.ps1 -SkipSkills
|
|
68
|
+
```
|
|
69
|
+
|
|
63
70
|
That's it. Open Claude Code and try one of the prompts below.
|
|
64
71
|
|
|
65
72
|
------------------------------------------------------------------------
|
|
@@ -107,6 +114,19 @@ You stay in control: the agent shows you what it's about to do before it does it
|
|
|
107
114
|
|
|
108
115
|
The installer drops a single prebuilt `wbox` binary onto your PATH (`~/.local/bin/wbox` on Mac/Linux, `%LOCALAPPDATA%\Programs\wbox\wbox.exe` on Windows), then registers the AI agent skill with Claude Code (and Codex, if it's installed). Firm data lives at `~/.config/wbox/firm/` (Mac/Linux) or `%APPDATA%\wbox\firm\` (Windows) and survives reinstalls and upgrades. Run `wbox doctor` to see your install status anytime, and `wbox skills upgrade` to pull the latest skill template.
|
|
109
116
|
|
|
117
|
+
### Upgrading
|
|
118
|
+
|
|
119
|
+
The upgrade command depends on how you installed:
|
|
120
|
+
|
|
121
|
+
| If you installed via... | Upgrade with |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `install.sh` / `install.ps1` (the one-line installer above) | `wbox self upgrade` |
|
|
124
|
+
| `pip install wealthbox-cli` | `pip install --upgrade wealthbox-cli` |
|
|
125
|
+
| `pipx install wealthbox-cli` | `pipx upgrade wealthbox-cli` |
|
|
126
|
+
| `uv tool install wealthbox-cli` | `uv tool upgrade wealthbox-cli` |
|
|
127
|
+
|
|
128
|
+
`wbox self upgrade` only swaps the standalone bundle the one-line installer puts in place; on pip/pipx/uv installs it refuses with the correct follow-up command rather than half-replacing a venv-managed shim.
|
|
129
|
+
|
|
110
130
|
------------------------------------------------------------------------
|
|
111
131
|
|
|
112
132
|
## For developers and power users
|
|
@@ -178,6 +198,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CLAUDE.md](CLAUDE.md) for the archit
|
|
|
178
198
|
<summary><b>Troubleshooting</b></summary>
|
|
179
199
|
|
|
180
200
|
- **401 Unauthorized** — token expired or wrong. Run `wbox config show` to see the masked token, `wbox config set-token` to update.
|
|
201
|
+
- **`wbox self upgrade` says "run `uv tool upgrade …`" (or pipx / pip)** — expected. `wbox self upgrade` only swaps the standalone bundle the one-line installer drops in place. If you installed via pip / pipx / uv, follow the command the gate prints. See [Upgrading](#upgrading).
|
|
181
202
|
- **Windows: "execution policy" error during install** — the installer offers to fix this for you. If you skipped it, run `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser` and re-run the installer.
|
|
182
203
|
- **Linux: command not found after install** — open a new terminal, or run `source ~/.local/bin/env` in your current shell.
|
|
183
204
|
- **Date format errors** — Wealthbox needs ISO 8601: `"2026-05-01T10:00:00-07:00"` for datetimes, `"YYYY-MM-DD"` for date-only fields.
|
|
@@ -20,6 +20,13 @@ The installer:
|
|
|
20
20
|
2. Installs the AI agent skill into [Claude Code](https://claude.ai/download) (and Codex, if you have it).
|
|
21
21
|
3. Asks for your **Wealthbox API token**. *Where to find it:* in Wealthbox, click your initials in the top right → **Settings** → **API Access** → **Create Access Token**. Paste the token when prompted.
|
|
22
22
|
|
|
23
|
+
Re-running is idempotent: it keeps your stored token, refreshes the skill template, and leaves PATH alone if already correct. To install just the binary without the AI agent skill, on Windows download install.ps1 and run with `-SkipSkills`:
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
iwr https://raw.githubusercontent.com/massive-value/wealthbox-cli/main/scripts/install.ps1 -OutFile install.ps1
|
|
27
|
+
.\install.ps1 -SkipSkills
|
|
28
|
+
```
|
|
29
|
+
|
|
23
30
|
That's it. Open Claude Code and try one of the prompts below.
|
|
24
31
|
|
|
25
32
|
------------------------------------------------------------------------
|
|
@@ -67,6 +74,19 @@ You stay in control: the agent shows you what it's about to do before it does it
|
|
|
67
74
|
|
|
68
75
|
The installer drops a single prebuilt `wbox` binary onto your PATH (`~/.local/bin/wbox` on Mac/Linux, `%LOCALAPPDATA%\Programs\wbox\wbox.exe` on Windows), then registers the AI agent skill with Claude Code (and Codex, if it's installed). Firm data lives at `~/.config/wbox/firm/` (Mac/Linux) or `%APPDATA%\wbox\firm\` (Windows) and survives reinstalls and upgrades. Run `wbox doctor` to see your install status anytime, and `wbox skills upgrade` to pull the latest skill template.
|
|
69
76
|
|
|
77
|
+
### Upgrading
|
|
78
|
+
|
|
79
|
+
The upgrade command depends on how you installed:
|
|
80
|
+
|
|
81
|
+
| If you installed via... | Upgrade with |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `install.sh` / `install.ps1` (the one-line installer above) | `wbox self upgrade` |
|
|
84
|
+
| `pip install wealthbox-cli` | `pip install --upgrade wealthbox-cli` |
|
|
85
|
+
| `pipx install wealthbox-cli` | `pipx upgrade wealthbox-cli` |
|
|
86
|
+
| `uv tool install wealthbox-cli` | `uv tool upgrade wealthbox-cli` |
|
|
87
|
+
|
|
88
|
+
`wbox self upgrade` only swaps the standalone bundle the one-line installer puts in place; on pip/pipx/uv installs it refuses with the correct follow-up command rather than half-replacing a venv-managed shim.
|
|
89
|
+
|
|
70
90
|
------------------------------------------------------------------------
|
|
71
91
|
|
|
72
92
|
## For developers and power users
|
|
@@ -138,6 +158,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) and [CLAUDE.md](CLAUDE.md) for the archit
|
|
|
138
158
|
<summary><b>Troubleshooting</b></summary>
|
|
139
159
|
|
|
140
160
|
- **401 Unauthorized** — token expired or wrong. Run `wbox config show` to see the masked token, `wbox config set-token` to update.
|
|
161
|
+
- **`wbox self upgrade` says "run `uv tool upgrade …`" (or pipx / pip)** — expected. `wbox self upgrade` only swaps the standalone bundle the one-line installer drops in place. If you installed via pip / pipx / uv, follow the command the gate prints. See [Upgrading](#upgrading).
|
|
141
162
|
- **Windows: "execution policy" error during install** — the installer offers to fix this for you. If you skipped it, run `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser` and re-run the installer.
|
|
142
163
|
- **Linux: command not found after install** — open a new terminal, or run `source ~/.local/bin/env` in your current shell.
|
|
143
164
|
- **Date format errors** — Wealthbox needs ISO 8601: `"2026-05-01T10:00:00-07:00"` for datetimes, `"YYYY-MM-DD"` for date-only fields.
|
|
@@ -8,20 +8,26 @@ This guide walks you through installing **wealthbox-cli** and configuring it to
|
|
|
8
8
|
|
|
9
9
|
### One-line installer (no Python required)
|
|
10
10
|
|
|
11
|
-
If you don't have Python installed, the bootstrap script handles everything: it
|
|
11
|
+
If you don't have Python installed, the bootstrap script handles everything: it downloads a prebuilt `wbox`, places it on your PATH, prompts for your API token, and offers to install the AI agent skill.
|
|
12
12
|
|
|
13
13
|
=== "macOS / Linux"
|
|
14
14
|
|
|
15
|
+
Installs [uv](https://github.com/astral-sh/uv) (which provisions Python automatically) and uses it to put `wbox` on your PATH.
|
|
16
|
+
|
|
15
17
|
```bash
|
|
16
18
|
curl -LsSf https://raw.githubusercontent.com/massive-value/wealthbox-cli/main/scripts/install.sh | bash
|
|
17
19
|
```
|
|
18
20
|
|
|
19
21
|
=== "Windows (PowerShell)"
|
|
20
22
|
|
|
23
|
+
Downloads a standalone PyInstaller-bundled `wbox.exe` (no Python, no uv) to `%LOCALAPPDATA%\Programs\wbox\`. Pass `-SkipSkills` (via `iwr -OutFile install.ps1; .\install.ps1 -SkipSkills`) to skip the AI agent skill.
|
|
24
|
+
|
|
21
25
|
```powershell
|
|
22
26
|
irm https://raw.githubusercontent.com/massive-value/wealthbox-cli/main/scripts/install.ps1 | iex
|
|
23
27
|
```
|
|
24
28
|
|
|
29
|
+
Either install supports `wbox self upgrade` for future updates. If you instead install via pip / pipx / uv tool (below), use the corresponding package-manager upgrade command — `wbox self upgrade` will refuse with the right pointer.
|
|
30
|
+
|
|
25
31
|
### From PyPI (recommended for Python users)
|
|
26
32
|
|
|
27
33
|
```bash
|
|
@@ -451,6 +451,80 @@ async def resolve_category_id(
|
|
|
451
451
|
raise typer.BadParameter(f"No {label} configured in this workspace.")
|
|
452
452
|
|
|
453
453
|
|
|
454
|
+
def _match_contact_role(roles: list[dict[str, Any]], token: str) -> dict[str, Any]:
|
|
455
|
+
"""Find a contact role by numeric id or case-insensitive name."""
|
|
456
|
+
if token.isdigit():
|
|
457
|
+
rid = int(token)
|
|
458
|
+
for r in roles:
|
|
459
|
+
if int(r.get("id", 0)) == rid:
|
|
460
|
+
return r
|
|
461
|
+
raise typer.BadParameter(f"No contact role with id {rid} in this workspace.")
|
|
462
|
+
target = token.casefold()
|
|
463
|
+
for r in roles:
|
|
464
|
+
if str(r.get("name", "")).casefold() == target:
|
|
465
|
+
return r
|
|
466
|
+
available = sorted(str(r["name"]) for r in roles if r.get("name"))
|
|
467
|
+
raise typer.BadParameter(
|
|
468
|
+
f"Unknown contact role '{token}'. Available: {', '.join(available) or '(none configured)'}"
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _role_option_user(option: dict[str, Any]) -> dict[str, Any]:
|
|
473
|
+
return option.get("assigned_to") or {}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _match_role_option(role: dict[str, Any], token: str) -> int:
|
|
477
|
+
"""Resolve a user (numeric id, exact name, or unique substring) to a role-option id."""
|
|
478
|
+
options = role.get("available_options", []) or []
|
|
479
|
+
role_name = role.get("name", "role")
|
|
480
|
+
if token.isdigit():
|
|
481
|
+
uid = int(token)
|
|
482
|
+
for opt in options:
|
|
483
|
+
if int(_role_option_user(opt).get("id", 0)) == uid:
|
|
484
|
+
return int(opt["id"])
|
|
485
|
+
raise typer.BadParameter(f"No user with id {uid} is available for role '{role_name}'.")
|
|
486
|
+
target = token.casefold()
|
|
487
|
+
exact = [o for o in options if str(_role_option_user(o).get("name", "")).casefold() == target]
|
|
488
|
+
matches = exact or [
|
|
489
|
+
o for o in options if target in str(_role_option_user(o).get("name", "")).casefold()
|
|
490
|
+
]
|
|
491
|
+
if len(matches) == 1:
|
|
492
|
+
return int(matches[0]["id"])
|
|
493
|
+
if not matches:
|
|
494
|
+
names = sorted(str(_role_option_user(o).get("name", "")) for o in options)
|
|
495
|
+
raise typer.BadParameter(
|
|
496
|
+
f"No user matching '{token}' for role '{role_name}'. Available: {', '.join(names)}"
|
|
497
|
+
)
|
|
498
|
+
cand = sorted(str(_role_option_user(o).get("name", "")) for o in matches)
|
|
499
|
+
raise typer.BadParameter(
|
|
500
|
+
f"'{token}' is ambiguous for role '{role_name}': matches {', '.join(cand)}. Be more specific."
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
async def resolve_contact_roles(client: WealthboxClient, specs: list[str]) -> list[dict[str, int]]:
|
|
505
|
+
"""Resolve ``Role:User`` specs to ``[{"id": role_id, "value": option_id}]`` entries.
|
|
506
|
+
|
|
507
|
+
Role may be a contact-role name (case-insensitive) or numeric id. User may be a
|
|
508
|
+
user name (case-insensitive; exact preferred, else unique substring) or numeric
|
|
509
|
+
user id. Both are resolved against ``wbox contacts categories contact-roles`` —
|
|
510
|
+
the option id (``available_options[].id``), not the user id, is the value the
|
|
511
|
+
Wealthbox API stores.
|
|
512
|
+
"""
|
|
513
|
+
data = await client.list_all_categories(CategoryType.CONTACT_ROLES)
|
|
514
|
+
roles = data.get("contact_roles", []) or []
|
|
515
|
+
resolved: list[dict[str, int]] = []
|
|
516
|
+
for spec in specs:
|
|
517
|
+
role_token, sep, user_token = spec.partition(":")
|
|
518
|
+
role_token, user_token = role_token.strip(), user_token.strip()
|
|
519
|
+
if not sep or not role_token or not user_token:
|
|
520
|
+
raise typer.BadParameter(
|
|
521
|
+
f"--advisor-role expects 'Role:User' (e.g. 'Associate Advisor:Greg Hyde'); got '{spec}'."
|
|
522
|
+
)
|
|
523
|
+
role = _match_contact_role(roles, role_token)
|
|
524
|
+
resolved.append({"id": int(role["id"]), "value": _match_role_option(role, user_token)})
|
|
525
|
+
return resolved
|
|
526
|
+
|
|
527
|
+
|
|
454
528
|
def parse_more_fields(more_fields: str, reserved: set[str]) -> dict[str, Any]:
|
|
455
529
|
"""Parse --more-fields JSON and validate against reserved keys.
|
|
456
530
|
|
|
@@ -25,9 +25,16 @@ from ._util import (
|
|
|
25
25
|
make_resource_app,
|
|
26
26
|
output_result,
|
|
27
27
|
parse_more_fields,
|
|
28
|
+
resolve_contact_roles,
|
|
28
29
|
run_client,
|
|
29
30
|
)
|
|
30
31
|
|
|
32
|
+
_ADVISOR_ROLE_HELP = (
|
|
33
|
+
"Assign a contact role as 'Role:User' (repeatable), e.g. "
|
|
34
|
+
"'Associate Advisor:Greg Hyde'. User may be a name or numeric user id. "
|
|
35
|
+
"Resolved via: wbox contacts categories contact-roles"
|
|
36
|
+
)
|
|
37
|
+
|
|
31
38
|
app = make_resource_app(help="Manage Wealthbox contacts.")
|
|
32
39
|
|
|
33
40
|
_DEFAULT_FIELDS = ["id", "name", "type", "contact_type", "assigned_to", "status"]
|
|
@@ -180,6 +187,23 @@ def _build_contact_entry(value: str | None, kind: str | None) -> list[dict[str,
|
|
|
180
187
|
return [entry]
|
|
181
188
|
|
|
182
189
|
|
|
190
|
+
def _finish_create(
|
|
191
|
+
payload: dict[str, Any], advisor_role: list[str] | None, token: str | None, fmt: OutputFormat
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Resolve any --advisor-role specs in-session, build the model, create, and print."""
|
|
194
|
+
if advisor_role and "contact_roles" in payload:
|
|
195
|
+
raise typer.BadParameter(
|
|
196
|
+
"Set contact roles via --advisor-role OR contact_roles in --more-fields, not both."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def _do(client: Any) -> Any:
|
|
200
|
+
if advisor_role:
|
|
201
|
+
payload["contact_roles"] = await resolve_contact_roles(client, advisor_role)
|
|
202
|
+
return await client.create_contact(ContactCreateInput(**payload))
|
|
203
|
+
|
|
204
|
+
output_result(run_client(token, _do), fmt)
|
|
205
|
+
|
|
206
|
+
|
|
183
207
|
def _create_named_contact(
|
|
184
208
|
record_type: RecordType,
|
|
185
209
|
reserved: set[str],
|
|
@@ -194,6 +218,7 @@ def _create_named_contact(
|
|
|
194
218
|
phone_type: str | None,
|
|
195
219
|
tags: str | None,
|
|
196
220
|
more_fields: str | None,
|
|
221
|
+
advisor_role: list[str] | None,
|
|
197
222
|
token: str | None,
|
|
198
223
|
fmt: OutputFormat,
|
|
199
224
|
) -> None:
|
|
@@ -216,8 +241,7 @@ def _create_named_contact(
|
|
|
216
241
|
payload["tags"] = tag_list
|
|
217
242
|
if more_fields:
|
|
218
243
|
payload.update(parse_more_fields(more_fields, reserved))
|
|
219
|
-
|
|
220
|
-
output_result(run_client(token, lambda c: c.create_contact(input_model)), fmt)
|
|
244
|
+
_finish_create(payload, advisor_role, token, fmt)
|
|
221
245
|
|
|
222
246
|
|
|
223
247
|
@add_app.command("person", help="Create a Person contact.")
|
|
@@ -253,6 +277,7 @@ def add_person(
|
|
|
253
277
|
more_fields: str | None = typer.Option(
|
|
254
278
|
None, "--more-fields", help="Extra fields as JSON object (merged with flags; cannot override explicit flags)"
|
|
255
279
|
),
|
|
280
|
+
advisor_role: list[str] | None = typer.Option(None, "--advisor-role", help=_ADVISOR_ROLE_HELP),
|
|
256
281
|
token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
|
|
257
282
|
fmt: OutputFormat = typer.Option(OutputFormat.JSON, "--format"),
|
|
258
283
|
) -> None:
|
|
@@ -286,8 +311,7 @@ def add_person(
|
|
|
286
311
|
payload["tags"] = tag_list
|
|
287
312
|
if more_fields:
|
|
288
313
|
payload.update(parse_more_fields(more_fields, _PERSON_RESERVED))
|
|
289
|
-
|
|
290
|
-
output_result(run_client(token, lambda c: c.create_contact(input_model)), fmt)
|
|
314
|
+
_finish_create(payload, advisor_role, token, fmt)
|
|
291
315
|
|
|
292
316
|
|
|
293
317
|
@add_app.command("household", help="Create a Household contact.")
|
|
@@ -308,6 +332,7 @@ def add_household(
|
|
|
308
332
|
more_fields: str | None = typer.Option(
|
|
309
333
|
None, "--more-fields", help="Extra fields as JSON object (merged with flags; cannot override explicit flags)"
|
|
310
334
|
),
|
|
335
|
+
advisor_role: list[str] | None = typer.Option(None, "--advisor-role", help=_ADVISOR_ROLE_HELP),
|
|
311
336
|
token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
|
|
312
337
|
fmt: OutputFormat = typer.Option(OutputFormat.JSON, "--format"),
|
|
313
338
|
) -> None:
|
|
@@ -327,8 +352,7 @@ def add_household(
|
|
|
327
352
|
payload["tags"] = tag_list
|
|
328
353
|
if more_fields:
|
|
329
354
|
payload.update(parse_more_fields(more_fields, _HOUSEHOLD_RESERVED))
|
|
330
|
-
|
|
331
|
-
output_result(run_client(token, lambda c: c.create_contact(input_model)), fmt)
|
|
355
|
+
_finish_create(payload, advisor_role, token, fmt)
|
|
332
356
|
|
|
333
357
|
|
|
334
358
|
@add_app.command("org", help="Create an Organization contact.")
|
|
@@ -353,12 +377,13 @@ def add_org(
|
|
|
353
377
|
more_fields: str | None = typer.Option(
|
|
354
378
|
None, "--more-fields", help="Extra fields as JSON object (merged with flags; cannot override explicit flags)"
|
|
355
379
|
),
|
|
380
|
+
advisor_role: list[str] | None = typer.Option(None, "--advisor-role", help=_ADVISOR_ROLE_HELP),
|
|
356
381
|
token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
|
|
357
382
|
fmt: OutputFormat = typer.Option(OutputFormat.JSON, "--format"),
|
|
358
383
|
) -> None:
|
|
359
384
|
_create_named_contact(
|
|
360
385
|
RecordType.ORGANIZATION, _ORG_TRUST_RESERVED, name, contact_type, contact_source,
|
|
361
|
-
active, assigned_to, email, email_type, phone, phone_type, tags, more_fields, token, fmt,
|
|
386
|
+
active, assigned_to, email, email_type, phone, phone_type, tags, more_fields, advisor_role, token, fmt,
|
|
362
387
|
)
|
|
363
388
|
|
|
364
389
|
|
|
@@ -384,12 +409,13 @@ def add_trust(
|
|
|
384
409
|
more_fields: str | None = typer.Option(
|
|
385
410
|
None, "--more-fields", help="Extra fields as JSON object (merged with flags; cannot override explicit flags)"
|
|
386
411
|
),
|
|
412
|
+
advisor_role: list[str] | None = typer.Option(None, "--advisor-role", help=_ADVISOR_ROLE_HELP),
|
|
387
413
|
token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
|
|
388
414
|
fmt: OutputFormat = typer.Option(OutputFormat.JSON, "--format"),
|
|
389
415
|
) -> None:
|
|
390
416
|
_create_named_contact(
|
|
391
417
|
RecordType.TRUST, _ORG_TRUST_RESERVED, name, contact_type, contact_source,
|
|
392
|
-
active, assigned_to, email, email_type, phone, phone_type, tags, more_fields, token, fmt,
|
|
418
|
+
active, assigned_to, email, email_type, phone, phone_type, tags, more_fields, advisor_role, token, fmt,
|
|
393
419
|
)
|
|
394
420
|
|
|
395
421
|
|
|
@@ -420,13 +446,19 @@ def update_contact(
|
|
|
420
446
|
"include existing tags you wish to keep. New tags are auto-created."
|
|
421
447
|
),
|
|
422
448
|
),
|
|
449
|
+
advisor_role: list[str] | None = typer.Option(None, "--advisor-role", help=_ADVISOR_ROLE_HELP),
|
|
423
450
|
token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
|
|
424
451
|
fmt: OutputFormat = typer.Option(OutputFormat.JSON, "--format"),
|
|
425
452
|
) -> None:
|
|
453
|
+
if advisor_role and json_data is not None:
|
|
454
|
+
raise typer.BadParameter(
|
|
455
|
+
"Set contact roles via --advisor-role OR contact_roles in --json, not both."
|
|
456
|
+
)
|
|
457
|
+
|
|
426
458
|
if json_data is not None:
|
|
427
|
-
|
|
459
|
+
payload: dict[str, Any] = json.loads(json_data)
|
|
428
460
|
else:
|
|
429
|
-
payload
|
|
461
|
+
payload = {k: v for k, v in {
|
|
430
462
|
"first_name": first_name,
|
|
431
463
|
"middle_name": middle_name,
|
|
432
464
|
"last_name": last_name,
|
|
@@ -441,9 +473,13 @@ def update_contact(
|
|
|
441
473
|
tag_list = _parse_tags(tags)
|
|
442
474
|
if tag_list is not None:
|
|
443
475
|
payload["tags"] = tag_list
|
|
444
|
-
input_model = ContactUpdateInput(**payload)
|
|
445
476
|
|
|
446
|
-
|
|
477
|
+
async def _do(client: Any) -> Any:
|
|
478
|
+
if advisor_role:
|
|
479
|
+
payload["contact_roles"] = await resolve_contact_roles(client, advisor_role)
|
|
480
|
+
return await client.update_contact(contact_id, ContactUpdateInput(**payload))
|
|
481
|
+
|
|
482
|
+
output_result(run_client(token, _do), fmt)
|
|
447
483
|
|
|
448
484
|
|
|
449
485
|
@app.command("delete", help="Delete an existing contact.")
|
|
@@ -37,8 +37,29 @@ def _default_install_root() -> Path:
|
|
|
37
37
|
return Path(sys.executable).resolve().parent
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
_NON_BUNDLE_UPGRADE_HINTS = {
|
|
41
|
+
"uv-tool": "uv tool upgrade wealthbox-cli",
|
|
42
|
+
"pipx": "pipx upgrade wealthbox-cli",
|
|
43
|
+
"pip": "pip install --upgrade wealthbox-cli",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
40
47
|
@app.command("upgrade", help="Upgrade the `wbox` CLI to the latest GitHub release.")
|
|
41
48
|
def upgrade_cmd() -> None:
|
|
49
|
+
kind = self_upgrade._install_kind()
|
|
50
|
+
if kind != "bundle":
|
|
51
|
+
# The frozen-bundle swap would either silently no-op (uv copies the
|
|
52
|
+
# shim to ~/.local/bin so replacing the venv-side shim doesn't change
|
|
53
|
+
# what PATH resolves) or wedge the venv. Refuse before downloading.
|
|
54
|
+
hint = _NON_BUNDLE_UPGRADE_HINTS.get(kind, _NON_BUNDLE_UPGRADE_HINTS["pip"])
|
|
55
|
+
typer.echo(
|
|
56
|
+
"wbox self upgrade only swaps the standalone binary from "
|
|
57
|
+
"install.ps1.",
|
|
58
|
+
err=True,
|
|
59
|
+
)
|
|
60
|
+
typer.echo(f"This wbox was installed via {kind} — run: {hint}", err=True)
|
|
61
|
+
raise typer.Exit(code=1)
|
|
62
|
+
|
|
42
63
|
candidate = self_upgrade.check()
|
|
43
64
|
if candidate is None:
|
|
44
65
|
typer.echo("Already on the latest version.")
|
|
@@ -105,8 +105,16 @@ class StreetAddress(WealthboxModel):
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
class ContactRoleAssignment(WealthboxModel):
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
"""A contact-role assignment on a contact's write payload.
|
|
109
|
+
|
|
110
|
+
``id`` is the contact-role id (e.g. "Associate Advisor") and ``value`` is
|
|
111
|
+
the *role-option* id that maps to a specific user — NOT the user's id.
|
|
112
|
+
Resolve both from ``wbox contacts categories contact-roles``, whose
|
|
113
|
+
``available_options[].id`` is the value to use here.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
id: int = Field(ge=1, description="Contact-role id (from contacts categories contact-roles)")
|
|
117
|
+
value: int = Field(ge=1, description="Role-option id mapping to a user (available_options[].id)")
|
|
110
118
|
|
|
111
119
|
|
|
112
120
|
class LinkedToRef(WealthboxModel):
|
|
@@ -51,6 +51,7 @@ __all__ = [
|
|
|
51
51
|
"check_release_staleness",
|
|
52
52
|
"apply",
|
|
53
53
|
"_cleanup_stale_backups",
|
|
54
|
+
"_install_kind",
|
|
54
55
|
]
|
|
55
56
|
|
|
56
57
|
_RELEASES_LATEST_URL = (
|
|
@@ -139,6 +140,35 @@ def _running_version() -> str:
|
|
|
139
140
|
return __version__
|
|
140
141
|
|
|
141
142
|
|
|
143
|
+
def _install_kind() -> str:
|
|
144
|
+
"""Classify how the running ``wbox`` was installed.
|
|
145
|
+
|
|
146
|
+
The :func:`apply` swap is only correct for the standalone PyInstaller
|
|
147
|
+
bundle that ``install.ps1`` ships. Under uv / pipx / pip the install is
|
|
148
|
+
a tiny console-script shim around a venv; replacing it with a frozen
|
|
149
|
+
binary would either silently no-op (PATH points at a copy uv made
|
|
150
|
+
elsewhere, e.g. ``~/.local/bin/wbox.exe``) or wedge the venv on the
|
|
151
|
+
next ``uv tool sync``. The gate in :mod:`cli.self_cmd` consults this
|
|
152
|
+
classifier to refuse with a clear "use <installer> instead" message.
|
|
153
|
+
|
|
154
|
+
Returns one of:
|
|
155
|
+
|
|
156
|
+
- ``"bundle"`` — frozen PyInstaller EXE (``sys.frozen`` is set).
|
|
157
|
+
- ``"uv-tool"`` — ``sys.executable`` lives under a ``uv/tools/`` venv.
|
|
158
|
+
- ``"pipx"`` — ``sys.executable`` lives under a ``pipx/`` venv.
|
|
159
|
+
- ``"pip"`` — anything else (ordinary pip/venv install).
|
|
160
|
+
"""
|
|
161
|
+
if getattr(sys, "frozen", False):
|
|
162
|
+
return "bundle"
|
|
163
|
+
parts = [p.lower() for p in Path(sys.executable).resolve().parts]
|
|
164
|
+
for i in range(len(parts) - 1):
|
|
165
|
+
if parts[i] == "uv" and parts[i + 1] == "tools":
|
|
166
|
+
return "uv-tool"
|
|
167
|
+
if "pipx" in parts:
|
|
168
|
+
return "pipx"
|
|
169
|
+
return "pip"
|
|
170
|
+
|
|
171
|
+
|
|
142
172
|
# ---------------------------------------------------------------------------
|
|
143
173
|
# Internal helpers
|
|
144
174
|
# ---------------------------------------------------------------------------
|
{wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/SKILL.md
RENAMED
|
@@ -195,3 +195,5 @@ The single-quoted heredoc (`<<'EOF'`) is the recommended default — it disables
|
|
|
195
195
|
- **Relative due dates:** tasks accept `--frame today|tomorrow|this-week|next-week|this-month|next-month` instead of `--due-date` (mutually exclusive)
|
|
196
196
|
- **Activity pagination:** uses `--cursor`, not `--page`
|
|
197
197
|
- **Don't infer flag names:** read `references/<resource>.md` before invoking — flags like `--name` (not `--search`), `--frame` (not `--due-in`) are easy to guess wrong
|
|
198
|
+
- **Advisor / contact roles:** assign them with `wbox contacts add|update ... --advisor-role "Role:User"` (e.g. `"Associate Advisor:Greg Hyde"`). This covers a firm's "Second Advisor" / Partner assignments. See `references/contacts.md` → "Contact Roles".
|
|
199
|
+
- **Before falling back to raw API, exhaust the CLI:** if a firm-required field has no obvious flag, check `references/<resource>.md`, run `wbox <resource> --help` / `wbox <resource> <sub> --help`, and look for a `wbox <resource> categories <type>` lookup that supplies the needed ids. Reach for raw `curl`/API only after these come up empty — and if they do, that's a CLI gap worth filing as an issue.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# firm/contacts.md — Example
|
|
2
|
+
|
|
3
|
+
This is an illustrative template. Replace every value below with your firm's
|
|
4
|
+
real conventions. Valid category values (contact types, sources, roles, etc.)
|
|
5
|
+
come from your workspace — list them with `wbox contacts categories <type>`.
|
|
6
|
+
|
|
7
|
+
## Required Fields
|
|
8
|
+
- contact-type: one of your workspace's contact types (`wbox contacts categories contact-types`)
|
|
9
|
+
- contact-source: one of your workspace's sources (`wbox contacts categories contact-sources`)
|
|
10
|
+
|
|
11
|
+
## Defaults
|
|
12
|
+
- assigned-to: <your default user id> (`wbox me user-id`)
|
|
13
|
+
- active: true
|
|
14
|
+
|
|
15
|
+
## Advisor / Contact Roles
|
|
16
|
+
- If your firm assigns advisors via contact roles (e.g. "Associate Advisor",
|
|
17
|
+
"Partner"), set them with `--advisor-role "Role:User"` on add/update.
|
|
18
|
+
- List the configured roles and their assignable users with
|
|
19
|
+
`wbox contacts categories contact-roles`.
|
|
20
|
+
- Example: a prospect's primary advisor is `--assigned-to`, and the second
|
|
21
|
+
advisor is `--advisor-role "Associate Advisor:<user>"`.
|
|
22
|
+
|
|
23
|
+
## Person Contacts
|
|
24
|
+
- Always collect: email, phone, birth-date
|
|
25
|
+
- Preferred email-type: Personal
|
|
26
|
+
- Preferred phone-type: Mobile
|
|
27
|
+
|
|
28
|
+
## Household Contacts
|
|
29
|
+
- Always create a household when onboarding a married couple
|
|
30
|
+
- Naming convention: e.g. "{Last}, {Head First} & {Spouse First}"
|
|
31
|
+
- Add members one at a time (sequential) — see references/households.md
|
|
32
|
+
|
|
33
|
+
## Organization / Trust Contacts
|
|
34
|
+
- Require EIN in --more-fields for Trusts
|
|
@@ -70,6 +70,7 @@ wbox contacts add person [OPTIONS]
|
|
|
70
70
|
| `--phone` | STR | Phone number |
|
|
71
71
|
| `--phone-type` | STR | Work, Mobile, etc. |
|
|
72
72
|
| `--tags` | STR | Comma-separated tag names (e.g. "VIP,Q1-Outreach"). New tags are auto-created. |
|
|
73
|
+
| `--advisor-role` | STR | Assign a contact role (repeatable): `"Role:User"`, e.g. `"Associate Advisor:Greg Hyde"`. See "Contact Roles" below. |
|
|
73
74
|
| `--more-fields` | JSON | Additional fields as JSON object |
|
|
74
75
|
| `--format` | json\|table\|csv\|tsv | Output format |
|
|
75
76
|
|
|
@@ -77,7 +78,7 @@ wbox contacts add person [OPTIONS]
|
|
|
77
78
|
```bash
|
|
78
79
|
wbox contacts add household --name <NAME> [OPTIONS]
|
|
79
80
|
```
|
|
80
|
-
`--name` is required. Supports: `--contact-type`, `--contact-source`, `--active/--inactive`, `--assigned-to`, `--email`, `--email-type`, `--tags`, `--more-fields`, `--format`.
|
|
81
|
+
`--name` is required. Supports: `--contact-type`, `--contact-source`, `--active/--inactive`, `--assigned-to`, `--email`, `--email-type`, `--tags`, `--advisor-role`, `--more-fields`, `--format`.
|
|
81
82
|
|
|
82
83
|
### Organization
|
|
83
84
|
```bash
|
|
@@ -112,6 +113,7 @@ Only pass the fields you want to change:
|
|
|
112
113
|
| `--active` / `--inactive` | flag | Active status |
|
|
113
114
|
| `--assigned-to` | INT | Reassign to user ID |
|
|
114
115
|
| `--tags` | STR | Comma-separated tag names. Replaces all tags — include existing ones to keep. |
|
|
116
|
+
| `--advisor-role` | STR | Assign a contact role (repeatable): `"Role:User"`. See "Contact Roles" below. Mutually exclusive with a `contact_roles` block in `--json`. |
|
|
115
117
|
| `--json` | STR | Full JSON for nested/complex fields |
|
|
116
118
|
| `--format` | json\|table\|csv\|tsv | Output format |
|
|
117
119
|
|
|
@@ -136,6 +138,51 @@ wbox contacts categories website-types
|
|
|
136
138
|
wbox contacts categories contact-roles
|
|
137
139
|
```
|
|
138
140
|
|
|
141
|
+
## Contact Roles (advisor assignments)
|
|
142
|
+
|
|
143
|
+
Contact roles attach a user to a contact in a named role — e.g. **Associate
|
|
144
|
+
Advisor** ("Second Advisor") or **Partner**. This is how firm advisor
|
|
145
|
+
assignments are stored; they are *separate* from `--assigned-to` (the primary
|
|
146
|
+
owner).
|
|
147
|
+
|
|
148
|
+
**Use the `--advisor-role` flag** (repeatable) on `add person|household|org|trust`
|
|
149
|
+
and on `update`:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
wbox contacts add person --first-name Mark --last-name Leone \
|
|
153
|
+
--advisor-role "Associate Advisor:Greg Hyde" \
|
|
154
|
+
--advisor-role "Partner:Jane Smith"
|
|
155
|
+
|
|
156
|
+
wbox contacts update 12345 --advisor-role "Associate Advisor:Greg Hyde"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The spec is `Role:User`. The role is matched by name (case-insensitive) or
|
|
160
|
+
numeric id; the user by name (exact, else unique substring) or numeric user id.
|
|
161
|
+
The flag resolves both against `wbox contacts categories contact-roles` for you.
|
|
162
|
+
|
|
163
|
+
**The underlying write shape** (if you ever build it by hand via `--more-fields`
|
|
164
|
+
or `--json`) is `{"id": <role_id>, "value": <option_id>}`:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
wbox contacts update 12345 --json '{"contact_roles": [{"id": 1338, "value": 4226}]}'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
⚠️ `value` is the **role-option id**, NOT the user id. Each role exposes
|
|
171
|
+
`available_options[].id`, one per assignable user — that option id is the value.
|
|
172
|
+
Look it up with `wbox contacts categories contact-roles`:
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
{ "id": 1338, "name": "Associate Advisor",
|
|
176
|
+
"available_options": [
|
|
177
|
+
{ "id": 4226, "assigned_to": { "id": 154372, "name": "Greg Hyde" } }
|
|
178
|
+
] }
|
|
179
|
+
// → to set Greg as Associate Advisor: {"id": 1338, "value": 4226}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Notes: roles can only be added/updated, not removed, via the API. You only need
|
|
183
|
+
to include the roles you're changing. `--advisor-role` and a `contact_roles`
|
|
184
|
+
block in `--more-fields`/`--json` are mutually exclusive.
|
|
185
|
+
|
|
139
186
|
## Generated Flag Reference
|
|
140
187
|
|
|
141
188
|
The following section is auto-generated from the Typer command tree by
|
|
@@ -150,6 +197,7 @@ Create a Household contact.
|
|
|
150
197
|
| Flag | Type | Default | Description |
|
|
151
198
|
|------|------|---------|-------------|
|
|
152
199
|
| `--active` / `--inactive` | `BOOLEAN` | `-` | Set contact status to Active or Inactive |
|
|
200
|
+
| `--advisor-role` | `TEXT` | `-` | Assign a contact role as 'Role:User' (repeatable), e.g. 'Associate Advisor:Greg Hyde'. User may be a name or numeric user id. Resolved via: wbox contacts categories contact-roles |
|
|
153
201
|
| `--assigned-to` | `INTEGER` | `-` | Assign to a user by ID |
|
|
154
202
|
| `--contact-source` | `TEXT` | `-` | |
|
|
155
203
|
| `--contact-type` | `TEXT` | `-` | e.g. Client, Prospect |
|
|
@@ -174,6 +222,7 @@ Create an Organization contact.
|
|
|
174
222
|
| Flag | Type | Default | Description |
|
|
175
223
|
|------|------|---------|-------------|
|
|
176
224
|
| `--active` / `--inactive` | `BOOLEAN` | `-` | Set contact status to Active or Inactive |
|
|
225
|
+
| `--advisor-role` | `TEXT` | `-` | Assign a contact role as 'Role:User' (repeatable), e.g. 'Associate Advisor:Greg Hyde'. User may be a name or numeric user id. Resolved via: wbox contacts categories contact-roles |
|
|
177
226
|
| `--assigned-to` | `INTEGER` | `-` | Assign to a user by ID |
|
|
178
227
|
| `--contact-source` | `TEXT` | `-` | |
|
|
179
228
|
| `--contact-type` | `TEXT` | `-` | e.g. Client, Prospect |
|
|
@@ -200,6 +249,7 @@ Create a Person contact.
|
|
|
200
249
|
| Flag | Type | Default | Description |
|
|
201
250
|
|------|------|---------|-------------|
|
|
202
251
|
| `--active` / `--inactive` | `BOOLEAN` | `-` | Set contact status to Active or Inactive |
|
|
252
|
+
| `--advisor-role` | `TEXT` | `-` | Assign a contact role as 'Role:User' (repeatable), e.g. 'Associate Advisor:Greg Hyde'. User may be a name or numeric user id. Resolved via: wbox contacts categories contact-roles |
|
|
203
253
|
| `--anniversary` | `TEXT` | `-` | Format: YYYY-MM-DD |
|
|
204
254
|
| `--assigned-to` | `INTEGER` | `-` | Assign to a user by ID |
|
|
205
255
|
| `--birth-date` | `TEXT` | `-` | Format: YYYY-MM-DD |
|
|
@@ -254,6 +304,7 @@ Create a Trust contact.
|
|
|
254
304
|
| Flag | Type | Default | Description |
|
|
255
305
|
|------|------|---------|-------------|
|
|
256
306
|
| `--active` / `--inactive` | `BOOLEAN` | `-` | Set contact status to Active or Inactive |
|
|
307
|
+
| `--advisor-role` | `TEXT` | `-` | Assign a contact role as 'Role:User' (repeatable), e.g. 'Associate Advisor:Greg Hyde'. User may be a name or numeric user id. Resolved via: wbox contacts categories contact-roles |
|
|
257
308
|
| `--assigned-to` | `INTEGER` | `-` | Assign to a user by ID |
|
|
258
309
|
| `--contact-source` | `TEXT` | `-` | |
|
|
259
310
|
| `--contact-type` | `TEXT` | `-` | e.g. Client, Prospect |
|
|
@@ -479,6 +530,7 @@ Update an existing contact. Pass only the fields you want to change.
|
|
|
479
530
|
| Flag | Type | Default | Description |
|
|
480
531
|
|------|------|---------|-------------|
|
|
481
532
|
| `--active` / `--inactive` | `BOOLEAN` | `-` | Set contact status to Active or Inactive |
|
|
533
|
+
| `--advisor-role` | `TEXT` | `-` | Assign a contact role as 'Role:User' (repeatable), e.g. 'Associate Advisor:Greg Hyde'. User may be a name or numeric user id. Resolved via: wbox contacts categories contact-roles |
|
|
482
534
|
| `--assigned-to` | `INTEGER` | `-` | Reassign to a user by ID |
|
|
483
535
|
| `--company-name` | `TEXT` | `-` | |
|
|
484
536
|
| `--contact-source` | `TEXT` | `-` | |
|
|
@@ -512,7 +564,12 @@ wbox contacts add person --first-name Jane --last-name Doe --contact-type Client
|
|
|
512
564
|
# Update job title
|
|
513
565
|
wbox contacts update 12345 --job-title "CFO"
|
|
514
566
|
|
|
515
|
-
# Create a
|
|
567
|
+
# Create a prospect with primary owner + advisor role in one call
|
|
568
|
+
wbox contacts add person --first-name Jane --last-name Doe \
|
|
569
|
+
--contact-type Prospect --assigned-to 154372 \
|
|
570
|
+
--advisor-role "Associate Advisor:Greg Hyde"
|
|
571
|
+
|
|
572
|
+
# Create a household and add members (two-step — add members SEQUENTIALLY)
|
|
516
573
|
wbox contacts add household --name "The Smith Family"
|
|
517
574
|
# note the returned ID, then:
|
|
518
575
|
wbox households add-member <HOUSEHOLD_ID> <MEMBER_ID> --title Head
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Manage membership of household contacts. Household contacts are created via `wbox contacts add household`.
|
|
4
4
|
|
|
5
|
+
> ⚠️ **Add members one at a time, sequentially.** Each `add-member` call
|
|
6
|
+
> rewrites the household's member list, so concurrent/parallel calls race and
|
|
7
|
+
> the last write clobbers the earlier ones. Run them in series and wait for
|
|
8
|
+
> each to return before issuing the next.
|
|
9
|
+
|
|
5
10
|
## Add Member
|
|
6
11
|
|
|
7
12
|
```bash
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# firm/contacts.md — Example
|
|
2
|
-
|
|
3
|
-
## Required Fields
|
|
4
|
-
- contact-type: always one of: Client, Prospect, Center of Influence, Vendor
|
|
5
|
-
- contact-source: always one of: Referral, Website, Event, Cold Call
|
|
6
|
-
|
|
7
|
-
## Defaults
|
|
8
|
-
- assigned-to: 12345
|
|
9
|
-
- active: true
|
|
10
|
-
|
|
11
|
-
## Person Contacts
|
|
12
|
-
- Always collect: email, phone, birth-date
|
|
13
|
-
- Preferred email-type: Personal
|
|
14
|
-
- Preferred phone-type: Mobile
|
|
15
|
-
|
|
16
|
-
## Household Contacts
|
|
17
|
-
- Always create a household when onboarding a married couple
|
|
18
|
-
- Naming convention: "The {LastName} Family"
|
|
19
|
-
|
|
20
|
-
## Organization / Trust Contacts
|
|
21
|
-
- Require EIN in more-fields for Trusts
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wealthbox_cli-2.2.0 → wealthbox_cli-2.3.0}/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|