thoughtleaders-cli 0.7.11__tar.gz → 0.7.12__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.
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/.claude-plugin/plugin.json +1 -1
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/PKG-INFO +1 -1
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/pyproject.toml +1 -1
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/SKILL.md +1 -1
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/elasticsearch-schema.md +18 -12
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/__init__.py +1 -1
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/setup.py +55 -7
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_setup.py +44 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/.claude-plugin/marketplace.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/.github/workflows/python-publish.yml +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/.gitignore +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/AGENTS.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/API.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/CLAUDE.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/LICENSE +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/README.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/agents/tl-analyst.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/agents/youtube-comment-classifier.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/hooks/hooks.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/hooks/scripts/load-tl-skill.mjs +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/hooks/scripts/post-usage.sh +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/hooks/scripts/pre-check.sh +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/business-glossary.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/firebolt-schema.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/postgres-schema.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/.gitignore +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/references/comment-patterns.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/references/peer-cohort.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/references/red-flags.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/references/scoring.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/_io_utf8.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/analyze_channel.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/anomaly_detector.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/comment_analyzer.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/comment_scraper.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/engagement_ratios.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/peer_cohort.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/report.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/resolve_channel.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/score.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/tl_cli.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/video_integrity.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/scripts/view_curves.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-keyword-research/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-keyword-research/scripts/probe.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/columns_brands.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/columns_channels.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/columns_content.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/columns_sponsorships.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/intelligence_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/intelligence_widget_schema.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/report_glossary.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/sortable_columns.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/sponsorship_filterset_schema.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/sponsorship_widget_schema.json +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/widgets.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-top-partnerships/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-top-partnerships/scripts/top_partnerships.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-views-guarantee/SKILL.md +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-views-guarantee/scripts/vg.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/_completions.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/_typer_utils.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/auth/__init__.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/auth/commands.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/auth/login.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/auth/pkce.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/auth/token_store.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/client/__init__.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/client/errors.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/client/http.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/__init__.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/_comments_common.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/balance.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/brands.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/bulk_import.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/changelog.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/channels.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/credits.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/db.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/deals.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/describe.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/doctor.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/matches.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/proposals.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/recommender.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/reports.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/schema.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/snapshots.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/sponsorships.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/uploads.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/whoami.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/config.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/filters.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/hints.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/main.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/output/__init__.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/output/formatter.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/self_update.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/__init__.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_auth.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_describe.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_filters.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_http_auth.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_output.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_reports.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/tests/test_sponsorships.py +0 -0
- {thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtleaders-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.12
|
|
4
4
|
Summary: ThoughtLeaders CLI — query sponsorship data, channels, brands, and intelligence
|
|
5
5
|
Project-URL: Homepage, https://thoughtleaders.io
|
|
6
6
|
Project-URL: Repository, https://github.com/ThoughtLeaders-io/thoughtleaders-cli
|
|
@@ -446,7 +446,7 @@ If unsure about what information to find where, read the [references/postgresql-
|
|
|
446
446
|
| Pre-insert validation queries (joining `adspot ↔ channel ↔ profile ↔ org` to confirm MSN, integration=1, persona, plan) | **Available** via `tl db pg`. | One SELECT joining the four tables. Use `thoughtleaders_channel.media_selling_network_join_date IS NOT NULL` for MSN, `thoughtleaders_adspot.integration = 1` for mention adspots, `thoughtleaders_profile.persona` for the persona code (see persona constants in `references/postgres-schema.md`). |
|
|
447
447
|
| Firebolt cross-table or join queries; filtering on non-indexed columns in WHERE | **Unavailable** — not accepted. | Fetch a wider slice keyed on `channel_id` (and optionally `id`), filter the rest in `jq`/Python. |
|
|
448
448
|
| ES `query_string`, `regexp`, `wildcard`, `fuzzy`, `more_like_this`, parent/child joins; any `script_*`; multiple aggregations in one body | **Unavailable** — not accepted. | Rewrite using `term`/`terms`/`match`/`bool`/`nested`. For multi-agg dashboards, run multiple `tl db es` calls and combine client-side. For "similar"-style queries, try `tl channels similar` / `tl brands similar` (server-implemented similarity search). |
|
|
449
|
-
| ES deep pagination beyond `from+size = 10,000` | **
|
|
449
|
+
| ES deep pagination beyond `from+size = 10,000` | **Available** via `search_after` (stateless cursor); `scroll` and `pit` remain unavailable. | Sort with a unique tiebreaker (e.g. `id`), then pass the response envelope's `next_search_after` back as `search_after` in the next call, keeping `query`/`sort` identical and `from` at 0. See the ES reference's *Deep pagination* section. |
|
|
450
450
|
| ES index introspection (`_cat/indices`, mappings) | **Unavailable** — only `_search` is wired. | Read [references/elasticsearch-schema.md](references/elasticsearch-schema.md). It's manually maintained — update it when you discover new fields. |
|
|
451
451
|
| Schema introspection on Postgres (`information_schema.columns`, `pg_class`, …) | **Partial** — catalog-resolving casts and many `pg_*` helpers are blocked. | Use `tl schema pg` for the live table/column listing, or read [references/postgres-schema.md](references/postgres-schema.md). |
|
|
452
452
|
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/elasticsearch-schema.md
RENAMED
|
@@ -21,8 +21,9 @@ Output flags: `--json`, `--csv`, `--md`, `--toon`. The CLI flattens hits into ro
|
|
|
21
21
|
|
|
22
22
|
See the output of `tl db es`" for the object schema. Highlights:
|
|
23
23
|
|
|
24
|
-
- **Top-level keys** accepted: `query`, `aggs`/`aggregations`, `sort`, `_source`, `size`, `from`, `track_total_hits`, `highlight`, `fields`, `min_score`, `timeout`, `collapse`, `post_filter`. Anything else (incl. `scroll`, `pit`, `
|
|
25
|
-
- `size` ≤ 10,000. `from + size` ≤ 10,000 —
|
|
24
|
+
- **Top-level keys** accepted: `query`, `aggs`/`aggregations`, `sort`, `_source`, `size`, `from`, `search_after`, `track_total_hits`, `highlight`, `fields`, `min_score`, `timeout`, `collapse`, `post_filter`. Anything else (incl. `scroll`, `pit`, `runtime_mappings`, `knn`) is not accepted.
|
|
25
|
+
- `size` ≤ 10,000. `from + size` ≤ 10,000 — to page past 10,000 hits use `search_after` (see *Deep pagination* below), not `from`.
|
|
26
|
+
- `search_after` must be a non-empty array of ≤ 10 scalar sort values, requires an explicit `sort`, and `from` must be 0 or omitted.
|
|
26
27
|
- **Accepted query types** include `term`/`terms`/`match`/`bool`/`nested`/`range`/`exists`/`match_phrase`. `query_string`, `regexp`, `wildcard`, `fuzzy`, `more_like_this`, `has_child`, `has_parent`, `parent_id` are not accepted.
|
|
27
28
|
- **No scripts** — any key whose name contains `script` is not accepted.
|
|
28
29
|
- **At most one aggregation total** counted recursively (top-level + sub-agg = 2 = not accepted). Run multiple calls for multi-metric work.
|
|
@@ -215,24 +216,29 @@ tl db es '{
|
|
|
215
216
|
|
|
216
217
|
For more dimensions, run multiple `tl db es` calls and join client-side.
|
|
217
218
|
|
|
218
|
-
### Deep
|
|
219
|
+
### Deep pagination — `search_after`
|
|
219
220
|
|
|
220
|
-
`from + size` is capped at 10,000 and
|
|
221
|
+
`from + size` is capped at 10,000, and the stateful cursors (`scroll`, `pit`) are not accepted. To page past 10,000 hits, use the stateless `search_after` cursor: sort deterministically with a unique tiebreaker (the `id` field — not `_id`), then pass each response's `next_search_after` envelope value back as `search_after` in the next request, keeping the same `query` and `sort`:
|
|
221
222
|
|
|
222
223
|
```bash
|
|
223
|
-
#
|
|
224
|
+
# First page
|
|
224
225
|
tl db es '{
|
|
225
226
|
"size": 10000,
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
"query": {"term": {"channel.id": 12345}},
|
|
228
|
+
"sort": [{"publication_date": "asc"}, {"id": "asc"}]
|
|
229
|
+
}'
|
|
230
|
+
# → envelope includes "next_search_after": ["2025-09-14", "12345:abc123"]
|
|
231
|
+
|
|
232
|
+
# Next page — identical query & sort, plus the cursor
|
|
233
|
+
tl db es '{
|
|
234
|
+
"size": 10000,
|
|
235
|
+
"query": {"term": {"channel.id": 12345}},
|
|
236
|
+
"sort": [{"publication_date": "asc"}, {"id": "asc"}],
|
|
237
|
+
"search_after": ["2025-09-14", "12345:abc123"]
|
|
232
238
|
}'
|
|
233
239
|
```
|
|
234
240
|
|
|
235
|
-
|
|
241
|
+
Repeat until a page comes back short (`next_search_after` is absent on an empty page). Pages are not a consistent snapshot — concurrent indexing can occasionally duplicate or skip a boundary row, which is fine for analytics sweeps. Date-range windowing (filtering by `publication_date` ranges) remains a good alternative when you want resumable, idempotent slices.
|
|
236
242
|
|
|
237
243
|
## Text analyzer behavior
|
|
238
244
|
|
|
@@ -76,30 +76,66 @@ def _find_plugin_root() -> Path | None:
|
|
|
76
76
|
return None
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def _newest_desktop_claude(base: Path, exe: str) -> Path | None:
|
|
80
|
+
"""Pick the highest-version claude binary bundled by the Claude desktop app.
|
|
81
|
+
|
|
82
|
+
The desktop app keeps versioned copies under `<base>/<version>/<exe>`
|
|
83
|
+
(e.g. `%APPDATA%/Claude/claude-code/2.1.170/claude.exe`).
|
|
84
|
+
"""
|
|
85
|
+
if not base.is_dir():
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def _version_key(d: Path) -> tuple[int, ...]:
|
|
89
|
+
try:
|
|
90
|
+
return tuple(int(part) for part in d.name.split("."))
|
|
91
|
+
except ValueError:
|
|
92
|
+
return (0,)
|
|
93
|
+
|
|
94
|
+
for version_dir in sorted(base.iterdir(), key=_version_key, reverse=True):
|
|
95
|
+
candidate = version_dir / exe
|
|
96
|
+
if candidate.is_file():
|
|
97
|
+
return candidate
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
79
101
|
def _find_claude_binary() -> str | None:
|
|
80
102
|
"""Find the claude binary on PATH, falling back to known install locations.
|
|
81
103
|
|
|
82
104
|
On Windows the Claude Code installers often don't end up on the PATH of
|
|
83
105
|
the shell running `tl` (stale PATH, PowerShell-only profile changes), so
|
|
84
106
|
after `shutil.which` we probe the documented install targets directly:
|
|
85
|
-
the native installer (`~/.local/bin`)
|
|
107
|
+
the native installer (`~/.local/bin`), the npm global prefix, and the
|
|
108
|
+
binaries bundled with the Claude desktop app. When `tl` itself runs
|
|
109
|
+
inside a Claude Code session, `CLAUDE_CODE_EXECPATH` points straight at
|
|
110
|
+
the running binary and wins.
|
|
86
111
|
"""
|
|
112
|
+
env_exec = os.environ.get("CLAUDE_CODE_EXECPATH")
|
|
113
|
+
if env_exec and Path(env_exec).is_file():
|
|
114
|
+
return env_exec
|
|
87
115
|
found = shutil.which("claude")
|
|
88
116
|
if found:
|
|
89
117
|
return found
|
|
90
118
|
home = Path.home()
|
|
91
119
|
if sys.platform == "win32":
|
|
120
|
+
appdata = Path(os.environ.get("APPDATA", str(home / "AppData" / "Roaming")))
|
|
92
121
|
candidates = [
|
|
93
122
|
home / ".local" / "bin" / "claude.exe",
|
|
94
|
-
|
|
123
|
+
appdata / "npm" / "claude.cmd",
|
|
124
|
+
_newest_desktop_claude(appdata / "Claude" / "claude-code", "claude.exe"),
|
|
95
125
|
]
|
|
96
126
|
else:
|
|
127
|
+
desktop_base = (
|
|
128
|
+
home / "Library" / "Application Support" / "Claude" / "claude-code"
|
|
129
|
+
if sys.platform == "darwin"
|
|
130
|
+
else home / ".config" / "Claude" / "claude-code"
|
|
131
|
+
)
|
|
97
132
|
candidates = [
|
|
98
133
|
home / ".local" / "bin" / "claude",
|
|
99
134
|
home / ".claude" / "local" / "claude",
|
|
135
|
+
_newest_desktop_claude(desktop_base, "claude"),
|
|
100
136
|
]
|
|
101
137
|
for candidate in candidates:
|
|
102
|
-
if candidate.is_file():
|
|
138
|
+
if candidate is not None and candidate.is_file():
|
|
103
139
|
return str(candidate)
|
|
104
140
|
return None
|
|
105
141
|
|
|
@@ -204,10 +240,22 @@ def _install_command_shim() -> Path:
|
|
|
204
240
|
|
|
205
241
|
|
|
206
242
|
def _trees_identical(a: Path, b: Path) -> bool:
|
|
207
|
-
"""True if two directory trees contain the same files with the same contents.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
243
|
+
"""True if two directory trees contain the same files with the same contents.
|
|
244
|
+
|
|
245
|
+
Python runtime artifacts (`__pycache__/`, `*.pyc`) are ignored — skills
|
|
246
|
+
that ship scripts grow them when the scripts run, and they shouldn't make
|
|
247
|
+
an otherwise-pristine copy look user-modified.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def _files(root: Path) -> list[Path]:
|
|
251
|
+
return sorted(
|
|
252
|
+
p.relative_to(root)
|
|
253
|
+
for p in root.rglob("*")
|
|
254
|
+
if p.is_file() and p.suffix != ".pyc" and "__pycache__" not in p.parts
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
a_files = _files(a)
|
|
258
|
+
if a_files != _files(b):
|
|
211
259
|
return False
|
|
212
260
|
return all(filecmp.cmp(a / rel, b / rel, shallow=False) for rel in a_files)
|
|
213
261
|
|
|
@@ -64,6 +64,16 @@ class TestTreesIdentical:
|
|
|
64
64
|
(d / "f.md").write_text(body, encoding="utf-8")
|
|
65
65
|
assert not _trees_identical(tmp_path / "a", tmp_path / "b")
|
|
66
66
|
|
|
67
|
+
def test_ignores_pycache_artifacts(self, tmp_path):
|
|
68
|
+
for root in ("a", "b"):
|
|
69
|
+
d = tmp_path / root / "scripts"
|
|
70
|
+
d.mkdir(parents=True)
|
|
71
|
+
(d / "run.py").write_text("print()", encoding="utf-8")
|
|
72
|
+
cache = tmp_path / "b" / "scripts" / "__pycache__"
|
|
73
|
+
cache.mkdir()
|
|
74
|
+
(cache / "run.cpython-313.pyc").write_text("bytecode", encoding="utf-8")
|
|
75
|
+
assert _trees_identical(tmp_path / "a", tmp_path / "b")
|
|
76
|
+
|
|
67
77
|
def test_extra_file(self, tmp_path):
|
|
68
78
|
for root in ("a", "b"):
|
|
69
79
|
d = tmp_path / root
|
|
@@ -128,11 +138,44 @@ class TestInstallCommandShim:
|
|
|
128
138
|
|
|
129
139
|
|
|
130
140
|
class TestFindClaudeBinary:
|
|
141
|
+
def test_prefers_execpath_env(self, tmp_path, monkeypatch):
|
|
142
|
+
exe = tmp_path / "claude.exe"
|
|
143
|
+
exe.write_text("", encoding="utf-8")
|
|
144
|
+
monkeypatch.setenv("CLAUDE_CODE_EXECPATH", str(exe))
|
|
145
|
+
assert _find_claude_binary() == str(exe)
|
|
146
|
+
|
|
147
|
+
def test_ignores_stale_execpath_env(self, tmp_path, monkeypatch):
|
|
148
|
+
monkeypatch.setenv("CLAUDE_CODE_EXECPATH", str(tmp_path / "gone.exe"))
|
|
149
|
+
monkeypatch.setattr(setup.shutil, "which", lambda _: "/somewhere/claude")
|
|
150
|
+
assert _find_claude_binary() == "/somewhere/claude"
|
|
151
|
+
|
|
131
152
|
def test_prefers_path(self, monkeypatch):
|
|
153
|
+
monkeypatch.delenv("CLAUDE_CODE_EXECPATH", raising=False)
|
|
132
154
|
monkeypatch.setattr(setup.shutil, "which", lambda _: "/somewhere/claude")
|
|
133
155
|
assert _find_claude_binary() == "/somewhere/claude"
|
|
134
156
|
|
|
157
|
+
def test_finds_newest_desktop_app_binary(self, tmp_path, monkeypatch):
|
|
158
|
+
monkeypatch.delenv("CLAUDE_CODE_EXECPATH", raising=False)
|
|
159
|
+
monkeypatch.setattr(setup.shutil, "which", lambda _: None)
|
|
160
|
+
monkeypatch.setattr(setup.Path, "home", staticmethod(lambda: tmp_path))
|
|
161
|
+
if sys.platform == "win32":
|
|
162
|
+
base = tmp_path / "AppData" / "Roaming" / "Claude" / "claude-code"
|
|
163
|
+
monkeypatch.setenv("APPDATA", str(tmp_path / "AppData" / "Roaming"))
|
|
164
|
+
exe = "claude.exe"
|
|
165
|
+
elif sys.platform == "darwin":
|
|
166
|
+
base = tmp_path / "Library" / "Application Support" / "Claude" / "claude-code"
|
|
167
|
+
exe = "claude"
|
|
168
|
+
else:
|
|
169
|
+
base = tmp_path / ".config" / "Claude" / "claude-code"
|
|
170
|
+
exe = "claude"
|
|
171
|
+
for version in ("2.1.165", "2.1.170"):
|
|
172
|
+
d = base / version
|
|
173
|
+
d.mkdir(parents=True)
|
|
174
|
+
(d / exe).write_text("", encoding="utf-8")
|
|
175
|
+
assert _find_claude_binary() == str(base / "2.1.170" / exe)
|
|
176
|
+
|
|
135
177
|
def test_falls_back_to_local_bin(self, tmp_path, monkeypatch):
|
|
178
|
+
monkeypatch.delenv("CLAUDE_CODE_EXECPATH", raising=False)
|
|
136
179
|
monkeypatch.setattr(setup.shutil, "which", lambda _: None)
|
|
137
180
|
monkeypatch.setattr(setup.Path, "home", staticmethod(lambda: tmp_path))
|
|
138
181
|
exe = "claude.exe" if sys.platform == "win32" else "claude"
|
|
@@ -142,6 +185,7 @@ class TestFindClaudeBinary:
|
|
|
142
185
|
assert _find_claude_binary() == str(target)
|
|
143
186
|
|
|
144
187
|
def test_not_found_anywhere(self, tmp_path, monkeypatch):
|
|
188
|
+
monkeypatch.delenv("CLAUDE_CODE_EXECPATH", raising=False)
|
|
145
189
|
monkeypatch.setattr(setup.shutil, "which", lambda _: None)
|
|
146
190
|
monkeypatch.setattr(setup.Path, "home", staticmethod(lambda: tmp_path))
|
|
147
191
|
if sys.platform == "win32":
|
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/.github/workflows/python-publish.yml
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
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/agents/youtube-comment-classifier.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/business-glossary.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/firebolt-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl/references/postgres-schema.md
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/.gitignore
RENAMED
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-channel-authenticity/SKILL.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-keyword-research/scripts/probe.py
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
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-save-report/references/widgets.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/skills/tl-views-guarantee/scripts/vg.py
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
|
{thoughtleaders_cli-0.7.11 → thoughtleaders_cli-0.7.12}/src/tl_cli/commands/_comments_common.py
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
|
|
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
|