primethink-cli 1.5.0__tar.gz → 1.6.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.
Files changed (51) hide show
  1. {primethink_cli-1.5.0/primethink_cli.egg-info → primethink_cli-1.6.0}/PKG-INFO +3 -2
  2. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/README.md +1 -1
  3. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/SPECS.md +6 -6
  4. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/USER_GUIDE.md +18 -5
  5. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/docs/agent-tools.md +1 -1
  6. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/docs/cli-reference.md +36 -10
  7. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink.py +179 -15
  8. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_agent_tools/tools.py +3 -3
  9. {primethink_cli-1.5.0 → primethink_cli-1.6.0/primethink_cli.egg-info}/PKG-INFO +3 -2
  10. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_cli.egg-info/requires.txt +1 -0
  11. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_mcp.py +46 -14
  12. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/pyproject.toml +4 -1
  13. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/skills/primethink-cli/SKILL.md +8 -3
  14. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_agent_tools.py +10 -0
  15. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_cli.py +130 -3
  16. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_install_developer_skill.py +28 -0
  17. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_live_app.py +188 -0
  18. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_mcp_server.py +47 -0
  19. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/DEVELOPER.md +0 -0
  20. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/LICENSE +0 -0
  21. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/MANIFEST.in +0 -0
  22. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/docs/install.md +0 -0
  23. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/docs/primethink_help_llms.txt +0 -0
  24. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/install/homebrew/primethink-cli.rb +0 -0
  25. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/install/install.cmd +0 -0
  26. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/install/install.ps1 +0 -0
  27. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/install/install.sh +0 -0
  28. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_agent_tools/__init__.py +0 -0
  29. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_agent_tools/client.py +0 -0
  30. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_agent_tools/settings.py +0 -0
  31. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_cli.egg-info/SOURCES.txt +0 -0
  32. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_cli.egg-info/dependency_links.txt +0 -0
  33. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_cli.egg-info/entry_points.txt +0 -0
  34. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/primethink_cli.egg-info/top_level.txt +0 -0
  35. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/pytest.ini +0 -0
  36. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/scripts/build.sh +0 -0
  37. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/scripts/deploy.sh +0 -0
  38. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/scripts/publish.sh +0 -0
  39. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/setup.cfg +0 -0
  40. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/setup.py +0 -0
  41. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/skills/__init__.py +0 -0
  42. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/__init__.py +0 -0
  43. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/conftest.py +0 -0
  44. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_agent_commands.py +0 -0
  45. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_env_config.py +0 -0
  46. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_install_skill.py +0 -0
  47. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_packaging.py +0 -0
  48. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_project_workflows.py +0 -0
  49. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_search_commands.py +0 -0
  50. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_task_commands.py +0 -0
  51. {primethink_cli-1.5.0 → primethink_cli-1.6.0}/tests/test_whoami.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: primethink-cli
3
- Version: 1.5.0
3
+ Version: 1.6.0
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
@@ -26,6 +26,7 @@ Requires-Dist: click>=8.0.0
26
26
  Requires-Dist: requests>=2.25.0
27
27
  Provides-Extra: mcp
28
28
  Requires-Dist: mcp<2,>=1.2.0; extra == "mcp"
29
+ Requires-Dist: pydantic>=2.0; extra == "mcp"
29
30
  Provides-Extra: agent-tools
30
31
  Requires-Dist: langchain-core>=0.3.0; extra == "agent-tools"
31
32
  Requires-Dist: pydantic>=2.0; extra == "agent-tools"
@@ -104,7 +105,7 @@ pt chat send --agent AGENT_ID --message "your message"
104
105
  | `pt chat sync-from` | Sync a chat's files to a local directory |
105
106
  | `pt chat sync` | Two-way sync between a chat folder and a local directory |
106
107
  | `pt chatdb init` | Initialize a chat's ChatDB (Live App data) store |
107
- | `pt chatdb list` | List entities in a chat's ChatDB store |
108
+ | `pt chatdb list` | List entities in a chat's ChatDB store (or an attached DB Collection via `--collection`/`--collection-id`) |
108
109
  | `pt chatdb get` | Get one ChatDB entity by ID |
109
110
  | `pt chatdb add` | Add one or more rows to a ChatDB entity |
110
111
  | `pt chatdb update` | Update ChatDB entities (single or bulk) |
@@ -65,7 +65,7 @@ pt chat send --agent AGENT_ID --message "your message"
65
65
  | `pt chat sync-from` | Sync a chat's files to a local directory |
66
66
  | `pt chat sync` | Two-way sync between a chat folder and a local directory |
67
67
  | `pt chatdb init` | Initialize a chat's ChatDB (Live App data) store |
68
- | `pt chatdb list` | List entities in a chat's ChatDB store |
68
+ | `pt chatdb list` | List entities in a chat's ChatDB store (or an attached DB Collection via `--collection`/`--collection-id`) |
69
69
  | `pt chatdb get` | Get one ChatDB entity by ID |
70
70
  | `pt chatdb add` | Add one or more rows to a ChatDB entity |
71
71
  | `pt chatdb update` | Update ChatDB entities (single or bulk) |
@@ -66,13 +66,13 @@ To obtain an API key, go to `Settings > API Keys` in the PrimeThink app and gene
66
66
  | `pt scheduled-job delete` | `DELETE /api/v1/scheduled_jobs/scheduled_job_in_chat/{job_id}` |
67
67
  | document download (used by `download-file` and `sync-from`) | `GET /api/v1/documents/{document_id}/download` |
68
68
  | `pt chatdb init` | `POST /api/v1/chats/{chat_id}/chatdb/init` |
