makea-cli 0.2.14__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.14 → makea_cli-0.2.16}/CHANGELOG.md +50 -0
  2. {makea_cli-0.2.14 → makea_cli-0.2.16}/PKG-INFO +7 -2
  3. {makea_cli-0.2.14 → makea_cli-0.2.16}/README.md +6 -1
  4. makea_cli-0.2.16/makea_cli/__init__.py +1 -0
  5. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/api_client.py +66 -3
  6. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/__init__.py +2 -0
  7. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/metrics/cmd.py +27 -65
  8. makea_cli-0.2.16/makea_cli/commands/playbook/__init__.py +0 -0
  9. makea_cli-0.2.16/makea_cli/commands/playbook/cmd.py +329 -0
  10. {makea_cli-0.2.14 → makea_cli-0.2.16}/pyproject.toml +1 -1
  11. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/golden_manifest.json +440 -3
  12. makea_cli-0.2.16/tests/test_currency_conversion.py +106 -0
  13. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/test_persona_commands.py +78 -0
  14. {makea_cli-0.2.14 → makea_cli-0.2.16}/uv.lock +1 -1
  15. makea_cli-0.2.14/makea_cli/__init__.py +0 -1
  16. {makea_cli-0.2.14 → makea_cli-0.2.16}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
  17. {makea_cli-0.2.14 → makea_cli-0.2.16}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
  18. {makea_cli-0.2.14 → makea_cli-0.2.16}/.claude/skills/link-supplier-user/SKILL.md +0 -0
  19. {makea_cli-0.2.14 → makea_cli-0.2.16}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
  20. {makea_cli-0.2.14 → makea_cli-0.2.16}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
  21. {makea_cli-0.2.14 → makea_cli-0.2.16}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
  22. {makea_cli-0.2.14 → makea_cli-0.2.16}/.gitignore +0 -0
  23. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea-collection-upload/SKILL.md +0 -0
  24. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea-collection-upload/scripts/create_products.py +0 -0
  25. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/__main__.py +0 -0
  26. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/auth_pkce.py +0 -0
  27. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/broker.py +0 -0
  28. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/_util.py +0 -0
  29. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/analytics/__init__.py +0 -0
  30. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/analytics/cmd.py +0 -0
  31. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/auth/__init__.py +0 -0
  32. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/auth/cmd.py +0 -0
  33. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/brand/__init__.py +0 -0
  34. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/brand/cmd.py +0 -0
  35. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/designer/__init__.py +0 -0
  36. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/designer/cmd.py +0 -0
  37. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/directory/__init__.py +0 -0
  38. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/directory/cmd.py +0 -0
  39. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/document/__init__.py +0 -0
  40. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/document/cmd.py +0 -0
  41. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/email/__init__.py +0 -0
  42. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/email/cmd.py +0 -0
  43. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/factory/__init__.py +0 -0
  44. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/factory/cmd.py +0 -0
  45. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/library/__init__.py +0 -0
  46. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/library/cmd.py +0 -0
  47. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/metrics/__init__.py +0 -0
  48. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/orders/__init__.py +0 -0
  49. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/orders/cmd.py +0 -0
  50. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/product/__init__.py +0 -0
  51. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/product/cmd.py +0 -0
  52. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/production/__init__.py +0 -0
  53. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/production/cmd.py +0 -0
  54. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/production/pricing_spec.py +0 -0
  55. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/supplier/__init__.py +0 -0
  56. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/commands/supplier/cmd.py +0 -0
  57. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/config.py +0 -0
  58. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/errors.py +0 -0
  59. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/identity.py +0 -0
  60. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/main.py +0 -0
  61. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/output.py +0 -0
  62. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/posthog_client.py +0 -0
  63. {makea_cli-0.2.14 → makea_cli-0.2.16}/makea_cli/registry.py +0 -0
  64. {makea_cli-0.2.14 → makea_cli-0.2.16}/npm/makea-cli/README.md +0 -0
  65. {makea_cli-0.2.14 → makea_cli-0.2.16}/npm/makea-cli/makea-cli.mjs +0 -0
  66. {makea_cli-0.2.14 → makea_cli-0.2.16}/npm/makea-cli/package.json +0 -0
  67. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/test_broker.py +0 -0
  68. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/test_output_contract.py +0 -0
  69. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/test_pm_reads.py +0 -0
  70. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/test_registry.py +0 -0
  71. {makea_cli-0.2.14 → makea_cli-0.2.16}/tests/test_supplier_scorecard.py +0 -0
