primethink-cli 1.3.0__tar.gz → 1.3.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.
Files changed (41) hide show
  1. {primethink_cli-1.3.0/primethink_cli.egg-info → primethink_cli-1.3.2}/PKG-INFO +1 -1
  2. primethink_cli-1.3.2/docs/superpowers/specs/2026-08-16-install-attached-skills-design.md +184 -0
  3. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink.py +16 -5
  4. {primethink_cli-1.3.0 → primethink_cli-1.3.2/primethink_cli.egg-info}/PKG-INFO +1 -1
  5. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink_cli.egg-info/SOURCES.txt +1 -0
  6. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/pyproject.toml +1 -1
  7. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_cli.py +7 -2
  8. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/DEVELOPER.md +0 -0
  9. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/LICENSE +0 -0
  10. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/MANIFEST.in +0 -0
  11. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/README.md +0 -0
  12. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/SPECS.md +0 -0
  13. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/USER_GUIDE.md +0 -0
  14. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/docs/cli-reference.md +0 -0
  15. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/docs/install.md +0 -0
  16. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/docs/primethink_help_llms.txt +0 -0
  17. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/install/homebrew/primethink-cli.rb +0 -0
  18. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/install/install.cmd +0 -0
  19. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/install/install.ps1 +0 -0
  20. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/install/install.sh +0 -0
  21. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink_cli.egg-info/dependency_links.txt +0 -0
  22. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink_cli.egg-info/entry_points.txt +0 -0
  23. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink_cli.egg-info/requires.txt +0 -0
  24. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink_cli.egg-info/top_level.txt +0 -0
  25. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/primethink_mcp.py +0 -0
  26. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/scripts/build.sh +0 -0
  27. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/scripts/deploy.sh +0 -0
  28. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/scripts/publish.sh +0 -0
  29. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/setup.cfg +0 -0
  30. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/setup.py +0 -0
  31. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/skills/__init__.py +0 -0
  32. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/skills/primethink-cli/SKILL.md +0 -0
  33. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_agent_commands.py +0 -0
  34. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_env_config.py +0 -0
  35. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_install_developer_skill.py +0 -0
  36. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_install_skill.py +0 -0
  37. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_live_app.py +0 -0
  38. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_mcp_server.py +0 -0
  39. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_search_commands.py +0 -0
  40. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_task_commands.py +0 -0
  41. {primethink_cli-1.3.0 → primethink_cli-1.3.2}/tests/test_whoami.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: primethink-cli
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: PrimeThink CLI - A powerful tool for interacting with PrimeThink AI API
5
5
  Author-email: PrimeThink <support@primethink.ai>
6
6
  License-Expression: MIT