69
- | `pt chatdb list` | `POST /api/v1/chats/{chat_id}/chatdb/list` |
70
- | `pt chatdb get` | `GET /api/v1/chats/{chat_id}/chatdb/entities/{entity_id}` |
71
- | `pt chatdb add` | `POST /api/v1/chats/{chat_id}/chatdb/entities` |
72
- | `pt chatdb update` | `PATCH /api/v1/chats/{chat_id}/chatdb/entities` |
73
- | `pt chatdb delete` | `DELETE /api/v1/chats/{chat_id}/chatdb/entities` |
69
+ | `pt chatdb list` | `POST /api/v1/chats/{chat_id}/chatdb/list` (body may add `collection_name`/`collection_id`) |
70
+ | `pt chatdb get` | `GET /api/v1/chats/{chat_id}/chatdb/entities/{entity_id}` (query: `collection_name`, `collection_id`) |
71
+ | `pt chatdb add` | `POST /api/v1/chats/{chat_id}/chatdb/entities` (body may add `collection_name`/`collection_id`) |
72
+ | `pt chatdb update` | `PATCH /api/v1/chats/{chat_id}/chatdb/entities` (body may add `collection_name`/`collection_id`) |
73
+ | `pt chatdb delete` | `DELETE /api/v1/chats/{chat_id}/chatdb/entities` (body may add `collection_name`/`collection_id`) |
74
74
  | `pt collection list` | `GET /api/v1/collections` |
75
- | `pt collection create` | `POST /api/v1/collections` (params: `name`, `description`, `type`, `public`) |
75
+ | `pt collection create` | `POST /api/v1/collections` (params: `name`, `description`, `type` = `collection`/`skill`/`external_source`/`db`, `public`) |
76
76
  | `pt collection get` | `GET /api/v1/collections/{collection_id}` |
77
77
  | `pt collection update` | `PATCH /api/v1/collections/{collection_id}` |
78
78
  | `pt collection reindex` | `POST /api/v1/collections/{collection_id}/reindex` |
@@ -77,7 +77,7 @@ pt version
77
77
 
78
78
  You should see output like:
79
79
  ```
80
- PrimeThink CLI v1.5.0
80
+ PrimeThink CLI v1.6.0
81
81
  ```
82
82
 
83
83
  ## Getting Started
@@ -713,6 +713,19 @@ pt chatdb delete 123 --entity-id 7 # asks for confirmation; --yes to sk
713
713
 
714
714
  `--data` takes a single JSON object; `--items` takes a JSON array for bulk operations. On update, `--merge` patches the existing row while `--replace` overwrites it, and `--if-unchanged-since TIMESTAMP` guards against overwriting a row that changed underneath you.
715
715
 
716
+ ### DB Collections
717
+
718
+ A **DB Collection** is a collection of type `db` that holds the same kind of entities, but can be shared between chats. Once one is attached to a chat, `list`, `get`, `add`, `update` and `delete` can target it instead of the chat's own ChatDB, by name or by ID:
719
+
720
+ ```bash
721
+ pt collection create --name project-db --type db # note the id, e.g. 42
722
+ # attach it to the chat in the web app, then:
723
+ pt chatdb add 123 --entity tasks --data '{"title": "a"}' --collection project-db
724
+ pt chatdb list 123 --entity tasks --collection-id 42
725
+ ```
726
+
727
+ Prefer `--collection-id` when two attached DB Collections share a name, or in scripts that should survive a rename. If you pass both, they must identify the same collection. A collection attached read-only accepts `list`/`get` but rejects writes. This mirrors `pt.db('project-db')` / `pt.db(42)` in a Live App.
728
+
716
729
  > Building the Live App itself (the front-end that uses this data) is covered by the **primethink-developer** skill; `pt chatdb` is the deterministic data plane underneath it.
717
730
 
718
731
  ## Semantic Search
@@ -1144,10 +1157,10 @@ pt eval delete 99 EVAL_ID
1144
1157
  ### 2. Configure how evaluations run
1145
1158
 
1146
1159
  ```bash
1147
- pt eval settings 99 --active --run-time daily --evaluator-agent-id 7 --pass-threshold 0.8
1160
+ pt eval settings 99 --active --run-time daily --evaluator-agent-id 7 --pass-threshold 80
1148
1161
  ```
1149
1162
 
1150
- `--run-time` is `manual`, `daily`, `weekly`, or `monthly`; `--pass-threshold` is the minimum score to count as a pass; `--message-delay-ms` throttles messages during a run.
1163
+ `--run-time` is `manual`, `daily`, `weekly`, or `monthly`; `--pass-threshold` is the minimum score to count as a pass, as a whole-number percentage from 1 to 100 (`80`, not `0.8`) — anything outside that range is rejected by the CLI before a request is made; `--message-delay-ms` throttles messages during a run.
1151
1164
 
1152
1165
  ### 3. Run it, then read the results
1153
1166
 
@@ -1270,8 +1283,8 @@ Defaults are `--framework react --tailwind --flowbite`. Flowbite depends on Tail
1270
1283
  After generation:
1271
1284
 
1272
1285
  1. Read the generated `README.md`; build and deployment steps differ by template.
1273
- 2. Rename the sample Chat DB entity so it is unique to your app.
1274
- 3. Replace the sample CRUD interface while retaining the PrimeThink runtime, theme, persistence, and real-time patterns.
1286
+ 2. Name every Chat DB entity for your app. The templates ship none, and a generic name such as `item` collides with a sibling app in the same chat.
1287
+ 3. Build the interface on the blank starter, keeping the PrimeThink wiring it ships: the runtime, the host-theme bridge, and the deployment configuration.
1275
1288
  4. Build if required, then upload the files described by the template README.
1276
1289
 
1277
1290
  The default Flowbite React starter is a Vite project and requires a local `npm install` and build. The other default starters are no-build, one-file HTML applications. The CLI itself intentionally runs neither package installation nor generated code.
@@ -60,7 +60,7 @@ resolve on 3.8. The `dev` extra installs the plugin's test dependencies only on
60
60
 