@@ -1,5 +1,55 @@
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
+
25
+ ## 0.2.15
26
+
27
+ **Behaviour change: `admin get currency-conversion` returns different
28
+ numbers.** This is not only a bug fix. Anyone who reconciled figures with
29
+ this command should expect them to move, by up to several percent per
30
+ currency. The same call on 2026-10-16:
31
+
32
+ | GBP per | 0.2.14 | 0.2.15 | 1,000 converted, 0.2.14 → 0.2.15 |
33
+ |---|---|---|---|
34
+ | CNY | 0.12 | 0.113 | 120.00 → 113.00 (−5.8%) |
35
+ | USD | 0.76 | 0.752 | 760.00 → 752.00 |
36
+ | EUR | 0.85 | 0.848 | 850.00 → 848.00 |
37
+
38
+ What changed:
39
+
40
+ - Rates now come from the platform (`GET /admin/fx/rates`, backend
41
+ 2026-10-16) instead of the CLI fetching the free FX source itself. They are
42
+ rounded **up to 3 decimals**, as the platform rounds them; 0.2.14 rounded
43
+ up to 2, which overstated cheap currencies such as CNY most.
44
+ - With one `--from` and `--amount`, `converted` is the platform's own
45
+ conversion, rounded **up** to the cent; 0.2.14 rounded to the nearest cent.
46
+ - So the numbers now match the platform's prices and revenue metrics, which
47
+ the 0.2.14 ones did not.
48
+ - Output keys are unchanged except `rounding` (now `"up"`, was
49
+ `"ceil_2dp"`), plus new `rate_decimals` and `unavailable`. A currency with
50
+ no rate still fails the command. Needs an admin login, like every `admin`
51
+ command.
52
+
3
53
  ## 0.2.14
4
54
 
5
55
  - `admin update tracking-status`: help text no longer describes it as "the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: makea-cli
3
- Version: 0.2.14
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,20 +92,23 @@ 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` |
101
103
  | `makea-cli admin get active-users` | read | `POST /admin/analytics/query` | Distinct active users per day over the last N days (DAU series). | `analytics-active-users` |
102
104
  | `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
103
- | `makea-cli admin get currency-conversion` | read | `currency-api` | avoids repeated lookups. | `convert-currency` |
105
+ | `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
104
106
  | `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