@@ -0,0 +1,184 @@
1
+ # Design: Install skills attached to a chat or agent
2
+
3
+ **Date:** 2026-08-16
4
+ **Status:** Approved for planning
5
+
6
+ ## Purpose
7
+
8
+ Add CLI commands that download and install every skill attached to a PrimeThink
9
+ chat and/or agent into a local Claude-Code-style skills directory. The primary
10
+ use case is a sandboxed agent turn, where `PT_CHAT_UUID` / `PT_AGENT_ID` are in
11
+ the environment and a single zero-argument command should fetch everything the
12
+ current chat and agent carry.
13
+
14
+ ## Background: how skills are modeled in the API
15
+
16
+ Skills are collections, in two flavors:
17
+
18
+ 1. **`skill` collections** — `type == "skill"`. The collection's document tree
19
+ *is* the skill's file tree; it is downloaded through the same per-collection
20
+ document endpoints that `pt collections sync-from` uses.
21
+ 2. **GitHub-backed skills** — `type == "external_source"` with
22
+ `external_source_type == "github_skill"`. `external_source_value` holds a
23
+ **full GitHub URL** pointing at the skill (repo root, or a
24
+ `/tree/<ref>/<path>` / `/blob/<ref>/<path>` URL naming a subdirectory).
25
+
26
+ Attachment discovery:
27
+
28
+ - **Chat:** `GET /api/v1/chats/{chat_id_or_uuid}/collections` — the path
29
+ parameter accepts an integer ID or a chat UUID. Returns
30
+ `List[BaseCollectionResponse]` including `type`, `external_source_type`,
31
+ `external_source_value`, and the link `status`.
32
+ - **Agent:** `GET /api/v1/virtual-assistants/{va_id}/collections` — integer ID
33
+ only. Returns `BaseCollectionResponse` plus per-agent link fields `hidden`
34
+ and `status`.
35
+
36
+ There is no server-side `?type=` filter; filtering is client-side, matching
37
+ what the Deep1 backend itself does internally (`_load_skills`).
38
+
39
+ Both endpoints authenticate with the standard token, so the CLI's existing
40
+ `_resolve_connection` / `PT_TOKEN` flow works unchanged.
41
+
42
+ ## Commands
43
+
44
+ Three commands sharing one implementation:
45
+
46
+ ### `pt install-skills` (top-level)
47
+
48
+ Installs skills from the current chat **and** its agent by default.
49
+
50
+ - Chat default: `PT_CHAT_UUID` env var, falling back to `PT_CHAT_ID`.
51
+ - Agent default: `PT_AGENT_ID` env var.
52
+ - `--chat-id TEXT` / `--agent-id INT` — explicit overrides.
53
+ - `--chat-only` / `--agent-only` — restrict to one side (mutually exclusive).
54
+ - Standard installer options: `--user` (default, `~/.claude/skills`),
55
+ `--project` (`./.claude/skills`), `--dir PATH`, `--force`.
56
+ - Standard connection options (`_connection_options`).
57
+ - Errors cleanly (exit 1) if, after env fallback, neither a chat nor an agent
58
+ can be resolved for the requested sides.
59
+
60
+ ### `pt chat install-skills [CHAT_ID]`
61
+
62
+ Chat side only. `CHAT_ID` argument (ID or UUID) is optional, falling back to
63
+ `PT_CHAT_UUID` / `PT_CHAT_ID`. Uses `profile_short=False` (the chat group
64
+ reserves `-p` for `--path`). Same installer/connection options as above minus
65
+ the agent-related flags.
66
+
67
+ ### `pt agent install-skills [AGENT_ID]`
68
+
69
+ Agent side only. `AGENT_ID` (int) optional, falling back to `PT_AGENT_ID`.
70
+ Same installer/connection options minus the chat-related flags.
71
+
72
+ ## Filtering semantics
73
+
74
+ From each collections listing, keep a collection when:
75
+
76
+ - `type == "skill"`, **or**
77
+ - `type == "external_source"` and `external_source_type == "github_skill"`.
78
+
79
+ Additionally:
80
+
81
+ - **Agent links:** require `status == "active"` and `hidden` falsy — this
82
+ replicates how Deep1 computes "the skills this agent actually has".
83
+ - **Chat links:** require the link `status` to be `active` when present.
84
+ - When both chat and agent are processed, de-duplicate by collection `id`
85
+ (first occurrence wins; chat side is processed first).
86
+
87
+ Non-skill collections (`collection`, `db`, other external-source types) are
88
+ ignored silently.
89
+
90
+ ## Install pipeline
91
+
92
+ Destination base directory resolves via the existing `_skill_install_base`
93
+ (`--user` / `--project` / `--dir`). Each skill installs into its own
94
+ subdirectory. If the destination subdirectory already exists, the skill is
95
+ **skipped with a notice** unless `--force` is given.
96
+
97
+ ### `skill` collections
98
+
99
+ - Reuse the existing remote-tree machinery (`_list_remote_tree` and the
100
+ download helpers behind `pt collections sync-from`) to walk the collection's
101
+ document tree and download every file into
102
+ `<skills-dir>/<slug>/`, where `<slug>` is the sanitized collection name
103
+ (reusing `_sanitize_filename`-style slugging; kebab-case).
104
+ - After download, **warn** (do not fail) if there is no `SKILL.md` at the
105
+ skill's root.
106
+
107
+ ### `github_skill` collections
108
+
109
+ - Parse `external_source_value` as a GitHub URL:
110
+ - `https://github.com/{owner}/{repo}` → repo root, ref `main`, path empty.
111
+ - `https://github.com/{owner}/{repo}/tree/{ref}/{path...}` (or `/blob/`) →
112
+ that ref and subdirectory.
113
+ - Anything else (non-GitHub host, malformed) → per-skill failure.
114
+ - Download via the existing `_download_github_archive` and extract via
115
+ `_extract_github_subdirectory`, which already enforce SKILL.md presence,
116
+ path safety (no traversal, no symlinks), and file-count/size caps. Note:
117
+ the extractor currently rejects an empty source path, so supporting
118
+ repo-root URLs requires a small extension to it (treat empty path as "the
119
+ archive's single top-level directory", keeping the SKILL.md marker check).
120
+ - Destination folder name: last path segment of the skill path, or the repo
121
+ name for root URLs.
122
+
123
+ ## Failure semantics
124
+
125
+ Consistent with the sync commands: **per-skill failures are non-fatal**. Each
126
+ skill prints an install/skip/fail line as it is processed; the command ends
127
+ with a summary such as `Installed 3, skipped 1, failed 1`.
128
+
129
+ Exit code 1 when:
130
+
131
+ - discovery itself fails (the collections request errors), or
132
+ - at least one skill was requested and **all** of them failed.
133
+
134
+ Exit code 0 otherwise (including "no skills attached", which prints a notice).
135
+
136
+ ## Error handling details
137
+
138
+ - Unresolvable chat/agent (no arg, no env var): clear message naming the env
139
+ vars and flags, exit 1.
140
+ - `--chat-only` together with `--agent-only`: usage error.
141
+ - API errors during discovery: standard `_api_request` error path.
142
+ - Download errors, bad GitHub URLs, unsafe archives: recorded as that skill's
143
+ failure; processing continues.
144
+
145
+ ## Testing
146
+
147
+ All API traffic is mocked via `primethink.requests.request` (repo convention):
148
+
149
+ - Discovery: chat and agent collections responses; client-side filter drops
150
+ non-skill types, inactive links, and hidden agent links.
151
+ - Dedup: same collection attached to both chat and agent installs once.
152
+ - `skill` collection install: mocked directory listing + file downloads;
153
+ correct tree on disk; SKILL.md warning path.
154
+ - `github_skill` install: URL parsing cases (root, `/tree/`, `/blob/`,
155
+ malformed); archive extraction reusing the zip-fixture pattern from the
156
+ developer-skill tests.
157
+ - Env-var defaults (`PT_CHAT_UUID`, `PT_CHAT_ID`, `PT_AGENT_ID`) and explicit
158
+ overrides; missing-both error.
159
+ - Skip-existing vs `--force`.
160
+ - Failure semantics: partial failure exits 0 with summary; total failure
161
+ exits 1.
162
+ - All three command entry points share behavior.
163
+
164
+ ## Documentation updates (same change)
165
+
166
+ - `docs/cli-reference.md` — full reference for all three commands.
167
+ - `skills/primethink-cli/SKILL.md` — command map + sandbox workflow note.
168
+ - `README.md` — commands-at-a-glance table.
169
+ - `USER_GUIDE.md` — walkthrough section.
170
+ - `SPECS.md` — the two collections-listing endpoints.
171
+ - `CHANGELOG.md` — entry under `[Unreleased]`.
172
+
173
+ **MCP / agent-tools:** deliberately not exposed. Like `pt install-skill`, this
174
+ is a local-filesystem installer, which the MCP server and LangChain plugin do
175
+ not surface.
176
+
177
+ ## Out of scope
178
+
179
+ - Server-side `?type=` filter on the collections endpoints (five-line backend
180
+ change; not needed for v1 since client-side filtering is the established
181
+ pattern).
182
+ - Updating/refreshing already-installed skills beyond `--force` overwrite.
183
+ - Installing skills from collections not attached to a chat/agent (a direct
184
+ `pt collections install-skill <id>` could be a follow-up).
@@ -119,6 +119,17 @@ def _api_call(method, url, token, timeout=REQUEST_TIMEOUT, **kwargs):
119
119
  return response, None
120
120
 
121
121
 
122
+ def _upload_part(path, fileobj):
123
+ """Multipart tuple for a document upload: (field, (filename, fp, mimetype)).
124
+
125
+ Sends the BASENAME (not the local path) and an explicit part
126
+ Content-Type — requests omits the part header otherwise, which some
127
+ servers reject (and older PrimeThink APIs answered 500 to).
128
+ """
129
+ guessed = mimetypes.guess_type(path)[0] or 'application/octet-stream'
130
+ return ('documents', (os.path.basename(str(path)), fileobj, guessed))
131
+
132
+
122
133
  def _api_request(method, url, token, ok_status=(200,), timeout=REQUEST_TIMEOUT, **kwargs):
123
134
  """Make an authenticated API request; print an error and exit on failure."""
124
135
  response, error = _api_call(method, url, token, timeout=timeout, **kwargs)
@@ -194,7 +205,7 @@ def _upload_file_groups(local_path, file_list, path, upload_batch):
194
205
  files_data = []
195
206
  for file in dir_files:
196
207
  try:
197
- files_data.append(('documents', stack.enter_context(open(file, 'rb'))))
208
+ files_data.append(_upload_part(file, stack.enter_context(open(file, 'rb'))))
198
209
  except OSError as e:
199
210
  error_count += 1
200
211
  click.echo(f" Error opening {file}: {e}")
@@ -416,7 +427,7 @@ def cli():
416
427
  @cli.command()
417
428
  def version():
418
429
  """Display the version of PrimeThink CLI."""
419
- click.echo("PrimeThink CLI v1.3.0")
430
+ click.echo("PrimeThink CLI v1.3.2")
420
431
 
421
432
 
422
433
  @cli.command()
@@ -802,7 +813,7 @@ def live_app_new(directory, framework, tailwind, flowbite, repo_url, ref):
802
813
  )