61
61
  ```bash
62
62
  # In the API image (production): one pinned line in requirements.txt
63
- primethink-cli[agent-tools]==1.5.0
63
+ primethink-cli[agent-tools]==1.6.0
64
64
 
65
65
  # From a checkout (dev): the dev extra already includes langchain-core
66
66
  pip install -e ".[dev]"
@@ -1,6 +1,6 @@
1
1
  # PrimeThink CLI — Command Reference
2
2
 
3
- Complete reference for every command in the PrimeThink CLI (`pt`), version 1.5.0.
3
+ Complete reference for every command in the PrimeThink CLI (`pt`), version 1.6.0.
4
4
 
5
5
  Commands are organized into noun groups: `profile`, `live-app`, `chat`, `collection`, `agent`, `task`, `search`, and `image`.
6
6
 
@@ -221,7 +221,7 @@ Display the CLI version.
221
221
 
222
222
  ```bash
223
223
  pt version
224
- # PrimeThink CLI v1.5.0
224
+ # PrimeThink CLI v1.6.0
225
225
  ```
226
226
 
227
227
  ### `pt whoami`
@@ -1021,6 +1021,13 @@ Entities are grouped by name (think tables); each row has an `id`. Initialize th
1021
1021
 
1022
1022
  > Unlike `pt chat`/`pt collection`, this group *does* use `-p` for `--profile` (there is no `--path` here).
1023
1023
 
1024
+ **Targeting a DB Collection.** By default every command below except `init` reads and writes the chat's own ChatDB. Pass `--collection NAME` and/or `--collection-id N` to target a **DB Collection** (a collection of type `db`, see `pt collection create --type db`) attached to the chat instead. The ID is unambiguous when several attached DB Collections share a name, and it survives a rename; when you pass both, they must identify the same collection or the API answers 404. Writes (`add`, `update`, `delete`) to a collection attached read-only are rejected with 403. The target is sent as `collection_name`/`collection_id` in the request body, or as query parameters for `get`. There is no `pt` command to attach a collection to a chat yet — attach it in the web app.
1025
+
1026
+ ```bash
1027
+ pt chatdb list 123 --entity tasks --collection project-db
1028
+ pt chatdb add 123 --entity tasks --data '{"title": "a"}' --collection-id 42
1029
+ ```
1030
+
1024
1031
  ### `pt chatdb init`
1025
1032
 
1026
1033
  Initialize the ChatDB store for a chat.
@@ -1047,6 +1054,8 @@ pt chatdb list CHAT_ID [OPTIONS]
1047
1054
  | `--offset` | Row offset |
1048
1055
  | `--page` | Page number |
1049
1056
  | `--page-size` | Results per page |
1057
+ | `--collection` | Target an attached DB Collection by name instead of the chat's own ChatDB |
1058
+ | `--collection-id` | Target an attached DB Collection by ID (≥ 1; unambiguous when names repeat) |
1050
1059
 
1051
1060
  Calls `POST /api/v1/chats/{chat_id}/chatdb/list`.
1052
1061
 
@@ -1060,9 +1069,14 @@ pt chatdb list 123 --entity todos --filters '{"done": false}' --limit 20
1060
1069
  Get a single ChatDB entity by its ID.
1061
1070
 
1062
1071
  ```bash
1063
- pt chatdb get CHAT_ID ENTITY_ID [--profile NAME] [--api-url URL]
1072
+ pt chatdb get CHAT_ID ENTITY_ID [--collection NAME] [--collection-id N] [--profile NAME] [--api-url URL]
1064
1073
  ```
1065
1074
 
1075
+ | Option | Description |
1076
+ |---|---|
1077
+ | `--collection` | Target an attached DB Collection by name instead of the chat's own ChatDB |
1078
+ | `--collection-id` | Target an attached DB Collection by ID (≥ 1; unambiguous when names repeat) |
1079
+
1066
1080
  Calls `GET /api/v1/chats/{chat_id}/chatdb/entities/{entity_id}`.
1067
1081
 
1068
1082
  ### `pt chatdb add`
@@ -1078,6 +1092,8 @@ pt chatdb add CHAT_ID --entity NAME [--data JSON] [--items JSON]
1078
1092
  | `--entity` | Entity name to add to (required) |
1079
1093
  | `--data` | A single row as a JSON object |
1080
1094
  | `--items` | Multiple rows as a JSON array (bulk insert) |
1095
+ | `--collection` | Target an attached DB Collection by name instead of the chat's own ChatDB |
1096
+ | `--collection-id` | Target an attached DB Collection by ID (≥ 1; unambiguous when names repeat) |
1081
1097
 
1082
1098
  Calls `POST /api/v1/chats/{chat_id}/chatdb/entities`.
1083
1099
 
@@ -1101,8 +1117,10 @@ pt chatdb update CHAT_ID [OPTIONS]
1101
1117
  | `--merge` / `--replace` | Merge `--data` into the existing row or replace it (server default applies if unset) |
1102
1118
  | `--items` | Bulk updates as a JSON array |
1103
1119
  | `--if-unchanged-since` | Only update if the row is unchanged since this timestamp (optimistic concurrency) |
1120
+ | `--collection` | Target an attached DB Collection by name instead of the chat's own ChatDB |
1121
+ | `--collection-id` | Target an attached DB Collection by ID (≥ 1; unambiguous when names repeat) |
1104
1122
 
1105
- Requires at least one of `--entity-id`/`--data` or `--items`. Calls `PATCH /api/v1/chats/{chat_id}/chatdb/entities`.
1123
+ Requires at least one of `--entity-id`/`--data` or `--items`; a collection target on its own does not count. Calls `PATCH /api/v1/chats/{chat_id}/chatdb/entities`.
1106
1124
 
1107
1125
  ```bash
1108
1126
  pt chatdb update 123 --entity-id 7 --data '{"done": true}' --merge
@@ -1113,9 +1131,17 @@ pt chatdb update 123 --entity-id 7 --data '{"done": true}' --merge
1113
1131
  Delete a single row (`--entity-id`) or several (`--ids`). **Destructive and irreversible** — prompts for confirmation unless `--yes` is passed.