105
107
  | `makea-cli admin get library-schema` | read | `GET /admin/library_components/schema` | the tech-pack library taxonomy: sections and their library-role field keys (run this before create/update). | `library-schema` |
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,20 +80,23 @@ 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` |
89
91
  | `makea-cli admin get active-users` | read | `POST /admin/analytics/query` | Distinct active users per day over the last N days (DAU series). | `analytics-active-users` |
90
92
  | `makea-cli admin get analytics-query` | read | `POST /admin/analytics/query` | Run an arbitrary HogQL query (escape hatch). Example: analytics-query "SELECT count() FROM events" | `analytics-query` |
91
- | `makea-cli admin get currency-conversion` | read | `currency-api` | avoids repeated lookups. | `convert-currency` |
93
+ | `makea-cli admin get currency-conversion` | read | `GET /admin/fx/rates` | the platform's own exchange rates and rounding (rate table for several --from; one --from with --amount converts it). | `convert-currency` |
92
94
  | `makea-cli admin get feature-usage` | read | `POST /admin/analytics/query` | How many distinct users used a feature recently, plus event volume. Matches $pageview path and feature_time_spent eve… | `analytics-feature-usage` |
93
95
  | `makea-cli admin get library-schema` | read | `GET /admin/library_components/schema` | the tech-pack library taxonomy: sections and their library-role field keys (run this before create/update). | `library-schema` |
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:
@@ -844,6 +850,18 @@ def get_cumulative_payment_and_platform_revenue(
844
850
  )
845
851
 
846
852
 
853
+ def get_fx_rates(*, to: str, sources: list[str], amount: float | None = None) -> Any:
854
+ """
855
+ GET /admin/fx/rates — the platform's own exchange rates (rounded up to its
856
+ RATE_DECIMALS), and with one source and an amount, that amount converted the
857
+ way the platform converts it.
858
+ """
859
+ params: dict[str, Any] = {"to": to, "from": ",".join(sources)}
860
+ if amount is not None:
861
+ params["amount"] = amount
862
+ return get_json("/admin/fx/rates", params=params)
863
+
864
+
847
865
  def extract_ai_tech_specifications(product_reference_id: str, *, timeout: float = 300.0) -> Any:
848
866
  """
849
867
  GET /admin/products/{id}/extract-tech-specifications — synchronous AI extraction;
@@ -1035,6 +1053,51 @@ def admin_delete_library_component(
1035
1053
  )
1036
1054
 
1037
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
+
1038
1101
  # --- supplier persona: a factory acting on its own RFQs, quotes and orders --------
1039
1102
 
1040
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)
@@ -1,8 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import math
4
-
5
- import httpx
6
3
  import typer
7
4
  from rich.console import Console
8
5
 
@@ -16,38 +13,6 @@ console = Console()
16
13
  _GRANULARITIES = ("day", "week", "month")
17
14
  _ORDER_TYPES = ("all", "sampling", "production")
18
15
 
19
- # Same free FX source the backend uses (FreeCurrencyService), so converted
20
- # amounts match the platform's revenue metrics.
21
- _FX_PRIMARY = (
22
- "https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{base}.json"
23
- )
24
- _FX_FALLBACK = "https://latest.currency-api.pages.dev/v1/currencies/{base}.json"
25
-
26
-
27
- def _fx_rate(base: str, target: str) -> float:
28
- """Exchange rate base -> target, rounded UP to 2dp exactly like the backend
29
- (math.ceil(raw * 100) / 100) so totals reconcile with revenue metrics."""
30
- base, target = base.lower(), target.lower()
31
- if base == target:
32
- return 1.0
33
- last_err: Exception | None = None
34
- for url in (_FX_PRIMARY.format(base=base), _FX_FALLBACK.format(base=base)):
35
- try:
36
- with httpx.Client(timeout=10) as client:
37
- resp = client.get(url)
38
- resp.raise_for_status()
39
- data = resp.json()
40
- raw = data.get(base, {}).get(target)
41
- if raw is not None:
42
- return math.ceil(float(raw) * 100) / 100
43
- except Exception as e: # noqa: BLE001 - try fallback URL
44
- last_err = e
45
- continue
46
- raise RuntimeError(
47
- f"FX rate {base.upper()}->{target.upper()} unavailable"
48
- + (f": {last_err}" if last_err else "")
49
- )
50
-
51
16
 
52
17
  def register(app: typer.Typer) -> None:
53
18
  @command(
@@ -141,13 +106,13 @@ def register(app: typer.Typer) -> None:
141
106
  "get",
142
107
  "currency-conversion",
143
108
  legacy="convert-currency",
144
- api="GET currency-api",
145
- external="currency-api",
109
+ api="GET /admin/fx/rates",
146
110
  help=(
147
- "Convert money between currencies using the same free FX source and "
148
- "rounding as the platform's revenue metrics. Pass several source "
149
- "currencies via --from (comma-separated) to get a full rate table in "
150
- "one call, then convert locally — avoids repeated lookups."
111
+ "GET /admin/fx/rates — convert money between currencies at the "
112
+ "platform's own rates and rounding, so totals reconcile with its "
113
+ "prices and revenue metrics. Pass several source currencies via --from "
114
+ "(comma-separated) to get a full rate table in one call, then convert "
115
+ "locally — avoids repeated lookups."
151
116
  ),
152
117
  )
153
118
  def convert_currency(
@@ -165,17 +130,20 @@ def register(app: typer.Typer) -> None:
165
130
  help="Optional amount to convert (only meaningful with a single --from).",
166
131
  ),
167
132
  ) -> None:
168
- """Return source->target rates (rounded like the backend), and optionally a
169
- converted amount.
133
+ """Return source->target rates, and optionally a converted amount, as the
134
+ platform computes them.
170
135
 
171
136
  Use it to collapse a mixed-currency set into one currency: collect the
172
137
  distinct currencies, call once with --from a,b,c to get every rate, then
173
- multiply locally. Rates use math.ceil(raw * 100) / 100, matching the
174
- platform's FreeCurrencyService, so totals reconcile with revenue metrics.
175
-
176
- Output JSON: {"to", "rates": {CUR: rate, ...}, "rounding", "source"} plus
177
- {"from", "rate", "amount", "converted"} when a single --from and --amount
178
- are given.
138
+ multiply locally. The rates are the platform's (FreeCurrencyService,
139
+ rounded up to ``rate_decimals``) and come from the backend rather than
140
+ being fetched and rounded here, so they cannot drift from it.
141
+
142
+ Output JSON: {"to", "rates": {CUR: rate, ...}, "rate_decimals",
143
+ "rounding", "source", "unavailable"} plus {"from", "rate", "amount",
144
+ "converted"} when a single --from and --amount are given; "converted" is
145
+ the platform's own conversion, rounding included. A currency with no
146
+ rate fails the command rather than coming back null.
179
147
  """
180
148
  sources = [s.strip() for s in from_.split(",") if s.strip()]
181
149
  if not sources:
@@ -183,22 +151,16 @@ def register(app: typer.Typer) -> None:
183
151
  raise typer.Exit(1)
184
152
 
185
153
  def _do() -> dict:
186
- rates = {s.upper(): _fx_rate(s, to) for s in sources}
187
- out: dict = {
188
- "to": to.upper(),
189
- "rates": rates,
190
- "rounding": "ceil_2dp",
191
- "source": "fawazahmed0/currency-api",
192
- }
193
- if amount is not None and len(sources) == 1:
194
- rate = rates[sources[0].upper()]
195
- out.update(
196
- {
197
- "from": sources[0].upper(),
198
- "rate": rate,
199
- "amount": amount,
200
- "converted": round(amount * rate, 2),
201
- }
154
+ payload = api_client.get_fx_rates(
155
+ to=to,
156
+ sources=sources,
157
+ amount=amount if len(sources) == 1 else None,
158
+ )
159
+ out = (payload or {}).get("result") or {}
160
+ if out.get("unavailable"):
161
+ raise RuntimeError(
162
+ "FX rate unavailable: "
163
+ + ", ".join(f"{s}->{to.upper()}" for s in out["unavailable"])
202
164
  )
203
165
  return out
204
166