803
814
  click.echo(f"Created PrimeThink Live App at {directory}")
804
815
  click.echo(f"Template: {template_id} ({framework}, tailwind={'yes' if tailwind else 'no'}, flowbite={'yes' if flowbite else 'no'})")
805
- click.echo("Next: read the generated README.md and rename its sample entity before deploying.")
816
+ click.echo("Next: read the generated README.md and learn how to build Live Apps.")
806
817
 
807
818
 
808
819
  # ============================================================================
@@ -1324,7 +1335,7 @@ def upload_files(chat_id, files, path, profile, api_url):
1324
1335
 
1325
1336
  params = {'path': path} if path else {}
1326
1337
  with contextlib.ExitStack() as stack:
1327
- files_data = [('documents', stack.enter_context(open(fp, 'rb'))) for fp in files]
1338
+ files_data = [_upload_part(fp, stack.enter_context(open(fp, 'rb'))) for fp in files]
1328
1339
  response = _api_request(
1329
1340
  'POST', f"{api_url}/api/v1/chats/{chat_id}/documents", token,
1330
1341
  files=files_data, params=params
@@ -1463,7 +1474,7 @@ def upload_files(collection_id, files, path, profile, api_url):
1463
1474
 
1464
1475
  form_data = {'path': path} if path else None
1465
1476
  with contextlib.ExitStack() as stack:
1466
- files_data = [('documents', stack.enter_context(open(fp, 'rb'))) for fp in files]
1477
+ files_data = [_upload_part(fp, stack.enter_context(open(fp, 'rb'))) for fp in files]
1467
1478
  response = _api_request(
1468
1479
  'POST', f"{api_url}/api/v1/collections/{collection_id}/documents", token,
1469
1480
  files=files_data, data=form_data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: primethink-cli
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: PrimeThink CLI - A powerful tool for interacting with PrimeThink AI API
5
5
  Author-email: PrimeThink <support@primethink.ai>
6
6
  License-Expression: MIT
@@ -11,6 +11,7 @@ setup.py
11
11
  docs/cli-reference.md
12
12
  docs/install.md
13
13
  docs/primethink_help_llms.txt
14
+ docs/superpowers/specs/2026-08-16-install-attached-skills-design.md
14
15
  install/install.cmd
15
16
  install/install.ps1
16
17
  install/install.sh
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "primethink-cli"
7
- version = "1.3.0"
7
+ version = "1.3.2"
8
8
  description = "PrimeThink CLI - A powerful tool for interacting with PrimeThink AI API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  import pytest
2
4
  import os
3
5
  import json
@@ -51,7 +53,8 @@ def test_version_command(runner):
51
53
  """Test version command."""
52
54
  result = runner.invoke(cli, ["version"])
53
55
  assert result.exit_code == 0
54
- assert "PrimeThink CLI v1.3.0" in result.output
56
+ # Version-agnostic: the hardcoded number went stale at every release.
57
+ assert re.match(r"PrimeThink CLI v\d+\.\d+\.\d+", result.output)
55
58
 
56
59
  @patch('primethink.CONFIG_DIR', Path('/tmp/.primethink'))
57
60
  @patch('primethink.CONFIG_FILE', Path('/tmp/.primethink/config.json'))
@@ -553,8 +556,10 @@ def test_chat_sync_prefer_local(mock_request, mock_config_fn, runner, tmp_path):
553
556
  assert upload_call.kwargs['params'] == {}
554
557
  files_sent = upload_call.kwargs['files']
555
558
  assert len(files_sent) == 1
556
- field_name, file_obj = files_sent[0]
559
+ field_name, (filename, file_obj, ctype) = files_sent[0]
557
560
  assert field_name == 'documents'
561
+ assert filename == 'shared.txt', "part filename must be the basename, not the local path"
562
+ assert ctype == 'text/plain', "part must carry an explicit Content-Type"
558
563
  assert Path(file_obj.name).name == 'shared.txt'
559
564
 
560
565
  @patch('primethink.get_active_config')
File without changes
File without changes
File without changes
File without changes
File without changes