1114
1132
 
1115
1133
  ```bash
1116
- pt chatdb delete CHAT_ID [--entity-id N | --ids 1,2,3] [--yes]
1134
+ pt chatdb delete CHAT_ID [--entity-id N | --ids 1,2,3] [--collection NAME] [--collection-id N] [--yes]
1117
1135
  ```
1118
1136
 
1137
+ | Option | Description |
1138
+ |---|---|
1139
+ | `--entity-id` | ID of a single row to delete |
1140
+ | `--ids` | Comma-separated row IDs to delete |
1141
+ | `--collection` | Target an attached DB Collection by name instead of the chat's own ChatDB |
1142
+ | `--collection-id` | Target an attached DB Collection by ID (≥ 1; unambiguous when names repeat) |
1143
+ | `--yes` | Skip the confirmation prompt |
1144
+
1119
1145
  Requires `--entity-id` or `--ids`. Calls `DELETE /api/v1/chats/{chat_id}/chatdb/entities`.
1120
1146
 
1121
1147
  ```bash
@@ -1162,7 +1188,7 @@ pt collection create --name NAME [OPTIONS]
1162
1188
  |---|---|
1163
1189
  | `--name` | Collection name (required) |
1164
1190
  | `--description` | Collection description |
1165
- | `--type` | Collection type: `collection`, `skill`, or `external_source` (server default: `collection`) |
1191
+ | `--type` | Collection type: `collection`, `skill`, `external_source`, or `db` (a DB Collection: structured entities that `pt chatdb --collection` and Live Apps' `pt.db()` read and write) (server default: `collection`) |
1166
1192
  | `--public` / `--private` | Make the collection public or private (server default: private) |
1167
1193
 
1168
1194
  Calls `POST /api/v1/collections` and prints the created collection as JSON.
@@ -1198,7 +1224,7 @@ pt collection update COLLECTION_ID [OPTIONS]
1198
1224
  |---|---|
1199
1225
  | `--name` | Collection name |
1200
1226
  | `--description` | Collection description |
1201
- | `--type` | Collection type: `collection`, `skill`, or `external_source` |
1227
+ | `--type` | Collection type: `collection`, `skill`, `external_source`, or `db` |
1202
1228
  | `--public` / `--private` | Make the collection public or private |
1203
1229
  | `--indexed` / `--not-indexed` | Enable or disable indexing |
1204
1230
  | `--ocr-instructions` | OCR instructions for document processing |
@@ -2472,7 +2498,7 @@ pt eval update TASK_ID EVAL_ID [--user-query …] [--ideal-response …] [--type
2472
2498
  pt eval delete TASK_ID EVAL_ID [--yes] # DESTRUCTIVE
2473
2499
 
2474
2500
  # Configure how evaluations run
2475
- pt eval settings TASK_ID [--active | --inactive] [--run-time manual|daily|weekly|monthly] [--evaluator-agent-id N] [--pass-threshold F] [--message-delay-ms N]
2501
+ pt eval settings TASK_ID [--active | --inactive] [--run-time manual|daily|weekly|monthly] [--evaluator-agent-id N] [--pass-threshold 1-100] [--message-delay-ms N]
2476
2502
 
2477
2503
  # Run an evaluation and inspect its runs/results
2478
2504
  pt eval run TASK_ID [--version N] [--model-override M]
@@ -2503,13 +2529,13 @@ pt eval delete-simulation TASK_ID SIM_ID [--yes] # DESTRUCTIVE
2503
2529
  | `simulations` | `GET /api/v1/tasks/{id}/simulations` | `list_task_simulations` |
2504
2530
  | `delete-simulation` | `DELETE /api/v1/tasks/{id}/simulations/{sim_id}` | `delete_task_simulation` |
2505
2531
 
2506
- `--type` for a test case is `exact` (exact-match), `similar` (fuzzy/semantic match), or `agent` (an evaluator agent judges the response). `--examples` takes a JSON object of good/bad example fields. `--chat-group` (default `1`) groups related test cases.
2532
+ `--pass-threshold` is the minimum score that counts as a pass, as a whole-number percentage from 1 to 100 (`80`, not `0.8`); the CLI rejects anything outside that range with a usage error before any request is made. `--type` for a test case is `exact` (exact-match), `similar` (fuzzy/semantic match), or `agent` (an evaluator agent judges the response). `--examples` takes a JSON object of good/bad example fields. `--chat-group` (default `1`) groups related test cases.
2507
2533
 
2508
2534
  ```bash
2509
2535
  # Build a two-case plan, make it evaluator-judged and daily, then run it
2510
2536
  pt eval add 99 --user-query "What's your return window?" --ideal-response "30 days" --type similar
2511
2537
  pt eval add 99 --user-query "Refund a gift?" --ideal-response "Yes, store credit" --type agent --evaluator-agent-id 7
2512
- pt eval settings 99 --active --run-time daily --evaluator-agent-id 7 --pass-threshold 0.8
2538
+ pt eval settings 99 --active --run-time daily --evaluator-agent-id 7 --pass-threshold 80
2513
2539
  pt eval run 99
2514
2540
  pt eval results 99 | jq '.[] | {case, passed, score}'
