wealthbox-cli 1.1.1__tar.gz → 1.1.2__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.1.1 → wealthbox_cli-1.1.2}/PKG-INFO +4 -1
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/README.md +3 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/pyproject.toml +1 -1
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/_skill_bootstrap.py +45 -15
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/_skill_platforms.py +30 -4
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/skills.py +77 -29
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/SKILL.md +24 -4
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md +10 -15
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/.env.example +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/.github/workflows/ci.yml +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/.github/workflows/docs.yml +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/.gitignore +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/CONTRIBUTING.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/LICENSE +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/SECURITY.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/docs/changelog.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/docs/cli-reference.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/docs/getting-started.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/docs/index.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/docs/overrides/main.html +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/docs/promotion.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/mkdocs.yml +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/__init__.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/__init__.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/_config.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/_util.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/activity.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/categories.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/config.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/contacts.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/events.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/households.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/main.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/me.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/notes.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/opportunities.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/projects.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/tasks.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/users.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/cli/workflows.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/__init__.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/activity.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/base.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/categories.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/comments.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/contacts.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/events.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/households.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/me.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/notes.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/opportunities.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/projects.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/tasks.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/users.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/client/workflows.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/__init__.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/activity.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/comments.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/common.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/contacts.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/custom_fields.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/enums.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/events.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/households.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/notes.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/opportunities.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/projects.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/tasks.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/models/workflows.py +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/py.typed +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/contacts.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/events.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/notes.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/opportunities.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/projects.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/tasks.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/firm-examples/workflows.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/contacts.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/events.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/households.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/lookups.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/notes.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/opportunities.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/projects.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/references/tasks.md +0 -0
- {wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/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.1.
|
|
3
|
+
Version: 1.1.2
|
|
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/
|
|
@@ -166,9 +166,12 @@ This updates the generated half of the skill's firm files; hand-edited policy is
|
|
|
166
166
|
wbox skills list # show where the skill is installed + last bootstrap time
|
|
167
167
|
wbox skills doctor # diagnose install state + token
|
|
168
168
|
wbox skills sync # copy firm/ from one install to others (Claude Code <-> Codex)
|
|
169
|
+
wbox skills upgrade # refresh template files (SKILL.md, references/, ...) preserving firm/
|
|
169
170
|
wbox skills uninstall # remove the skill
|
|
170
171
|
```
|
|
171
172
|
|
|
173
|
+
When the CLI ships a newer template (improved SKILL.md, new reference docs, etc.), `wbox skills upgrade` re-copies the bundled template into every installed platform without touching your firm-specific data in `firm/`. Each install records the template's CLI version in `_meta.json`, so the agent can detect drift and prompt you to upgrade when needed.
|
|
174
|
+
|
|
172
175
|
`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.
|
|
173
176
|
|
|
174
177
|
### Works with other agents too
|
|
@@ -125,9 +125,12 @@ This updates the generated half of the skill's firm files; hand-edited policy is
|
|
|
125
125
|
wbox skills list # show where the skill is installed + last bootstrap time
|
|
126
126
|
wbox skills doctor # diagnose install state + token
|
|
127
127
|
wbox skills sync # copy firm/ from one install to others (Claude Code <-> Codex)
|
|
128
|
+
wbox skills upgrade # refresh template files (SKILL.md, references/, ...) preserving firm/
|
|
128
129
|
wbox skills uninstall # remove the skill
|
|
129
130
|
```
|
|
130
131
|
|
|
132
|
+
When the CLI ships a newer template (improved SKILL.md, new reference docs, etc.), `wbox skills upgrade` re-copies the bundled template into every installed platform without touching your firm-specific data in `firm/`. Each install records the template's CLI version in `_meta.json`, so the agent can detect drift and prompt you to upgrade when needed.
|
|
133
|
+
|
|
131
134
|
`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.
|
|
132
135
|
|
|
133
136
|
### Works with other agents too
|
|
@@ -86,11 +86,11 @@ def render_users_md(users: list[dict[str, Any]]) -> str:
|
|
|
86
86
|
FIRM_README = """# firm/ — Firm Customizations
|
|
87
87
|
|
|
88
88
|
This folder holds everything the agent needs to know about your firm's
|
|
89
|
-
Wealthbox conventions.
|
|
89
|
+
Wealthbox conventions. Install metadata lives in `../_meta.json` (skill
|
|
90
|
+
root), not here.
|
|
90
91
|
|
|
91
92
|
## Generated files (overwritten by `wbox skills refresh`)
|
|
92
93
|
|
|
93
|
-
- `_meta.json` — refresh timestamps, firm identity, CLI version
|
|
94
94
|
- `categories.md` — every category type + valid values
|
|
95
95
|
- `custom-fields.md` — custom fields per document type + valid values
|
|
96
96
|
- `users.md` — user id → name → email
|
|
@@ -139,22 +139,52 @@ def write_stubs(firm_dir: Path) -> list[str]:
|
|
|
139
139
|
return written
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def
|
|
143
|
-
|
|
142
|
+
def meta_path(skill_dir: Path) -> Path:
|
|
143
|
+
"""Location of _meta.json relative to a skill install root."""
|
|
144
|
+
return skill_dir / "_meta.json"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def read_meta(skill_dir: Path) -> dict[str, Any]:
|
|
148
|
+
"""Read _meta.json. Returns {} if missing or unreadable."""
|
|
149
|
+
p = meta_path(skill_dir)
|
|
150
|
+
if not p.exists():
|
|
151
|
+
return {}
|
|
152
|
+
try:
|
|
153
|
+
loaded = json.loads(p.read_text(encoding="utf-8"))
|
|
154
|
+
except (json.JSONDecodeError, OSError):
|
|
155
|
+
return {}
|
|
156
|
+
return loaded if isinstance(loaded, dict) else {}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def write_meta(skill_dir: Path, data: dict[str, Any]) -> None:
|
|
160
|
+
meta_path(skill_dir).write_text(
|
|
161
|
+
json.dumps(data, indent=2) + "\n", encoding="utf-8"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def update_template_meta(skill_dir: Path, *, cli_version: str) -> None:
|
|
166
|
+
"""Set the template section of _meta.json. Preserves any existing firm section."""
|
|
167
|
+
meta = read_meta(skill_dir)
|
|
168
|
+
meta["template"] = {"cli_version": cli_version}
|
|
169
|
+
write_meta(skill_dir, meta)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def update_firm_meta(
|
|
173
|
+
skill_dir: Path,
|
|
144
174
|
*,
|
|
145
|
-
|
|
146
|
-
firm_identity: dict[str, Any],
|
|
175
|
+
identity: dict[str, Any],
|
|
147
176
|
cli_version: str,
|
|
177
|
+
generated_files: list[str],
|
|
148
178
|
) -> None:
|
|
179
|
+
"""Set the firm section of _meta.json. Preserves any existing template section."""
|
|
149
180
|
now = datetime.now(timezone.utc).isoformat()
|
|
150
|
-
meta =
|
|
151
|
-
|
|
181
|
+
meta = read_meta(skill_dir)
|
|
182
|
+
meta["firm"] = {
|
|
183
|
+
"identity": identity,
|
|
152
184
|
"cli_version": cli_version,
|
|
153
185
|
"files": {name: now for name in generated_files},
|
|
154
186
|
}
|
|
155
|
-
(
|
|
156
|
-
json.dumps(meta, indent=2) + "\n", encoding="utf-8"
|
|
157
|
-
)
|
|
187
|
+
write_meta(skill_dir, meta)
|
|
158
188
|
|
|
159
189
|
|
|
160
190
|
# --------------------------------------------------------------------------- #
|
|
@@ -230,11 +260,11 @@ def bootstrap_skill_dir(
|
|
|
230
260
|
(firm_dir / "users.md").write_text(render_users_md(users), encoding="utf-8")
|
|
231
261
|
|
|
232
262
|
generated = ["categories.md", "custom-fields.md", "users.md"]
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
firm_identity=firm_identity,
|
|
263
|
+
update_firm_meta(
|
|
264
|
+
skill_dir,
|
|
265
|
+
identity=firm_identity,
|
|
237
266
|
cli_version=_pkg_version("wealthbox-cli"),
|
|
267
|
+
generated_files=generated,
|
|
238
268
|
)
|
|
239
269
|
|
|
240
270
|
wrote_stubs = 0
|
|
@@ -69,10 +69,36 @@ def install_skill(platform: Platform, src: Path, *, force: bool) -> None:
|
|
|
69
69
|
_safe_rmtree(platform, dest)
|
|
70
70
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
71
71
|
shutil.copytree(src, dest)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
_rename_skill_md(platform, dest)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def upgrade_skill(platform: Platform, src: Path) -> None:
|
|
76
|
+
"""Re-copy the bundled template over an existing install.
|
|
77
|
+
|
|
78
|
+
Preserves the user's `firm/` directory and root `_meta.json` (the
|
|
79
|
+
bundled template ships neither). Refreshes SKILL.md, references/,
|
|
80
|
+
firm-examples/, and bootstrap.md.
|
|
81
|
+
"""
|
|
82
|
+
dest = skill_dir(platform)
|
|
83
|
+
if not dest.is_dir() or not (dest / platform.skill_filename).is_file():
|
|
84
|
+
raise SkillInstallError(
|
|
85
|
+
f"Cannot upgrade: nothing installed at {dest}"
|
|
86
|
+
)
|
|
87
|
+
shutil.copytree(src, dest, dirs_exist_ok=True)
|
|
88
|
+
_rename_skill_md(platform, dest)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _rename_skill_md(platform: Platform, dest: Path) -> None:
|
|
92
|
+
"""Rename SKILL.md → platform.skill_filename if needed (e.g. AGENTS.md for codex)."""
|
|
93
|
+
if platform.skill_filename == "SKILL.md":
|
|
94
|
+
return
|
|
95
|
+
skill_md = dest / "SKILL.md"
|
|
96
|
+
target = dest / platform.skill_filename
|
|
97
|
+
if not skill_md.exists():
|
|
98
|
+
return
|
|
99
|
+
if target.exists():
|
|
100
|
+
target.unlink()
|
|
101
|
+
skill_md.rename(target)
|
|
76
102
|
|
|
77
103
|
|
|
78
104
|
def uninstall_skill(platform: Platform) -> None:
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import shutil
|
|
5
4
|
from datetime import datetime, timezone
|
|
5
|
+
from importlib.metadata import version as _pkg_version
|
|
6
6
|
from importlib.resources import as_file, files
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
+
from ._skill_bootstrap import read_meta, update_template_meta
|
|
11
12
|
from ._skill_platforms import (
|
|
12
13
|
Platform,
|
|
13
14
|
SkillInstallError,
|
|
@@ -16,6 +17,7 @@ from ._skill_platforms import (
|
|
|
16
17
|
is_installed,
|
|
17
18
|
skill_dir,
|
|
18
19
|
uninstall_skill,
|
|
20
|
+
upgrade_skill,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
app = typer.Typer(
|
|
@@ -27,31 +29,31 @@ app = typer.Typer(
|
|
|
27
29
|
|
|
28
30
|
@app.command("list", help="Show where the skill is installed per platform.")
|
|
29
31
|
def list_platforms() -> None:
|
|
30
|
-
header = ("platform", "path", "status", "last-bootstrap")
|
|
31
|
-
rows: list[tuple[str, str, str, str]] = []
|
|
32
|
+
header = ("platform", "path", "status", "template-version", "last-bootstrap")
|
|
33
|
+
rows: list[tuple[str, str, str, str, str]] = []
|
|
32
34
|
for p in detect_platforms():
|
|
33
35
|
dest = skill_dir(p)
|
|
34
36
|
installed = is_installed(p)
|
|
35
|
-
|
|
37
|
+
template_version = "-"
|
|
36
38
|
last_bootstrap = "-"
|
|
37
|
-
if
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
last_bootstrap = "(unreadable)"
|
|
39
|
+
if installed:
|
|
40
|
+
meta = read_meta(dest)
|
|
41
|
+
template_version = meta.get("template", {}).get("cli_version", "-")
|
|
42
|
+
firm_section = meta.get("firm") or {}
|
|
43
|
+
firm_files = firm_section.get("files") or {}
|
|
44
|
+
if firm_files:
|
|
45
|
+
last_bootstrap = max(firm_files.values())
|
|
45
46
|
rows.append((
|
|
46
47
|
p.id,
|
|
47
48
|
str(dest),
|
|
48
49
|
"installed" if installed else "not installed",
|
|
50
|
+
template_version,
|
|
49
51
|
last_bootstrap,
|
|
50
52
|
))
|
|
51
53
|
|
|
52
54
|
widths = [
|
|
53
55
|
max(len(r[i]) for r in ([header] + rows))
|
|
54
|
-
for i in range(
|
|
56
|
+
for i in range(len(header))
|
|
55
57
|
]
|
|
56
58
|
fmt = " ".join(f"{{:<{w}}}" for w in widths)
|
|
57
59
|
typer.echo(fmt.format(*header))
|
|
@@ -113,6 +115,7 @@ def install_cmd(
|
|
|
113
115
|
except SkillInstallError as e:
|
|
114
116
|
typer.echo(f"Error installing to {target.id}: {e}", err=True)
|
|
115
117
|
raise typer.Exit(code=1) from e
|
|
118
|
+
update_template_meta(skill_dir(target), cli_version=_pkg_version("wealthbox-cli"))
|
|
116
119
|
typer.echo(f"OK installed to {skill_dir(target)}")
|
|
117
120
|
|
|
118
121
|
if not no_bootstrap:
|
|
@@ -192,26 +195,65 @@ def refresh_cmd(
|
|
|
192
195
|
raise typer.Exit(code=2)
|
|
193
196
|
|
|
194
197
|
for t in targets:
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
meta = read_meta(skill_dir(t))
|
|
199
|
+
firm_files = (meta.get("firm") or {}).get("files") or {}
|
|
200
|
+
if firm_files:
|
|
197
201
|
try:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
err=True,
|
|
207
|
-
)
|
|
208
|
-
except (json.JSONDecodeError, OSError, ValueError):
|
|
202
|
+
oldest = min(datetime.fromisoformat(ts) for ts in firm_files.values())
|
|
203
|
+
age_days = (datetime.now(timezone.utc) - oldest).days
|
|
204
|
+
if age_days > staleness_days:
|
|
205
|
+
typer.echo(
|
|
206
|
+
f"! {t.id}: generated files were {age_days} days stale - refreshing now",
|
|
207
|
+
err=True,
|
|
208
|
+
)
|
|
209
|
+
except ValueError:
|
|
209
210
|
pass
|
|
210
211
|
|
|
211
212
|
bootstrap_skill_dir(skill_dir(t), token=token, generated_only=True)
|
|
212
213
|
typer.echo(f"OK refreshed {t.id}")
|
|
213
214
|
|
|
214
215
|
|
|
216
|
+
@app.command(
|
|
217
|
+
"upgrade",
|
|
218
|
+
help="Refresh template files (SKILL.md, references/, firm-examples/, bootstrap.md) "
|
|
219
|
+
"in every installed platform. Preserves firm/ and _meta.json firm section.",
|
|
220
|
+
)
|
|
221
|
+
def upgrade_cmd(
|
|
222
|
+
platforms_flag: list[str] = typer.Option(
|
|
223
|
+
[], "--platform", "-p",
|
|
224
|
+
help="Platform id. Default: every installed platform.",
|
|
225
|
+
),
|
|
226
|
+
) -> None:
|
|
227
|
+
if platforms_flag:
|
|
228
|
+
targets = _resolve_platforms(platforms_flag)
|
|
229
|
+
for t in targets:
|
|
230
|
+
if not is_installed(t):
|
|
231
|
+
typer.echo(f"Error: {t.id!r} is not installed.", err=True)
|
|
232
|
+
raise typer.Exit(code=2)
|
|
233
|
+
else:
|
|
234
|
+
targets = [p for p in detect_platforms() if is_installed(p)]
|
|
235
|
+
|
|
236
|
+
if not targets:
|
|
237
|
+
typer.echo(
|
|
238
|
+
"No installed platforms found. Run 'wbox skills install' first.",
|
|
239
|
+
err=True,
|
|
240
|
+
)
|
|
241
|
+
raise typer.Exit(code=2)
|
|
242
|
+
|
|
243
|
+
current = _pkg_version("wealthbox-cli")
|
|
244
|
+
|
|
245
|
+
with as_file(files("wealthbox_tools").joinpath("skills/wealthbox-crm")) as src:
|
|
246
|
+
for t in targets:
|
|
247
|
+
existing = (read_meta(skill_dir(t)).get("template") or {}).get("cli_version", "-")
|
|
248
|
+
try:
|
|
249
|
+
upgrade_skill(t, Path(src))
|
|
250
|
+
except SkillInstallError as e:
|
|
251
|
+
typer.echo(f"Error upgrading {t.id}: {e}", err=True)
|
|
252
|
+
raise typer.Exit(code=1) from e
|
|
253
|
+
update_template_meta(skill_dir(t), cli_version=current)
|
|
254
|
+
typer.echo(f"OK upgraded {t.id}: {existing} -> {current}")
|
|
255
|
+
|
|
256
|
+
|
|
215
257
|
@app.command("doctor", help="Diagnose install state and token.")
|
|
216
258
|
def doctor_cmd(
|
|
217
259
|
token: str | None = typer.Option(None, envvar="WEALTHBOX_TOKEN", hidden=True),
|
|
@@ -220,13 +262,19 @@ def doctor_cmd(
|
|
|
220
262
|
|
|
221
263
|
from ._util import run_client
|
|
222
264
|
|
|
265
|
+
current = _pkg_version("wealthbox-cli")
|
|
223
266
|
typer.echo("# Skill install state")
|
|
224
267
|
for p in detect_platforms():
|
|
225
268
|
status = "installed" if is_installed(p) else "not installed"
|
|
226
|
-
|
|
227
|
-
bootstrapped = "bootstrapped" if
|
|
269
|
+
meta = read_meta(skill_dir(p)) if is_installed(p) else {}
|
|
270
|
+
bootstrapped = "bootstrapped" if meta.get("firm") else "not bootstrapped"
|
|
271
|
+
template_v = (meta.get("template") or {}).get("cli_version", "-")
|
|
272
|
+
upgrade_hint = ""
|
|
273
|
+
if template_v not in ("-", current):
|
|
274
|
+
upgrade_hint = f" [upgrade available: {template_v} -> {current}]"
|
|
228
275
|
typer.echo(
|
|
229
|
-
f" {p.id:<22} {status:<16} {bootstrapped:<18}
|
|
276
|
+
f" {p.id:<22} {status:<16} {bootstrapped:<18} "
|
|
277
|
+
f"template={template_v:<10} {skill_dir(p)}{upgrade_hint}"
|
|
230
278
|
)
|
|
231
279
|
|
|
232
280
|
typer.echo("\n# Token")
|
{wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/SKILL.md
RENAMED
|
@@ -8,14 +8,34 @@ description: Use when the user wants to interact with Wealthbox CRM — creating
|
|
|
8
8
|
Execute Wealthbox CRM operations via the `wbox` CLI. Supports contacts, tasks, notes, events,
|
|
9
9
|
opportunities, projects, workflows, households, and read-only lookups (categories, users, activity, me).
|
|
10
10
|
|
|
11
|
-
## First Run
|
|
11
|
+
## First Run
|
|
12
12
|
|
|
13
|
-
If `
|
|
14
|
-
|
|
13
|
+
If `_meta.json` in this skill's directory does **not** contain a `firm`
|
|
14
|
+
section, the firm has not been onboarded yet on this machine. Before
|
|
15
|
+
handling the user's request:
|
|
15
16
|
|
|
16
17
|
1. Read `bootstrap.md` in this skill's directory.
|
|
17
18
|
2. Follow it end-to-end.
|
|
18
|
-
3.
|
|
19
|
+
3. The bootstrap will populate `_meta.json.firm` so this check passes
|
|
20
|
+
on subsequent runs.
|
|
21
|
+
|
|
22
|
+
## Staleness Check
|
|
23
|
+
|
|
24
|
+
`_meta.json.template.cli_version` records which `wbox` version installed
|
|
25
|
+
the template files. Compare it to the running CLI version:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
wbox --version
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If the template version is older, suggest the user run:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
wbox skills upgrade
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This refreshes SKILL.md, references/, firm-examples/, and bootstrap.md
|
|
38
|
+
across every installed platform while preserving `firm/`.
|
|
19
39
|
|
|
20
40
|
## Prerequisites
|
|
21
41
|
|
{wealthbox_cli-1.1.1 → wealthbox_cli-1.1.2}/src/wealthbox_tools/skills/wealthbox-crm/bootstrap.md
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# First-Run Bootstrap
|
|
2
2
|
|
|
3
|
-
You were told to read this because `
|
|
4
|
-
|
|
5
|
-
the `## First Run` section out of `SKILL.md`.
|
|
3
|
+
You were told to read this because `_meta.json` in the skill root does not
|
|
4
|
+
contain a `firm` section. Follow every step below.
|
|
6
5
|
|
|
7
6
|
## Step 1 — Fetch API-derived firm data
|
|
8
7
|
|
|
@@ -17,12 +16,13 @@ It populates these files per installed platform:
|
|
|
17
16
|
- `firm/categories.md` — every category type + valid values
|
|
18
17
|
- `firm/custom-fields.md` — custom fields per document type + valid values
|
|
19
18
|
- `firm/users.md` — user id → name → email
|
|
20
|
-
- `
|
|
19
|
+
- `_meta.json` (skill root) — adds the `firm` section: identity, refresh
|
|
20
|
+
timestamps, and the CLI version that ran the bootstrap
|
|
21
21
|
- Stub files: `firm/contacts.md`, `firm/tasks.md`, `firm/notes.md`,
|
|
22
22
|
`firm/events.md`, `firm/opportunities.md`, `firm/projects.md`,
|
|
23
23
|
`firm/workflows.md` — one-line TODOs you will fill in next.
|
|
24
24
|
|
|
25
|
-
Verify `firm
|
|
25
|
+
Verify the `firm` section now exists in `_meta.json` before continuing.
|
|
26
26
|
|
|
27
27
|
## Step 2 — Gather firm policy (per-resource)
|
|
28
28
|
|
|
@@ -90,16 +90,11 @@ Ask the advisor: "Is there any firm convention, custom process, or preference
|
|
|
90
90
|
I haven't asked about that I should follow?" Append freeform answers to
|
|
91
91
|
`firm/workflows.md` under a `## Other Conventions` heading.
|
|
92
92
|
|
|
93
|
-
## Step 4 —
|
|
94
|
-
|
|
95
|
-
Now that all firm files are filled in:
|
|
96
|
-
|
|
97
|
-
1. Delete `bootstrap.md` (this file).
|
|
98
|
-
2. Open `SKILL.md`. Find the `## First Run (self-delete after completion)`
|
|
99
|
-
section. Delete that heading and everything under it through (but not
|
|
100
|
-
including) the next `## ` heading.
|
|
101
|
-
|
|
102
|
-
## Step 5 — Confirm and continue
|
|
93
|
+
## Step 4 — Confirm and continue
|
|
103
94
|
|
|
104
95
|
Tell the advisor: "First-run setup complete — `firm/` populated for <firm
|
|
105
96
|
name>." Then return to the user's original request.
|
|
97
|
+
|
|
98
|
+
You don't need to delete this file or trim `SKILL.md`. The first-run check
|
|
99
|
+
keys on `_meta.json.firm`, which now exists, so the bootstrap path will be
|
|
100
|
+
skipped on every subsequent invocation automatically.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|