makea-cli 0.2.15__tar.gz → 0.2.16__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 (71) hide show
  1. {makea_cli-0.2.15 → makea_cli-0.2.16}/CHANGELOG.md +22 -0
  2. {makea_cli-0.2.15 → makea_cli-0.2.16}/PKG-INFO +6 -1
  3. {makea_cli-0.2.15 → makea_cli-0.2.16}/README.md +5 -0
  4. makea_cli-0.2.16/makea_cli/__init__.py +1 -0
  5. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/api_client.py +54 -3
  6. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/__init__.py +2 -0
  7. makea_cli-0.2.16/makea_cli/commands/playbook/__init__.py +0 -0
  8. makea_cli-0.2.16/makea_cli/commands/playbook/cmd.py +329 -0
  9. {makea_cli-0.2.15 → makea_cli-0.2.16}/pyproject.toml +1 -1
  10. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/golden_manifest.json +437 -0
  11. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_persona_commands.py +78 -0
  12. {makea_cli-0.2.15 → makea_cli-0.2.16}/uv.lock +1 -1
  13. makea_cli-0.2.15/makea_cli/__init__.py +0 -1
  14. {makea_cli-0.2.15 → makea_cli-0.2.16}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
  15. {makea_cli-0.2.15 → makea_cli-0.2.16}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
  16. {makea_cli-0.2.15 → makea_cli-0.2.16}/.claude/skills/link-supplier-user/SKILL.md +0 -0
  17. {makea_cli-0.2.15 → makea_cli-0.2.16}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
  18. {makea_cli-0.2.15 → makea_cli-0.2.16}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
  19. {makea_cli-0.2.15 → makea_cli-0.2.16}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
  20. {makea_cli-0.2.15 → makea_cli-0.2.16}/.gitignore +0 -0
  21. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea-collection-upload/SKILL.md +0 -0
  22. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea-collection-upload/scripts/create_products.py +0 -0
  23. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/__main__.py +0 -0
  24. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/auth_pkce.py +0 -0
  25. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/broker.py +0 -0
  26. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/_util.py +0 -0
  27. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/analytics/__init__.py +0 -0
  28. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/analytics/cmd.py +0 -0
  29. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/auth/__init__.py +0 -0
  30. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/auth/cmd.py +0 -0
  31. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/brand/__init__.py +0 -0
  32. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/brand/cmd.py +0 -0
  33. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/designer/__init__.py +0 -0
  34. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/designer/cmd.py +0 -0
  35. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/directory/__init__.py +0 -0
  36. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/directory/cmd.py +0 -0
  37. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/document/__init__.py +0 -0
  38. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/document/cmd.py +0 -0
  39. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/email/__init__.py +0 -0
  40. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/email/cmd.py +0 -0
  41. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/factory/__init__.py +0 -0
  42. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/factory/cmd.py +0 -0
  43. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/library/__init__.py +0 -0
  44. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/library/cmd.py +0 -0
  45. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/metrics/__init__.py +0 -0
  46. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/metrics/cmd.py +0 -0
  47. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/orders/__init__.py +0 -0
  48. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/orders/cmd.py +0 -0
  49. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/product/__init__.py +0 -0
  50. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/product/cmd.py +0 -0
  51. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/production/__init__.py +0 -0
  52. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/production/cmd.py +0 -0
  53. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/production/pricing_spec.py +0 -0
  54. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/supplier/__init__.py +0 -0
  55. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/commands/supplier/cmd.py +0 -0
  56. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/config.py +0 -0
  57. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/errors.py +0 -0
  58. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/identity.py +0 -0
  59. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/main.py +0 -0
  60. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/output.py +0 -0
  61. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/posthog_client.py +0 -0
  62. {makea_cli-0.2.15 → makea_cli-0.2.16}/makea_cli/registry.py +0 -0
  63. {makea_cli-0.2.15 → makea_cli-0.2.16}/npm/makea-cli/README.md +0 -0
  64. {makea_cli-0.2.15 → makea_cli-0.2.16}/npm/makea-cli/makea-cli.mjs +0 -0
  65. {makea_cli-0.2.15 → makea_cli-0.2.16}/npm/makea-cli/package.json +0 -0
  66. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_broker.py +0 -0
  67. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_currency_conversion.py +0 -0
  68. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_output_contract.py +0 -0
  69. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_pm_reads.py +0 -0
  70. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_registry.py +0 -0
  71. {makea_cli-0.2.15 → makea_cli-0.2.16}/tests/test_supplier_scorecard.py +0 -0
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.16
4
+
5
+ New: an admin can read, add, edit and remove the AI playbook rules of one
6
+ person (backend 2026-10-18, deploy e408a077 or later).
7
+
8
+ - `admin list rule --user <user_id|email> [--status …] [--all]`: every rule
9
+ that holds for the person, from their team, their own account and, for a
10
+ factory, its company. Archived rules only with `--all`.
11
+ - `admin get rule <rule_id> --user …`: one rule with its evidence.
12
+ - `admin create rule --user … --text … --reason …`: admin-authored, active
13
+ unless `--status`. Goes to the team when there is one (`--scope` to pick).
14
+ A repeat or a conflict is refused unless `--force`.
15
+ - `admin update rule <rule_id> --user … --reason …`: text, status,
16
+ applies_to, which products (`--when`) or mode.
17
+ - `admin delete rule <rule_id> --user … --reason …`: archives the rule, the
18
+ way the brand's own dismiss does. It leaves the brand's Rules page and every
19
+ prompt, and Beeboo will not propose the same words again. `update rule
20
+ --status active` brings it back.
21
+
22
+ Every write needs `--reason`; the rule records who changed it, why and what
23
+ it was before. An email on two accounts is refused with both user ids.
24
+
3
25
  ## 0.2.15