2515
2541
  ```
@@ -7,6 +7,8 @@ import json
7
7
  import shutil
8
8
  import tempfile
9
9
  import zipfile
10
+ import re
11
+ import html as html_module
10
12
  import contextlib
11
13
  import mimetypes
12
14
  import webbrowser
@@ -921,7 +923,7 @@ def cli():
921
923
  @cli.command()
922
924
  def version():
923
925
  """Display the version of PrimeThink CLI."""
924
- click.echo("PrimeThink CLI v1.5.0")
926
+ click.echo("PrimeThink CLI v1.6.0")
925
927
 
926
928
 
927
929
  @cli.command()
@@ -1208,6 +1210,107 @@ def _select_live_app_template(templates, framework, tailwind, flowbite):
1208
1210
  return template_id, _safe_catalog_path(item.get('path'), 'template path')
1209
1211
 
1210
1212
 
1213
+ # ---------------------------------------------------------------------------
1214
+ # Scaffold identity
1215
+ #
1216
+ # `pt live-app new` used to extract the archive verbatim, so the TEMPLATE's own
1217
+ # <title> and meta description shipped with the app. In a reported build they
1218
+ # reached production: nothing downstream rewrites them, `verify-dist` cannot tell a
1219
+ # placeholder from a deliberate title, and the browser tab is the last place anyone
1220
+ # looks. This is the one point in the whole flow where the app has a name, so it is
1221
+ # the only place the rewrite can happen.
1222
+ # ---------------------------------------------------------------------------
1223
+
1224
+ _TITLE_TAG = re.compile(r'(<title\b[^>]*>)(.*?)(</title>)', re.IGNORECASE | re.DOTALL)
1225
+ # The quotes are back-referenced, not just "one of ' or \"". Matching them
1226
+ # independently breaks on an apostrophe inside a double-quoted attribute:
1227
+ # content="Bob's canvas" would end the match at the apostrophe and leave
1228
+ # `s canvas"` dangling in the markup after the replacement.
1229
+ _DESCRIPTION_META = re.compile(
1230
+ r'(<meta\b[^>]*\bname=(["\'])description\2[^>]*?\bcontent=(["\']))(.*?)\3',
1231
+ re.IGNORECASE | re.DOTALL,
1232
+ )
1233
+
1234
+ # npm rejects a package name longer than this.
1235
+ _MAX_PACKAGE_NAME = 214
1236
+
1237
+
1238
+ # Directory names that describe a slot rather than an app. A repo convention that
1239
+ # scaffolds into `<app>/sandbox` would otherwise title every app "Sandbox" — which it
1240
+ # did, and it reached a published task before anyone noticed.
1241
+ _CONTAINER_DIR_NAMES = {'sandbox', 'app', 'apps', 'src', 'dist', 'web', 'client', 'frontend'}
1242
+
1243
+
1244
+ def _app_identity(destination):
1245
+ """A display title and an npm-safe package name, from the target directory name.
1246
+
1247
+ ``my-cool-app`` -> ``("My Cool App", "my-cool-app")``. When the target is a
1248
+ container directory, the parent names the app instead:
1249
+ ``word-painter/sandbox`` -> ``("Word Painter", "word-painter")``.
1250
+ """
1251
+ resolved = Path(destination).resolve()
1252
+ raw = resolved.name
1253
+ if raw.lower() in _CONTAINER_DIR_NAMES and resolved.parent.name:
1254
+ raw = resolved.parent.name
1255
+ words = [w for w in re.split(r'[-_\s]+', raw) if w]
1256
+ title = ' '.join(w[:1].upper() + w[1:] for w in words) if words else raw
1257
+ package = re.sub(r'[^a-z0-9._-]+', '-', raw.lower()).strip('-._')
1258
+ package = package[:_MAX_PACKAGE_NAME].rstrip('-._')
1259
+ return title, (package or 'primethink-live-app')
1260
+
1261
+
1262
+ def _personalise_live_app_template(staging, title, package_name):
1263
+ """Stamp the new app's identity over the template's, in the staging directory.
1264
+
1265
+ Best-effort: a template that ships no index.html or no package.json is left
1266
+ alone, and an unreadable or malformed file is skipped rather than failing the
1267
+ scaffold. Returns the list of files actually changed, for the command output.
1268
+ """
1269
+ changed = []
1270
+
1271
+ index_html = Path(staging) / 'index.html'
1272
+ if index_html.is_file():
1273
+ try:
1274
+ source = index_html.read_text(encoding='utf-8')
1275
+ except (OSError, UnicodeDecodeError):
1276
+ source = None
1277
+ if source is not None:
1278
+ escaped = html_module.escape(title, quote=True)
1279
+ description = html_module.escape(
1280
+ f'{title} \u2014 a PrimeThink Live App', quote=True
1281
+ )
1282
+ updated, titles = _TITLE_TAG.subn(
1283
+ lambda m: m.group(1) + escaped + m.group(3), source, count=1
1284
+ )
1285
+ updated, metas = _DESCRIPTION_META.subn(
1286
+ lambda m: m.group(1) + description + m.group(3), updated, count=1
1287
+ ) # group(3) is the opening content quote, reused as the closing one
1288
+ if (titles or metas) and updated != source:
1289
+ try:
1290
+ index_html.write_text(updated, encoding='utf-8')
1291
+ changed.append('index.html')
1292
+ except OSError:
1293
+ pass
1294
+
1295
+ package_json = Path(staging) / 'package.json'
1296
+ if package_json.is_file():
1297
+ try:
1298
+ manifest = json.loads(package_json.read_text(encoding='utf-8'))
1299
+ except (OSError, UnicodeDecodeError, ValueError):
1300
+ manifest = None
1301
+ if isinstance(manifest, dict) and manifest.get('name') != package_name:
1302
+ manifest['name'] = package_name
1303
+ try:
1304
+ package_json.write_text(
1305
+ json.dumps(manifest, indent=2) + '\n', encoding='utf-8'
1306
+ )
1307
+ changed.append('package.json')
1308
+ except OSError:
1309
+ pass
1310
+
1311
+ return changed
1312
+
1313
+
1211
1314
  def _extract_live_app_template(archive_bytes, destination, framework, tailwind, flowbite):
1212
1315
  """Safely and atomically extract one selected template into destination."""
1213
1316
  destination = Path(destination)