4
26
 
5
27
  **Behaviour change: `admin get currency-conversion` returns different
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: makea-cli
3
- Version: 0.2.15
3
+ Version: 0.2.16
4
4
  Summary: Makea admin CLI (browser login + Makea admin HTTP API)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: click<9,>=8.1
@@ -92,9 +92,11 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
92
92
  | `makea-cli admin backfill supplier-id` | write(never) | `POST /admin/supplier/backfill_supplier_id_with_quote_requests` | Migrate supplier_id from a wrong UUID to the Cognito user sub (user_id): profile + Cognito supplierReferenceId, optio… | `backfill-supplier-id` |
93
93
  | `makea-cli admin create library-element` | write(auto) | `POST /admin/designer/{user_id}/library_components` | save a reusable component under a designer. Provide --section and --values (see admin get library-schema). | `library-create` |
94
94
  | `makea-cli admin create quote-request` | write(ask) | `POST /admin/supplier/create_quote_request` | create/send a quote request to a supplier for a product (admin). | `create-quote-request` |
95
+ | `makea-cli admin create rule` | write(ask) | `POST /admin/playbook/users/{user}/rules` | add a rule to one person's AI playbook (user_id or email); admin-authored, active unless --status. Refuses a repeat or a conflict unless --force. --reason required. | — |
95
96
  | `makea-cli admin create supplier` | write(ask) | `POST /admin/supplier/add_supplier` | create supplier from JSON file (admin). Run `makea-cli get-supplier-profile-schema` FIRST to get the field taxonomy (… | `add-supplier` |
96
97
  | `makea-cli admin create user` | write(never) | `POST /admin/users/create` | register a user and confirm the Cognito account in one step (no verification email). Allowed --user-type values: "Fas… | `create-user` |
97
98
  | `makea-cli admin delete library-element` | write(never) | `DELETE /admin/designer/{user_id}/library_components/{component_id}` | remove a component from the designer's org library. | `library-delete` |
99
+ | `makea-cli admin delete rule` | write(ask) | `DELETE /admin/playbook/users/{user}/rules/{rule_id}` | remove a rule: archived, with who and why kept on it (`update rule --status active` restores it). --reason required. | — |
98
100
  | `makea-cli admin download document` | read | `GET /admin/document/download` | save a document file (document id + designer user_id). | `download-document` |
99
101
  | `makea-cli admin download product-document` | read | `GET /admin/document/download` | Resolve product via admin list product-order payload, then GET /admin/document/download. | `download-product-document` |
100
102
  | `makea-cli admin generate pricing-groups` | write(ask) | `POST /admin/production_orders/{id}/details/update` | Lay a production order's variants into pricing groups along one axis (--by style \| colour \| variant \| all) with th… | `production-groups-generate` |
@@ -106,6 +108,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
106
108
  | `makea-cli admin get production-pricing` | read | `GET /admin/production_orders/{id}/details` | show a production order's price tiers, MOQ, sizes/colours, pricing groups and cart coverage. | `production-pricing-show` |
107
109
  | `makea-cli admin get quote-request` | read | `GET /admin/quote_requests/{quote_request_id}` | one quote request in full. | — |
108
110
  | `makea-cli admin get revenue-metrics` | read | `GET /admin/business-metrics/cumulative-payment-and-platform-revenue` | platform-wide payment received and platform revenue (service fee + transaction fee) bucketed over a date range. | `get-cumulative-payment-and-platform-revenue` |
111
+ | `makea-cli admin get rule` | read | `GET /admin/playbook/users/{user}/rules/{rule_id}` | one rule in full, with its evidence: where it came from, who changed it and why. | — |
109
112
  | `makea-cli admin get supplier-profile-schema` | read | `GET /admin/supplier/profile/schema` | supplier profile v2 schema (section/field taxonomy: sections, fields, options, required rules). | `get-supplier-profile-schema` |
110
113
  | `makea-cli admin get tech-spec-extraction` | read | `GET /admin/products/{id}/extract-tech-specifications` | AI-generate specs (not saved). | `extract-ai-tech-spec` |
111
114
  | `makea-cli admin get top-clicks` | read | `POST /admin/analytics/query` | which buttons/elements get clicked most. | `analytics-top-clicks` |
@@ -116,6 +119,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
116
119
  | `makea-cli admin list production-order` | read | `GET /admin/production_orders?type=PRODUCTION` | 大货 (production) orders only. For restock use admin list restock-order; for shipping use admin list shipping-order. | `list-all-production-orders` |
117
120
  | `makea-cli admin list quote-request` | read | `GET /admin/quote_requests` | quote requests, optionally for one product (--product) or one supplier (--supplier, GET /admin/quote_requests/supplie… | — |
118
121
  | `makea-cli admin list restock-order` | read | `GET /admin/production_orders?type=RESTOCK` | restock orders only. Restock shares the production_orders table; this filters to type=RESTOCK. | `list-all-restock-orders` |
122
+ | `makea-cli admin list rule` | read | `GET /admin/playbook/users/{user}/rules` | every AI playbook rule that holds for one person (team, own account, factory): status, scope, text, which products, effect, source. Archived ones only with --all. | — |
119
123
  | `makea-cli admin list sampling-order` | read | `GET /admin/sampling_orders/get_all` | paginated list of all sampling orders (admin). | `list-all-sampling-orders` |
120
124
  | `makea-cli admin list shipping-order` | read | `GET /admin/shipping_orders` | paginated list of all shipping orders (admin). | `list-all-shipping-orders` |
121
125
  | `makea-cli admin list supplier` | read | `GET /admin/supplier/get_all_suppliers` | list all suppliers (admin). | `list-suppliers` |
@@ -129,6 +133,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
129
133
  | `makea-cli admin update library-element` | write(auto) | `PATCH /admin/designer/{user_id}/library_components/{component_id}` | update a component's name and/or values (empty value clears a field). | `library-update` |
130
134
  | `makea-cli admin update quote` | write(ask) | `POST /admin/product/edit_supplier_link_override_info` | admin updates an existing quote (supplier link) in place. Only the options you pass are sent, and the backend leaves … | `update-supplier-quote` |
131
135
  | `makea-cli admin update quote-request` | write(ask) | `PATCH /admin/quote_requests/{quote_request_id}/update` | set a quote request's stage, status and/or due date. Only what you pass is changed. This is the door that unblocks a … | `update-quote-request` |
136
+ | `makea-cli admin update rule` | write(ask) | `PATCH /admin/playbook/users/{user}/rules/{rule_id}` | change a rule's text, status, applies_to, when or mode. --reason required; the rule keeps what it was before. | — |
132
137
  | `makea-cli admin update supplier` | write(ask) | `PUT /admin/supplier/update_supplier` | update an existing supplier from a JSON file (admin). Run `makea-cli get-supplier-profile-schema` FIRST to get the fi… | `update-supplier` |
133
138
  | `makea-cli admin update tech-spec` | write(ask) | `POST /admin/products/{id}/technical-specifications` | persist specs from a JSON file. | `save-product-tech-spec` |
134
139
  | `makea-cli admin update tracking-status` | write(ask) | `POST /admin/tracking/carrier_status` | apply a carrier status looked up by hand (tracking-sweep skill) to one order: status, fan-out, delivered emails (--silent: state only). | — |
@@ -80,9 +80,11 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
80
80
  | `makea-cli admin backfill supplier-id` | write(never) | `POST /admin/supplier/backfill_supplier_id_with_quote_requests` | Migrate supplier_id from a wrong UUID to the Cognito user sub (user_id): profile + Cognito supplierReferenceId, optio… | `backfill-supplier-id` |
81
81
  | `makea-cli admin create library-element` | write(auto) | `POST /admin/designer/{user_id}/library_components` | save a reusable component under a designer. Provide --section and --values (see admin get library-schema). | `library-create` |
82
82
  | `makea-cli admin create quote-request` | write(ask) | `POST /admin/supplier/create_quote_request` | create/send a quote request to a supplier for a product (admin). | `create-quote-request` |
83
+ | `makea-cli admin create rule` | write(ask) | `POST /admin/playbook/users/{user}/rules` | add a rule to one person's AI playbook (user_id or email); admin-authored, active unless --status. Refuses a repeat or a conflict unless --force. --reason required. | — |
83
84
  | `makea-cli admin create supplier` | write(ask) | `POST /admin/supplier/add_supplier` | create supplier from JSON file (admin). Run `makea-cli get-supplier-profile-schema` FIRST to get the field taxonomy (… | `add-supplier` |
84
85
  | `makea-cli admin create user` | write(never) | `POST /admin/users/create` | register a user and confirm the Cognito account in one step (no verification email). Allowed --user-type values: "Fas… | `create-user` |
85
86
  | `makea-cli admin delete library-element` | write(never) | `DELETE /admin/designer/{user_id}/library_components/{component_id}` | remove a component from the designer's org library. | `library-delete` |
87
+ | `makea-cli admin delete rule` | write(ask) | `DELETE /admin/playbook/users/{user}/rules/{rule_id}` | remove a rule: archived, with who and why kept on it (`update rule --status active` restores it). --reason required. | — |
86
88
  | `makea-cli admin download document` | read | `GET /admin/document/download` | save a document file (document id + designer user_id). | `download-document` |
87
89
  | `makea-cli admin download product-document` | read | `GET /admin/document/download` | Resolve product via admin list product-order payload, then GET /admin/document/download. | `download-product-document` |
88
90
  | `makea-cli admin generate pricing-groups` | write(ask) | `POST /admin/production_orders/{id}/details/update` | Lay a production order's variants into pricing groups along one axis (--by style \| colour \| variant \| all) with th… | `production-groups-generate` |
@@ -94,6 +96,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
94
96
  | `makea-cli admin get production-pricing` | read | `GET /admin/production_orders/{id}/details` | show a production order's price tiers, MOQ, sizes/colours, pricing groups and cart coverage. | `production-pricing-show` |
95
97
  | `makea-cli admin get quote-request` | read | `GET /admin/quote_requests/{quote_request_id}` | one quote request in full. | — |
96
98
  | `makea-cli admin get revenue-metrics` | read | `GET /admin/business-metrics/cumulative-payment-and-platform-revenue` | platform-wide payment received and platform revenue (service fee + transaction fee) bucketed over a date range. | `get-cumulative-payment-and-platform-revenue` |
99
+ | `makea-cli admin get rule` | read | `GET /admin/playbook/users/{user}/rules/{rule_id}` | one rule in full, with its evidence: where it came from, who changed it and why. | — |
97
100
  | `makea-cli admin get supplier-profile-schema` | read | `GET /admin/supplier/profile/schema` | supplier profile v2 schema (section/field taxonomy: sections, fields, options, required rules). | `get-supplier-profile-schema` |
98
101
  | `makea-cli admin get tech-spec-extraction` | read | `GET /admin/products/{id}/extract-tech-specifications` | AI-generate specs (not saved). | `extract-ai-tech-spec` |
99
102
  | `makea-cli admin get top-clicks` | read | `POST /admin/analytics/query` | which buttons/elements get clicked most. | `analytics-top-clicks` |
@@ -104,6 +107,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
104
107
  | `makea-cli admin list production-order` | read | `GET /admin/production_orders?type=PRODUCTION` | 大货 (production) orders only. For restock use admin list restock-order; for shipping use admin list shipping-order. | `list-all-production-orders` |
105
108
  | `makea-cli admin list quote-request` | read | `GET /admin/quote_requests` | quote requests, optionally for one product (--product) or one supplier (--supplier, GET /admin/quote_requests/supplie… | — |
106
109
  | `makea-cli admin list restock-order` | read | `GET /admin/production_orders?type=RESTOCK` | restock orders only. Restock shares the production_orders table; this filters to type=RESTOCK. | `list-all-restock-orders` |
110
+ | `makea-cli admin list rule` | read | `GET /admin/playbook/users/{user}/rules` | every AI playbook rule that holds for one person (team, own account, factory): status, scope, text, which products, effect, source. Archived ones only with --all. | — |
107
111
  | `makea-cli admin list sampling-order` | read | `GET /admin/sampling_orders/get_all` | paginated list of all sampling orders (admin). | `list-all-sampling-orders` |
108
112
  | `makea-cli admin list shipping-order` | read | `GET /admin/shipping_orders` | paginated list of all shipping orders (admin). | `list-all-shipping-orders` |
109
113
  | `makea-cli admin list supplier` | read | `GET /admin/supplier/get_all_suppliers` | list all suppliers (admin). | `list-suppliers` |
@@ -117,6 +121,7 @@ exit code:`0` 成功 · `2` 用法错 · `3` 未登录或身份与 persona 不
117
121
  | `makea-cli admin update library-element` | write(auto) | `PATCH /admin/designer/{user_id}/library_components/{component_id}` | update a component's name and/or values (empty value clears a field). | `library-update` |
118
122
  | `makea-cli admin update quote` | write(ask) | `POST /admin/product/edit_supplier_link_override_info` | admin updates an existing quote (supplier link) in place. Only the options you pass are sent, and the backend leaves … | `update-supplier-quote` |
119
123
  | `makea-cli admin update quote-request` | write(ask) | `PATCH /admin/quote_requests/{quote_request_id}/update` | set a quote request's stage, status and/or due date. Only what you pass is changed. This is the door that unblocks a … | `update-quote-request` |
124
+ | `makea-cli admin update rule` | write(ask) | `PATCH /admin/playbook/users/{user}/rules/{rule_id}` | change a rule's text, status, applies_to, when or mode. --reason required; the rule keeps what it was before. | — |
120
125
  | `makea-cli admin update supplier` | write(ask) | `PUT /admin/supplier/update_supplier` | update an existing supplier from a JSON file (admin). Run `makea-cli get-supplier-profile-schema` FIRST to get the fi… | `update-supplier` |
121
126
  | `makea-cli admin update tech-spec` | write(ask) | `POST /admin/products/{id}/technical-specifications` | persist specs from a JSON file. | `save-product-tech-spec` |
122
127
  | `makea-cli admin update tracking-status` | write(ask) | `POST /admin/tracking/carrier_status` | apply a carrier status looked up by hand (tracking-sweep skill) to one order: status, fan-out, delivered emails (--silent: state only). | — |
@@ -0,0 +1 @@
1
+ __version__ = "0.2.16"
@@ -5,7 +5,7 @@ import mimetypes
5
5
  from pathlib import Path
6
6
  from pathlib import PurePosixPath
7
7
  from typing import Any
8
- from urllib.parse import unquote
8
+ from urllib.parse import quote, unquote
9
9
 
10
10
  import httpx
11
11
 
@@ -197,8 +197,14 @@ def patch_json(path: str, body: Any, *, timeout: float = 60.0) -> Any:
197
197
  return _request("PATCH", path, json_body=body, timeout=timeout)
198
198
 
199
199
 
200
- def delete_json(path: str, *, params: dict[str, Any] | None = None, timeout: float = 60.0) -> Any:
201
- return _request("DELETE", path, params=params, timeout=timeout)
200
+ def delete_json(
201
+ path: str,
202
+ *,
203
+ params: dict[str, Any] | None = None,
204
+ body: Any = None,
205
+ timeout: float = 60.0,
206
+ ) -> Any:
207
+ return _request("DELETE", path, params=params, json_body=body, timeout=timeout)
202
208
 
203
209
 
204
210
  def send_admin_raw_email(body: dict[str, Any]) -> Any:
@@ -1047,6 +1053,51 @@ def admin_delete_library_component(
1047
1053
  )
1048
1054
 
1049
1055
 
1056
+ # --- admin: one person's AI playbook rules ---------------------------------------
1057
+ # Addressed by user_id or email; the backend resolves every scope the person's
1058
+ # rules live in (team, own account, factory) and refuses an email that is on
1059
+ # more than one account. Every write carries a reason the rule keeps.
1060
+
1061
+
1062
+ def _user_rules_path(user: str, rule_id: str | None = None) -> str:
1063
+ path = f"/admin/playbook/users/{quote(user.strip(), safe='@')}/rules"
1064
+ return f"{path}/{quote(rule_id, safe='')}" if rule_id else path
1065
+
1066
+
1067
+ def admin_list_user_rules(
1068
+ user: str, *, status: str | None = None, include_archived: bool = False
1069
+ ) -> Any:
1070
+ """GET /admin/playbook/users/{user}/rules — every rule that holds for one person."""
1071
+ params: dict[str, Any] = {}
1072
+ if status:
1073
+ params["status"] = status
1074
+ if include_archived:
1075
+ params["include_archived"] = "1"
1076
+ return get_json(_user_rules_path(user), params=params or None)
1077
+
1078
+
1079
+ def admin_get_user_rule(user: str, rule_id: str) -> Any:
1080
+ """GET /admin/playbook/users/{user}/rules/{rule_id} — one rule, evidence included."""
1081
+ return get_json(_user_rules_path(user, rule_id))
1082
+
1083
+
1084
+ def admin_create_user_rule(user: str, body: dict[str, Any]) -> Any:
1085
+ """POST /admin/playbook/users/{user}/rules — {text, reason, scope?, applies_to?,
1086
+ when?, mode?, category?, polarity?, status?, force?}."""
1087
+ return post_json(_user_rules_path(user), body)
1088
+
1089
+
1090
+ def admin_update_user_rule(user: str, rule_id: str, body: dict[str, Any]) -> Any:
1091
+ """PATCH /admin/playbook/users/{user}/rules/{rule_id} — {text?, status?,
1092
+ applies_to?, when?, mode?, reason}."""
1093
+ return patch_json(_user_rules_path(user, rule_id), body)
1094
+
1095
+
1096
+ def admin_delete_user_rule(user: str, rule_id: str, reason: str) -> Any:
1097
+ """DELETE /admin/playbook/users/{user}/rules/{rule_id} — archives it, with the reason."""
1098
+ return delete_json(_user_rules_path(user, rule_id), body={"reason": reason})
1099
+
1100
+
1050
1101
  # --- supplier persona: a factory acting on its own RFQs, quotes and orders --------
1051
1102
 
1052
1103
 
@@ -13,6 +13,7 @@ from makea_cli.commands.factory.cmd import register as register_factory
13
13
  from makea_cli.commands.library.cmd import register as register_library
14
14
  from makea_cli.commands.metrics.cmd import register as register_metrics
15
15
  from makea_cli.commands.orders.cmd import register as register_orders
16
+ from makea_cli.commands.playbook.cmd import register as register_playbook
16
17
  from makea_cli.commands.product.cmd import register as register_product
17
18
  from makea_cli.commands.production.cmd import register as register_production
18
19
  from makea_cli.commands.supplier.cmd import register as register_supplier
@@ -33,3 +34,4 @@ def register_all(app: typer.Typer) -> None:
33
34
  register_library(app)
34
35
  register_factory(app)
35
36
  register_brand(app)
37
+ register_playbook(app)
@@ -0,0 +1,329 @@
1
+ """Admin commands for one person's AI playbook rules (Knowledge > Rules).
2
+
3
+ A *rule* is one line Beeboo follows for an account ("Always book DHL Express",
4
+ "Approve PP samples from photos"). Rules live on the brand's team playbook, on
5
+ the person's own account, and, for a factory, on its company playbook. These
6
+ commands address the person (``--user`` takes a user_id or an email) and the
7
+ backend finds every scope; an email on more than one account is refused with
8
+ the candidates, so pass the user_id then.
9
+
10
+ Every write needs ``--reason``. The rule keeps it as an evidence entry
11
+ (``admin_add`` / ``admin_edit`` / ``admin_delete`` with who, why, when and,
12
+ for edits and deletes, what the rule was before), and the audit log records it.
13
+ ``delete`` archives: the rule leaves the brand's Rules page and every prompt,
14
+ the row stays as the record, and ``update --status active`` brings it back.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from typing import Any
21
+
22
+ import typer
23
+ from rich.console import Console
24
+
25
+ from makea_cli import api_client
26
+ from makea_cli.commands._util import print_json_or_exit
27
+ from makea_cli.registry import command
28
+
29
+ console = Console()
30
+
31
+ _STATUSES = ("pending", "active", "muted", "archived")
32
+ _SCOPES = ("org", "user", "supplier")
33
+ _APPLIES_TO_KINDS = ("style", "collection", "supplier", "element")
34
+ _MODES = ("remind", "act")
35
+
36
+ _RULES_EPILOG = """
37
+ --user takes a user_id (Cognito sub) or an email.
38
+
39
+ Scopes: ORG#… is the brand's team playbook, USER#… the person's own,
40
+ SUPPLIER#… a factory's. A new rule goes to the factory playbook for a supplier,
41
+ else the team's (the person's own when there is no team); --scope overrides.
42
+
43
+ --applies-to narrows a rule to one thing: KIND:ID[:LABEL] with KIND one of
44
+ style, collection, supplier, element (e.g. supplier:sup_123:Ningbo Knit).
45
+ "all" (update only) widens it back to the whole account.
46
+
47
+ --when is which products it is for, as JSON:
48
+ '{"categories": ["knitwear"], "service_modes": ["cmt"]}'
49
+ Order conditions the rule already has (units, destination) are kept; {} means
50
+ every product.
51
+
52
+ Examples
53
+ makea-cli admin list rule --user brand@example.com
54
+ makea-cli admin get rule rul_0123456789ab --user brand@example.com
55
+ makea-cli admin create rule --user brand@example.com \\
56
+ --text "Always book DHL Express for samples" --reason "Agreed on the 14 Oct call"
57
+ makea-cli admin update rule rul_0123456789ab --user brand@example.com \\
58
+ --status muted --reason "Brand asked to pause it by email"
59
+ makea-cli admin delete rule rul_0123456789ab --user brand@example.com \\
60
+ --reason "Learned from another brand's thread"
61
+ """.strip()
62
+
63
+
64
+ def _user_option() -> Any:
65
+ return typer.Option(..., "--user", "-u", help="Target person: user_id or email.")
66
+
67
+
68
+ def _reason_option() -> Any:
69
+ return typer.Option(
70
+ ...,
71
+ "--reason",
72
+ "-r",
73
+ help="Why (required). Kept on the rule and in the audit log.",
74
+ )
75
+
76
+
77
+ def _reason(raw: str) -> str:
78
+ reason = " ".join(raw.split())
79
+ if not reason:
80
+ raise typer.BadParameter("--reason must not be empty")
81
+ return reason
82
+
83
+
84
+ def _applies_to(raw: str) -> dict[str, str]:
85
+ if raw.strip().lower() == "all":
86
+ return {"kind": "all"}
87
+ kind, sep, rest = raw.partition(":")
88
+ item_id, _, label = rest.partition(":")
89
+ kind = kind.strip().lower()
90
+ if not sep or kind not in _APPLIES_TO_KINDS or not item_id.strip():
91
+ raise typer.BadParameter(
92
+ f"--applies-to: expected KIND:ID[:LABEL] with KIND one of {', '.join(_APPLIES_TO_KINDS)}"
93
+ )
94
+ out = {"kind": kind, "id": item_id.strip()}
95
+ if label.strip():
96
+ out["label"] = label.strip()
97
+ return out
98
+
99
+
100
+ def _when(raw: str) -> dict[str, Any]:
101
+ try:
102
+ data = json.loads(raw)
103
+ except json.JSONDecodeError as e:
104
+ raise typer.BadParameter(f"--when: invalid JSON ({e})") from e
105
+ if not isinstance(data, dict):
106
+ raise typer.BadParameter("--when: expected a JSON object")
107
+ return data
108
+
109
+
110
+ def _choice(value: str | None, allowed: tuple[str, ...], flag: str) -> str | None:
111
+ if value is None:
112
+ return None
113
+ value = value.strip().lower()
114
+ if value not in allowed:
115
+ raise typer.BadParameter(f"{flag}: one of {', '.join(allowed)}")
116
+ return value
117
+
118
+
119
+ def register(app: typer.Typer) -> None:
120
+ @command(
121
+ app,
122
+ "admin",
123
+ "list",
124
+ "rule",
125
+ api="GET /admin/playbook/users/{user}/rules",
126
+ help=(
127
+ "GET /admin/playbook/users/{user}/rules — every AI playbook rule that holds "
128
+ "for one person (team, own account, factory): status, scope, text, "
129
+ "applies_to/when, mode/effect, source line, dates. Archived ones only with --all."
130
+ ),
131
+ epilog=_RULES_EPILOG,
132
+ )
133
+ def list_rules(
134
+ user: str = _user_option(),
135
+ status: str | None = typer.Option(
136
+ None,
137
+ "--status",
138
+ "-s",
139
+ help=f"Only these statuses, comma-separated ({', '.join(_STATUSES)}).",
140
+ ),
141
+ include_archived: bool = typer.Option(
142
+ False, "--all", help="Also list archived (deleted) rules."
143
+ ),
144
+ ) -> None:
145
+ """List a person's playbook rules."""
146
+ if status:
147
+ for s in status.split(","):
148
+ _choice(s, _STATUSES, "--status")
149
+ print_json_or_exit(
150
+ console,
151
+ lambda: api_client.admin_list_user_rules(
152
+ user, status=status, include_archived=include_archived
153
+ ),
154
+ )
155
+
156
+ @command(
157
+ app,
158
+ "admin",
159
+ "get",
160
+ "rule",
161
+ api="GET /admin/playbook/users/{user}/rules/{rule_id}",
162
+ help=(
163
+ "GET /admin/playbook/users/{user}/rules/{rule_id} — one rule in full, "
164
+ "evidence (where it came from, who changed it and why) included."
165
+ ),
166
+ epilog=_RULES_EPILOG,
167
+ )
168
+ def get_rule(
169
+ rule_id: str = typer.Argument(..., help="Rule id (rul_…)."),
170
+ user: str = _user_option(),
171
+ ) -> None:
172
+ """Show one playbook rule with its evidence."""
173
+ print_json_or_exit(console, lambda: api_client.admin_get_user_rule(user, rule_id))
174
+
175
+ @command(
176
+ app,
177
+ "admin",
178
+ "create",
179
+ "rule",
180
+ api="POST /admin/playbook/users/{user}/rules",
181
+ side_effects=[
182
+ "Beeboo follows the rule in this account's AI answers from now on",
183
+ "shows on the brand's Knowledge > Rules page (brand scopes)",
184
+ ],
185
+ approval="ask",
186
+ reversible=True,
187
+ idempotent=False,
188
+ help=(
189
+ "POST /admin/playbook/users/{user}/rules — add a rule to a person's playbook "
190
+ "(admin-authored, active unless --status). Refuses a repeat or a conflict "
191
+ "unless --force. --reason required."
192
+ ),
193
+ epilog=_RULES_EPILOG,
194
+ )
195
+ def create_rule(
196
+ user: str = _user_option(),
197
+ text: str = typer.Option(..., "--text", help="The rule, one line (max 160 chars)."),
198
+ reason: str = _reason_option(),
199
+ scope: str | None = typer.Option(
200
+ None, "--scope", help=f"Where it lives: {', '.join(_SCOPES)} (default: see below)."
201
+ ),
202
+ applies_to: str | None = typer.Option(
203
+ None, "--applies-to", help="Narrow to one thing: KIND:ID[:LABEL]."
204
+ ),
205
+ when: str | None = typer.Option(
206
+ None,
207
+ "--when",
208
+ help='Which products, JSON: {"categories": [...], "service_modes": [...]}.',
209
+ ),
210
+ mode: str | None = typer.Option(
211
+ None, "--mode", help="remind (default): Beeboo tells; act: Beeboo does it."
212
+ ),
213
+ category: str | None = typer.Option(
214
+ None,
215
+ "--category",
216
+ help="communication, spec_defaults, sourcing, commercial, logistics, process, "
217
+ "hard_constraint, … (default: classified from the text).",
218
+ ),
219
+ polarity: str | None = typer.Option(
220
+ None, "--polarity", help="always, never or prefer (default: from the text)."
221
+ ),
222
+ status: str | None = typer.Option(
223
+ None, "--status", help="Start as pending, active (default) or muted."
224
+ ),
225
+ force: bool = typer.Option(
226
+ False, "--force", help="Add even if it repeats or disagrees with an existing rule."
227
+ ),
228
+ ) -> None:
229
+ """Add a playbook rule for a person."""
230
+ body: dict[str, Any] = {"text": text, "reason": _reason(reason)}
231
+ if scope is not None:
232
+ body["scope"] = _choice(scope, _SCOPES, "--scope")
233
+ if applies_to is not None:
234
+ body["applies_to"] = _applies_to(applies_to)
235
+ if body["applies_to"].get("kind") == "all":
236
+ body.pop("applies_to")
237
+ if when is not None:
238
+ body["when"] = _when(when)
239
+ if mode is not None:
240
+ body["mode"] = _choice(mode, _MODES, "--mode")
241
+ if category is not None:
242
+ body["category"] = category.strip().lower()
243
+ if polarity is not None:
244
+ body["polarity"] = _choice(polarity, ("always", "never", "prefer"), "--polarity")
245
+ if status is not None:
246
+ body["status"] = _choice(status, ("pending", "active", "muted"), "--status")
247
+ if force:
248
+ body["force"] = True
249
+ print_json_or_exit(console, lambda: api_client.admin_create_user_rule(user, body))
250
+
251
+ @command(
252
+ app,
253
+ "admin",
254
+ "update",
255
+ "rule",
256
+ api="PATCH /admin/playbook/users/{user}/rules/{rule_id}",
257
+ side_effects=[
258
+ "changes what Beeboo does for this account",
259
+ "a learned rule becomes authored and stops expiring",
260
+ ],
261
+ approval="ask",
262
+ reversible=True,
263
+ idempotent=True,
264
+ help=(
265
+ "PATCH /admin/playbook/users/{user}/rules/{rule_id} — change a rule's text, "
266
+ "status, applies_to, when or mode. --reason required; the rule keeps what it was before."
267
+ ),
268
+ epilog=_RULES_EPILOG,
269
+ )
270
+ def update_rule(
271
+ rule_id: str = typer.Argument(..., help="Rule id (rul_…)."),
272
+ user: str = _user_option(),
273
+ reason: str = _reason_option(),
274
+ text: str | None = typer.Option(None, "--text", help="New words."),
275
+ status: str | None = typer.Option(None, "--status", help=f"One of {', '.join(_STATUSES)}."),
276
+ applies_to: str | None = typer.Option(
277
+ None, "--applies-to", help='KIND:ID[:LABEL], or "all" for the whole account.'
278
+ ),
279
+ when: str | None = typer.Option(
280
+ None, "--when", help="Which products, JSON ({} for every product)."
281
+ ),
282
+ mode: str | None = typer.Option(None, "--mode", help="remind or act."),
283
+ ) -> None:
284
+ """Edit a person's playbook rule."""
285
+ body: dict[str, Any] = {}
286
+ if text is not None:
287
+ body["text"] = text
288
+ if status is not None:
289
+ body["status"] = _choice(status, _STATUSES, "--status")
290
+ if applies_to is not None:
291
+ body["applies_to"] = _applies_to(applies_to)
292
+ if when is not None:
293
+ body["when"] = _when(when)
294
+ if mode is not None:
295
+ body["mode"] = _choice(mode, _MODES, "--mode")
296
+ if not body:
297
+ raise typer.BadParameter(
298
+ "nothing to change: pass --text, --status, --applies-to, --when or --mode"
299
+ )
300
+ body["reason"] = _reason(reason)
301
+ print_json_or_exit(console, lambda: api_client.admin_update_user_rule(user, rule_id, body))
302
+
303
+ @command(
304
+ app,
305
+ "admin",
306
+ "delete",
307
+ "rule",
308
+ api="DELETE /admin/playbook/users/{user}/rules/{rule_id}",
309
+ side_effects=[
310
+ "archives the rule: off the brand's Rules page and out of every AI prompt",
311
+ "the learner will not propose the same words again",
312
+ ],
313
+ approval="ask",
314
+ reversible=True,
315
+ idempotent=False,
316
+ help=(
317
+ "DELETE /admin/playbook/users/{user}/rules/{rule_id} — remove a rule (archived, "
318
+ "with who and why kept on it; update --status active restores it). --reason required."
319
+ ),
320
+ epilog=_RULES_EPILOG,
321
+ )
322
+ def delete_rule(
323
+ rule_id: str = typer.Argument(..., help="Rule id (rul_…)."),
324
+ user: str = _user_option(),
325
+ reason: str = _reason_option(),
326
+ ) -> None:
327
+ """Remove (archive) a person's playbook rule."""
328
+ why = _reason(reason)
329
+ print_json_or_exit(console, lambda: api_client.admin_delete_user_rule(user, rule_id, why))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "makea-cli"
7
- version = "0.2.15"
7
+ version = "0.2.16"
8
8
  description = "Makea admin CLI (browser login + Makea admin HTTP API)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"