@@ -1275,6 +1378,8 @@ def _extract_live_app_template(archive_bytes, destination, framework, tailwind,
1275
1378
  target.parent.mkdir(parents=True, exist_ok=True)
1276
1379
  with archive.open(info) as source, open(target, 'wb') as output:
1277
1380
  shutil.copyfileobj(source, output)
1381
+ title, package_name = _app_identity(destination)
1382
+ personalised = _personalise_live_app_template(staging, title, package_name)
1278
1383
  if destination.exists():
1279
1384
  raise click.ClickException(f"destination already exists: {destination}")
1280
1385
  staging.replace(destination)
@@ -1282,7 +1387,7 @@ def _extract_live_app_template(archive_bytes, destination, framework, tailwind,
1282
1387
  raise
1283
1388
  except (OSError, RuntimeError, zipfile.BadZipFile) as e:
1284
1389
  raise click.ClickException(f"could not create live app at {destination}: {e}")
1285
- return template_id
1390
+ return template_id, title, personalised
1286
1391
 
1287
1392
 
1288
1393
  @cli.group(name='live-app')
@@ -1310,11 +1415,13 @@ def live_app_new(directory, framework, tailwind, flowbite, repo_url, ref):
1310
1415
  if directory.exists():
1311
1416
  raise click.ClickException(f"destination already exists: {directory}")
1312
1417
  archive = _download_template_archive(repo_url, ref)
1313
- template_id = _extract_live_app_template(
1418
+ template_id, title, personalised = _extract_live_app_template(
1314
1419
  archive, directory, framework, tailwind, flowbite
1315
1420
  )
1316
1421
  click.echo(f"Created PrimeThink Live App at {directory}")
1317
1422
  click.echo(f"Template: {template_id} ({framework}, tailwind={'yes' if tailwind else 'no'}, flowbite={'yes' if flowbite else 'no'})")
1423
+ if personalised:
1424
+ click.echo(f"Named \"{title}\" in {', '.join(personalised)} — edit if you want something else.")
1318
1425
  click.echo("Next: read the generated README.md and learn how to build Live Apps.")
1319
1426
 
1320
1427
 
@@ -1936,8 +2043,31 @@ def install_developer_skill(target, custom_dir, force, repo_url, ref):
1936
2043
  file_count = _extract_github_subdirectory(
1937
2044
  archive, DEVELOPER_SKILL_PATH, destination, force=force
1938
2045
  )
2046
+ # Record what was installed. Three build reports in a row could not cite which
2047
+ # revision of the skill they were reporting against, because the installed tree
2048
+ # carries no marker at all — which makes a finding unfixable by anyone who was
2049
+ # not in that session.
2050
+ from datetime import datetime, timezone
2051
+ installed_at = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
2052
+ marker = destination / 'VERSION'
2053
+ try:
2054
+ marker.write_text(
2055
+ f"ref: {ref}\n"
2056
+ f"repo: {repo_url}\n"
2057
+ f"path: {DEVELOPER_SKILL_PATH}\n"
2058
+ f"files: {file_count}\n"
2059
+ f"installed_at: {installed_at}\n",
2060
+ encoding='utf-8',
2061
+ )
2062
+ except OSError as exc:
2063
+ # A missing marker must never fail an otherwise good install.
2064
+ marker_line = f"Version marker not written ({exc}); installed ref {ref}"
2065
+ else:
2066
+ marker_line = f"Version marker: {marker} (ref {ref})"
2067
+
1939
2068
  click.echo(f"Installed skill '{DEVELOPER_SKILL_NAME}' to {destination}")
1940
2069
  click.echo(f"Included {file_count} files from {DEVELOPER_SKILL_PATH}, including subdirectories.")
2070
+ click.echo(marker_line)
1941
2071
  click.echo("Compatible agents will pick it up on their next session in that scope.")
1942
2072
 
1943
2073
  # ============================================================================
@@ -2644,10 +2774,28 @@ def sync(chat_id, local_dir, path, prefer, dry_run, profile, api_url):
2644
2774
 
2645
2775
  @cli.group(name='chatdb')
2646
2776
  def chatdb():
2647
- """ChatDB commands: read and write a chat's Live App data store."""
2777
+ """ChatDB commands: read and write a chat's Live App data store.
2778
+
2779
+ By default each command targets the chat's own ChatDB. Pass --collection NAME
2780
+ and/or --collection-id N to target a DB Collection attached to the chat instead.
2781
+ """
2648
2782
  pass
2649
2783
 
2650
2784
 
2785
+ def _chatdb_target_options(f):
2786
+ """Apply the --collection/--collection-id DB Collection target options to a chatdb command."""
2787
+ f = click.option('--collection-id', 'collection_id', default=None, type=click.IntRange(min=1),
2788
+ help="Target an attached DB Collection by ID (unambiguous when names repeat)")(f)
2789
+ f = click.option('--collection', 'collection_name', default=None,
2790
+ help="Target an attached DB Collection by name instead of the chat's own ChatDB")(f)
2791
+ return f
2792
+
2793
+
2794
+ def _chatdb_target(collection_name, collection_id):
2795
+ """The DB Collection target fields; empty (the chat's own ChatDB) when neither is set."""
2796
+ return _clean_payload({'collection_name': collection_name, 'collection_id': collection_id}, {})
2797
+
2798
+
2651
2799
  @chatdb.command(name='init')
2652
2800
  @click.argument('chat_id')
2653
2801
  @_connection_options
@@ -2669,8 +2817,10 @@ def chatdb_init(chat_id, profile, api_url):
2669
2817
  @click.option('--offset', default=None, type=int, help='Row offset')
2670
2818
  @click.option('--page', default=None, type=int, help='Page number')
2671
2819
  @click.option('--page-size', default=None, type=int, help='Results per page')
2820
+ @_chatdb_target_options
2672
2821
  @_connection_options
2673
- def chatdb_list(chat_id, entity_names, filters, limit, offset, page, page_size, profile, api_url):
2822
+ def chatdb_list(chat_id, entity_names, filters, limit, offset, page, page_size,
2823
+ collection_name, collection_id, profile, api_url):
2674
2824
  """List entities in a chat's ChatDB store."""
2675
2825
  api_url, token = _resolve_connection(profile, api_url)
2676
2826
  filters = _parse_json_option(filters, '--filters')
@@ -2678,6 +2828,7 @@ def chatdb_list(chat_id, entity_names, filters, limit, offset, page, page_size,
2678
2828
  'entity_names': list(entity_names) or None, 'filters': filters,
2679
2829
  'limit': limit, 'offset': offset, 'page': page, 'page_size': page_size,
2680
2830
  }, {})
2831
+ payload.update(_chatdb_target(collection_name, collection_id))
2681
2832
  response = _api_request('POST', f"{api_url}/api/v1/chats/{chat_id}/chatdb/list", token,
2682
2833
  ok_status=(200, 201), json=payload)
2683
2834
  click.echo(json.dumps(response.json(), indent=2))
@@ -2686,11 +2837,13 @@ def chatdb_list(chat_id, entity_names, filters, limit, offset, page, page_size,
2686
2837
  @chatdb.command(name='get')
2687
2838
  @click.argument('chat_id')
2688
2839
  @click.argument('entity_id')
2840
+ @_chatdb_target_options
2689
2841
  @_connection_options
2690
- def chatdb_get(chat_id, entity_id, profile, api_url):
2842
+ def chatdb_get(chat_id, entity_id, collection_name, collection_id, profile, api_url):
2691
2843
  """Get one ChatDB entity by its ID."""
2692
2844
  api_url, token = _resolve_connection(profile, api_url)
2693
- response = _api_request('GET', f"{api_url}/api/v1/chats/{chat_id}/chatdb/entities/{entity_id}", token)
2845
+ response = _api_request('GET', f"{api_url}/api/v1/chats/{chat_id}/chatdb/entities/{entity_id}", token,
2846
+ params=_chatdb_target(collection_name, collection_id) or None)
2694
2847
  click.echo(json.dumps(response.json(), indent=2))
2695
2848
 
2696
2849
 
@@ -2699,8 +2852,9 @@ def chatdb_get(chat_id, entity_id, profile, api_url):
2699
2852
  @click.option('--entity', 'entity_name', required=True, help='Entity name (collection/table)')
2700
2853
  @click.option('--data', default=None, help='A single entity as a JSON object')
2701
2854
  @click.option('--items', default=None, help='Multiple entities as a JSON array (bulk insert)')
2855
+ @_chatdb_target_options
2702
2856
  @_connection_options
2703
- def chatdb_add(chat_id, entity_name, data, items, profile, api_url):
2857
+ def chatdb_add(chat_id, entity_name, data, items, collection_name, collection_id, profile, api_url):
2704
2858
  """Add one entity (--data) or many (--items) to a ChatDB entity."""
2705
2859
  api_url, token = _resolve_connection(profile, api_url)
2706
2860
  payload = _clean_payload({
@@ -2708,6 +2862,7 @@ def chatdb_add(chat_id, entity_name, data, items, profile, api_url):
2708
2862
  'data': _parse_json_option(data, '--data'),
2709
2863
  'items': _parse_json_option(items, '--items'),
2710
2864
  }, {})
2865
+ payload.update(_chatdb_target(collection_name, collection_id))
2711
2866
  response = _api_request('POST', f"{api_url}/api/v1/chats/{chat_id}/chatdb/entities", token,
2712
2867
  ok_status=(200, 201), json=payload)
2713
2868
  click.echo(json.dumps(response.json(), indent=2))
@@ -2722,8 +2877,10 @@ def chatdb_add(chat_id, entity_name, data, items, profile, api_url):
2722
2877
  @click.option('--items', default=None, help='Bulk updates as a JSON array')
2723
2878
  @click.option('--if-unchanged-since', default=None,
2724
2879
  help='Only update if the row is unchanged since this timestamp (optimistic concurrency)')
2880
+ @_chatdb_target_options
2725
2881
  @_connection_options
2726
- def chatdb_update(chat_id, entity_id, data, merge, items, if_unchanged_since, profile, api_url):
2882
+ def chatdb_update(chat_id, entity_id, data, merge, items, if_unchanged_since,
2883
+ collection_name, collection_id, profile, api_url):
2727
2884
  """Update ChatDB entities (by --entity-id, or in bulk via --items)."""
2728
2885
  api_url, token = _resolve_connection(profile, api_url)
2729
2886
  payload = _clean_payload({
@@ -2734,6 +2891,7 @@ def chatdb_update(chat_id, entity_id, data, merge, items, if_unchanged_since, pr
2734
2891
  if not payload:
2735
2892
  click.echo("Error: Nothing to update. Provide --entity-id/--data or --items.")
2736
2893
  sys.exit(1)
2894
+ payload.update(_chatdb_target(collection_name, collection_id))
2737
2895
  response = _api_request('PATCH', f"{api_url}/api/v1/chats/{chat_id}/chatdb/entities", token,
2738
2896
  json=payload)
2739
2897
  click.echo(json.dumps(response.json(), indent=2))
@@ -2744,8 +2902,9 @@ def chatdb_update(chat_id, entity_id, data, merge, items, if_unchanged_since, pr
2744
2902
  @click.option('--entity-id', default=None, type=int, help='ID of a single entity to delete')
2745
2903
  @click.option('--ids', default=None, help='Comma-separated entity IDs to delete')
2746
2904
  @click.confirmation_option('--yes', prompt='Delete the specified ChatDB entities? This cannot be undone.')
2905
+ @_chatdb_target_options
2747
2906
  @_connection_options
2748
- def chatdb_delete(chat_id, entity_id, ids, profile, api_url):
2907
+ def chatdb_delete(chat_id, entity_id, ids, collection_name, collection_id, profile, api_url):
2749
2908
  """Delete ChatDB entities by --entity-id or a list of --ids."""
2750
2909
  api_url, token = _resolve_connection(profile, api_url)
2751
2910
  id_list = None
@@ -2759,6 +2918,7 @@ def chatdb_delete(chat_id, entity_id, ids, profile, api_url):
2759
2918
  if not payload:
2760
2919
  click.echo("Error: Provide --entity-id or --ids.")
2761
2920
  sys.exit(1)
2921
+ payload.update(_chatdb_target(collection_name, collection_id))
2762
2922
  _api_request('DELETE', f"{api_url}/api/v1/chats/{chat_id}/chatdb/entities", token,
2763
2923
  ok_status=(200, 204), json=payload)
2764
2924
  click.echo(f"Deleted ChatDB entities from chat {chat_id}")
@@ -2793,7 +2953,7 @@ def collections_list(page, page_size, search, profile, api_url):
2793
2953
  @click.option('--name', required=True, help='Collection name')
2794
2954
  @click.option('--description', default=None, help='Collection description')
2795
2955
  @click.option('--type', 'collection_type', default=None,
2796
- type=click.Choice(['collection', 'skill', 'external_source']),
2956
+ type=click.Choice(['collection', 'skill', 'external_source', 'db']),
2797
2957
  help='Collection type (server default: collection)')
2798
2958
  @click.option('--public/--private', 'public', default=None, help='Make the collection public (server default: private)')
2799
2959
  @_connection_options(profile_short=False)
@@ -2825,7 +2985,7 @@ def collection_get(collection_id, profile, api_url):
2825
2985
  @click.option('--name', default=None, help='Collection name')
2826
2986
  @click.option('--description', default=None, help='Collection description')
2827
2987
  @click.option('--type', 'collection_type', default=None,
2828
- type=click.Choice(['collection', 'skill', 'external_source']),
2988
+ type=click.Choice(['collection', 'skill', 'external_source', 'db']),
2829
2989
  help='Collection type')
2830
2990
  @click.option('--public/--private', 'public', default=None, help='Make the collection public or private')
2831
2991
  @click.option('--indexed/--not-indexed', 'indexed', default=None, help='Enable or disable indexing')
@@ -3128,7 +3288,11 @@ def _build_task_payload(**kwargs):
3128
3288
 
3129
3289
  @cli.group()
3130
3290
  def task():
3131
- """Task management commands."""
3291
+ """Task management commands.
3292
+
3293
+ Evaluations and simulations of a task live under `pt eval`
3294
+ (add/run/results, simulate/simulations) — they are not subcommands here.
3295
+ """
3132
3296
  pass
3133
3297
 
3134
3298
 
@@ -4789,8 +4953,8 @@ def eval_delete(task_id, evaluation_data_id, profile, api_url):
4789
4953
  help='When evaluations run: manual/daily/weekly/monthly')
4790
4954
  @click.option('--evaluator-agent-id', 'default_evaluator_agent_id', default=None, type=int,
4791
4955
  help='Default grading agent')
4792
- @click.option('--pass-threshold', 'evaluation_pass_threshold', default=None, type=float,
4793
- help='Pass threshold (0-1)')
4956
+ @click.option('--pass-threshold', 'evaluation_pass_threshold', default=None, type=click.IntRange(1, 100),
4957
+ help='Pass threshold as a percentage, 1-100 (e.g. 80)')
4794
4958
  @click.option('--message-delay-ms', 'evaluation_message_delay_ms', default=None, type=int,
4795
4959
  help='Delay between messages during a run')
4796
4960
  @_connection_options
@@ -696,7 +696,7 @@ async def create_collection(
696
696
  config: RunnableConfig,
697
697
  name: Annotated[str, Field(description="Collection name")],
698
698
  description: Annotated[Optional[str], Field(description="Collection description")] = None,
699
- collection_type: Annotated[Optional[str], Field(description="collection/skill/external_source (server default: collection)")] = None,
699
+ collection_type: Annotated[Optional[str], Field(description="collection/skill/external_source/db (server default: collection)")] = None,
700
700
  public: Annotated[Optional[bool], Field(description="Make the collection public (server default: private)")] = None,
701
701
  ) -> str:
702
702
  """Create a PrimeThink collection."""
@@ -722,7 +722,7 @@ async def update_collection(
722
722
  collection_id: Annotated[str, Field(description="Collection ID")],
723
723
  name: Annotated[Optional[str], Field(description="Collection name")] = None,
724
724
  description: Annotated[Optional[str], Field(description="Collection description")] = None,
725
- collection_type: Annotated[Optional[str], Field(description="collection/skill/external_source")] = None,
725
+ collection_type: Annotated[Optional[str], Field(description="collection/skill/external_source/db")] = None,
726
726
  public: Annotated[Optional[bool], Field(description="Make the collection public")] = None,
727
727
  indexed: Annotated[Optional[bool], Field(description="Enable or disable indexing")] = None,
728
728
  ocr_instructions: Annotated[Optional[str], Field(description="OCR instructions for document processing")] = None,
@@ -1452,7 +1452,7 @@ async def set_task_evaluation_settings(
1452
1452
  evaluation_active: Annotated[Optional[bool], Field(description="Enable/disable evaluation")] = None,
1453
1453
  evaluation_run_time: Annotated[Optional[str], Field(description="manual/daily/weekly/monthly")] = None,
1454
1454
  default_evaluator_agent_id: Annotated[Optional[int], Field(description="Default grading agent")] = None,
1455
- evaluation_pass_threshold: Annotated[Optional[float], Field(description="Pass threshold (0-1)")] = None,
1455
+ evaluation_pass_threshold: Annotated[Optional[int], Field(ge=1, le=100, description="Pass threshold as a percentage, 1-100 (e.g. 80)")] = None,
1456
1456
  evaluation_message_delay_ms: Annotated[Optional[int], Field(description="Delay between messages")] = None,
1457
1457
  ) -> str:
1458
1458
  """Update a task's evaluation settings (the plan configuration)."""