makea-cli 0.1.16__tar.gz → 0.1.17__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 (48) hide show
  1. {makea_cli-0.1.16 → makea_cli-0.1.17}/PKG-INFO +1 -1
  2. makea_cli-0.1.17/makea_cli/__init__.py +1 -0
  3. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/api_client.py +74 -0
  4. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/__init__.py +2 -0
  5. makea_cli-0.1.17/makea_cli/commands/library/cmd.py +281 -0
  6. makea_cli-0.1.17/makea_cli/commands/metrics/__init__.py +0 -0
  7. {makea_cli-0.1.16 → makea_cli-0.1.17}/pyproject.toml +1 -1
  8. makea_cli-0.1.16/makea_cli/__init__.py +0 -1
  9. {makea_cli-0.1.16 → makea_cli-0.1.17}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
  10. {makea_cli-0.1.16 → makea_cli-0.1.17}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
  11. {makea_cli-0.1.16 → makea_cli-0.1.17}/.claude/skills/link-supplier-user/SKILL.md +0 -0
  12. {makea_cli-0.1.16 → makea_cli-0.1.17}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
  13. {makea_cli-0.1.16 → makea_cli-0.1.17}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
  14. {makea_cli-0.1.16 → makea_cli-0.1.17}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
  15. {makea_cli-0.1.16 → makea_cli-0.1.17}/.gitignore +0 -0
  16. {makea_cli-0.1.16 → makea_cli-0.1.17}/README.md +0 -0
  17. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea-collection-upload/SKILL.md +0 -0
  18. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea-collection-upload/scripts/create_products.py +0 -0
  19. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/__main__.py +0 -0
  20. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/auth_pkce.py +0 -0
  21. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/_util.py +0 -0
  22. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/analytics/__init__.py +0 -0
  23. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/analytics/cmd.py +0 -0
  24. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/auth/__init__.py +0 -0
  25. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/auth/cmd.py +0 -0
  26. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/designer/__init__.py +0 -0
  27. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/designer/cmd.py +0 -0
  28. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/directory/__init__.py +0 -0
  29. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/directory/cmd.py +0 -0
  30. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/document/__init__.py +0 -0
  31. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/document/cmd.py +0 -0
  32. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/email/__init__.py +0 -0
  33. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/email/cmd.py +0 -0
  34. {makea_cli-0.1.16/makea_cli/commands/metrics → makea_cli-0.1.17/makea_cli/commands/library}/__init__.py +0 -0
  35. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/metrics/cmd.py +0 -0
  36. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/orders/__init__.py +0 -0
  37. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/orders/cmd.py +0 -0
  38. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/product/__init__.py +0 -0
  39. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/product/cmd.py +0 -0
  40. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/supplier/__init__.py +0 -0
  41. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/commands/supplier/cmd.py +0 -0
  42. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/config.py +0 -0
  43. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/main.py +0 -0
  44. {makea_cli-0.1.16 → makea_cli-0.1.17}/makea_cli/posthog_client.py +0 -0
  45. {makea_cli-0.1.16 → makea_cli-0.1.17}/npm/makea-cli/README.md +0 -0
  46. {makea_cli-0.1.16 → makea_cli-0.1.17}/npm/makea-cli/makea-cli.mjs +0 -0
  47. {makea_cli-0.1.16 → makea_cli-0.1.17}/npm/makea-cli/package.json +0 -0
  48. {makea_cli-0.1.16 → makea_cli-0.1.17}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makea-cli
3
- Version: 0.1.16
3
+ Version: 0.1.17
4
4
  Summary: Makea admin CLI (browser login + Makea admin HTTP API)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: httpx>=0.27
@@ -0,0 +1 @@
1
+ __version__ = "0.1.17"
@@ -79,6 +79,35 @@ def put_json(path: str, body: Any, *, timeout: float = 60.0) -> Any:
79
79
  return r.json()
80
80
 
81
81
 
82
+ def patch_json(path: str, body: Any, *, timeout: float = 60.0) -> Any:
83
+ """PATCH JSON body (same error handling as ``post_json``)."""
84
+ url = f"{api_base_url()}{path}"
85
+ headers = {**_headers(), "Content-Type": "application/json"}
86
+ with httpx.Client(timeout=timeout) as client:
87
+ r = client.patch(url, headers=headers, json=body)
88
+ if r.status_code >= 400:
89
+ try:
90
+ detail = r.json()
91
+ except json.JSONDecodeError:
92
+ detail = r.text
93
+ raise RuntimeError(f"HTTP {r.status_code}: {detail}")
94
+ return r.json()
95
+
96
+
97
+ def delete_json(path: str, *, params: dict[str, Any] | None = None, timeout: float = 60.0) -> Any:
98
+ """DELETE request (same error handling as ``get_json``)."""
99
+ url = f"{api_base_url()}{path}"
100
+ with httpx.Client(timeout=timeout) as client:
101
+ r = client.delete(url, headers=_headers(), params=params)
102
+ if r.status_code >= 400:
103
+ try:
104
+ detail = r.json()
105
+ except json.JSONDecodeError:
106
+ detail = r.text
107
+ raise RuntimeError(f"HTTP {r.status_code}: {detail}")
108
+ return r.json()
109
+
110
+
82
111
  def send_admin_raw_email(body: dict[str, Any]) -> Any:
83
112
  """
84
113
  POST /admin/email/send — queue HTML email (from must be @makea.co).
@@ -816,3 +845,48 @@ def update_product_technical_specifications(
816
845
  {"technical_specifications": json.dumps(technical_specifications)},
817
846
  timeout=timeout,
818
847
  )
848
+
849
+
850
+ # ── Tech-pack library (reusable library components), admin-side ──────────
851
+ def admin_library_component_schema(*, timeout: float = 60.0) -> Any:
852
+ """GET /admin/library_components/schema — the library taxonomy (sections +
853
+ library-role field descriptors). Static; no target user."""
854
+ return get_json("/admin/library_components/schema", timeout=timeout)
855
+
856
+
857
+ def admin_list_library_components(
858
+ user_id: str, *, section: str | None = None, timeout: float = 60.0
859
+ ) -> Any:
860
+ """GET /admin/designer/{user_id}/library_components — list a designer's
861
+ tech-pack library (org-fanned). Optional ``section`` scopes to one type."""
862
+ params = {"section": section} if section else None
863
+ return get_json(f"/admin/designer/{user_id}/library_components", params=params, timeout=timeout)
864
+
865
+
866
+ def admin_create_library_component(
867
+ user_id: str, body: dict[str, Any], *, timeout: float = 60.0
868
+ ) -> Any:
869
+ """POST /admin/designer/{user_id}/library_components — {section, name?,
870
+ values, documents?}."""
871
+ return post_json(f"/admin/designer/{user_id}/library_components", body, timeout=timeout)
872
+
873
+
874
+ def admin_update_library_component(
875
+ user_id: str, component_id: str, body: dict[str, Any], *, timeout: float = 60.0
876
+ ) -> Any:
877
+ """PATCH /admin/designer/{user_id}/library_components/{component_id} —
878
+ {name?, values?, documents?}."""
879
+ return patch_json(
880
+ f"/admin/designer/{user_id}/library_components/{component_id}",
881
+ body,
882
+ timeout=timeout,
883
+ )
884
+
885
+
886
+ def admin_delete_library_component(
887
+ user_id: str, component_id: str, *, timeout: float = 60.0
888
+ ) -> Any:
889
+ """DELETE /admin/designer/{user_id}/library_components/{component_id}."""
890
+ return delete_json(
891
+ f"/admin/designer/{user_id}/library_components/{component_id}", timeout=timeout
892
+ )
@@ -8,6 +8,7 @@ from makea_cli.commands.designer.cmd import register as register_designer
8
8
  from makea_cli.commands.directory.cmd import register as register_directory
9
9
  from makea_cli.commands.document.cmd import register as register_document
10
10
  from makea_cli.commands.email.cmd import register as register_email
11
+ from makea_cli.commands.library.cmd import register as register_library
11
12
  from makea_cli.commands.metrics.cmd import register as register_metrics
12
13
  from makea_cli.commands.orders.cmd import register as register_orders
13
14
  from makea_cli.commands.product.cmd import register as register_product
@@ -25,3 +26,4 @@ def register_all(app: typer.Typer) -> None:
25
26
  register_metrics(app)
26
27
  register_analytics(app)
27
28
  register_email(app)
29
+ register_library(app)
@@ -0,0 +1,281 @@
1
+ """Admin commands for a designer's tech-pack library (reusable library components).
2
+
3
+ A *library component* is a designer's saved, reusable spec for one tech-pack
4
+ section — the ``role='library'`` field values of a fabric / colour / trim …
5
+ (name, composition, construction, certifications, pricing). Designers fill them
6
+ into tech packs ("use from library") and capture them back ("save to library");
7
+ the library is shared across their org the same way products are.
8
+
9
+ These commands are the admin side: they take the target designer's ``user_id``
10
+ explicitly (Cognito sub) and curate that designer/org's library on their behalf.
11
+ Require an admin Makea login (makea-cli auth).
12
+
13
+ Workflow: run ``library-schema`` first to learn the valid ``section`` ids and the
14
+ ``values`` keys (and which are required) before ``library-create`` / ``library-update``.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ import typer
24
+ from rich.console import Console
25
+
26
+ from makea_cli import api_client
27
+ from makea_cli.commands._util import print_json_or_exit
28
+
29
+ console = Console()
30
+
31
+
32
+ _LIBRARY_EPILOG = """
33
+ The tech-pack library holds reusable per-section specs (fabrics, colours, trims …)
34
+ keyed to a designer and shared across their org.
35
+
36
+ Taxonomy first
37
+ Run ``makea-cli library-schema`` to list the sections that support a library
38
+ (e.g. MAIN_FABRIC) and, per section, the ``values`` keys, their labels/types,
39
+ and which are required ("essential") before a component can be saved. The
40
+ server ignores any ``values`` key that is not a library-role field for the
41
+ section, so use exactly the keys the schema reports.
42
+
43
+ Creating / updating
44
+ ``values`` is an object of ``{"<field key>": <value>}`` — e.g.
45
+ ``{"main_fabric.library.name": "Pique Knit", "main_fabric.library.composition": "100% Cotton"}``.
46
+ On update, a provided value replaces; an empty string / null clears that field.
47
+ A component's display name defaults to its ``*.library.name`` value when ``name``
48
+ is omitted.
49
+
50
+ Org scope
51
+ Reads (``library-list``) fan across the target designer's whole org and tag each
52
+ component with ``owner_user_id``. Creates land under the USER_ID you pass;
53
+ update/delete work on any component in that user's org.
54
+ """.strip()
55
+
56
+
57
+ def _load_values(values: str | None, values_file: Path | None) -> dict[str, Any]:
58
+ """Resolve the ``values`` map from an inline JSON string or a JSON file."""
59
+ if values is not None and values_file is not None:
60
+ raise ValueError("Pass only one of --values / --values-file.")
61
+ raw: str | None = None
62
+ if values_file is not None:
63
+ raw = values_file.read_text(encoding="utf-8")
64
+ elif values is not None:
65
+ raw = values
66
+ if raw is None:
67
+ return {}
68
+ parsed = json.loads(raw)
69
+ if not isinstance(parsed, dict):
70
+ raise ValueError("values must be a JSON object of {field key: value}.")
71
+ return parsed
72
+
73
+
74
+ def register(app: typer.Typer) -> None:
75
+ @app.command(
76
+ "library-schema",
77
+ help=(
78
+ "GET /admin/library_components/schema — the tech-pack library taxonomy: "
79
+ "sections and their library-role field keys (run this before create/update)."
80
+ ),
81
+ epilog=_LIBRARY_EPILOG,
82
+ )
83
+ def library_schema(
84
+ timeout: float = typer.Option(
85
+ 60.0,
86
+ "--timeout",
87
+ "-t",
88
+ help="HTTP timeout in seconds.",
89
+ min=10.0,
90
+ max=300.0,
91
+ ),
92
+ ) -> None:
93
+ """Print the library taxonomy (sections + fields). Admin token required."""
94
+ print_json_or_exit(
95
+ console,
96
+ lambda: api_client.admin_library_component_schema(timeout=timeout),
97
+ )
98
+
99
+ @app.command(
100
+ "library-list",
101
+ help=(
102
+ "GET /admin/designer/{user_id}/library_components — list a designer's "
103
+ "tech-pack library (org-fanned). Optional --section scopes to one type."
104
+ ),
105
+ epilog=_LIBRARY_EPILOG,
106
+ )
107
+ def library_list(
108
+ user_id: str = typer.Argument(..., help="Target designer Cognito user_id (sub)."),
109
+ section: str | None = typer.Option(
110
+ None,
111
+ "--section",
112
+ "-s",
113
+ help="Scope to one section id (e.g. MAIN_FABRIC); see library-schema.",
114
+ ),
115
+ timeout: float = typer.Option(
116
+ 60.0,
117
+ "--timeout",
118
+ "-t",
119
+ help="HTTP timeout in seconds.",
120
+ min=10.0,
121
+ max=300.0,
122
+ ),
123
+ ) -> None:
124
+ """List the target designer's library components across their org."""
125
+ print_json_or_exit(
126
+ console,
127
+ lambda: api_client.admin_list_library_components(
128
+ user_id, section=section, timeout=timeout
129
+ ),
130
+ )
131
+
132
+ @app.command(
133
+ "library-create",
134
+ help=(
135
+ "POST /admin/designer/{user_id}/library_components — save a reusable "
136
+ "component under a designer. Provide --section and --values (see library-schema)."
137
+ ),
138
+ epilog=_LIBRARY_EPILOG,
139
+ )
140
+ def library_create(
141
+ user_id: str = typer.Argument(..., help="Target designer Cognito user_id (sub)."),
142
+ section: str = typer.Option(
143
+ ...,
144
+ "--section",
145
+ "-s",
146
+ help="Section id the component belongs to (e.g. MAIN_FABRIC).",
147
+ ),
148
+ name: str | None = typer.Option(
149
+ None,
150
+ "--name",
151
+ "-n",
152
+ help="Optional display name (defaults to the *.library.name value).",
153
+ ),
154
+ values: str | None = typer.Option(
155
+ None,
156
+ "--values",
157
+ help='Inline JSON object of {field key: value}, e.g. \'{"main_fabric.library.name":"Pique Knit"}\'.',
158
+ ),
159
+ values_file: Path | None = typer.Option(
160
+ None,
161
+ "--values-file",
162
+ "-f",
163
+ exists=True,
164
+ dir_okay=False,
165
+ readable=True,
166
+ help="Path to a UTF-8 JSON file holding the values object (instead of --values).",
167
+ ),
168
+ timeout: float = typer.Option(
169
+ 60.0,
170
+ "--timeout",
171
+ "-t",
172
+ help="HTTP timeout in seconds.",
173
+ min=10.0,
174
+ max=300.0,
175
+ ),
176
+ ) -> None:
177
+ """Create a library component under the target designer."""
178
+ try:
179
+ body: dict[str, Any] = {
180
+ "section": section,
181
+ "values": _load_values(values, values_file),
182
+ }
183
+ if name is not None:
184
+ body["name"] = name
185
+ except (OSError, json.JSONDecodeError, ValueError) as e:
186
+ console.print(f"[red]Invalid input: {e}[/red]")
187
+ raise typer.Exit(1) from e
188
+
189
+ print_json_or_exit(
190
+ console,
191
+ lambda: api_client.admin_create_library_component(user_id, body, timeout=timeout),
192
+ )
193
+
194
+ @app.command(
195
+ "library-update",
196
+ help=(
197
+ "PATCH /admin/designer/{user_id}/library_components/{component_id} — update "
198
+ "a component's name and/or values (empty value clears a field)."
199
+ ),
200
+ epilog=_LIBRARY_EPILOG,
201
+ )
202
+ def library_update(
203
+ user_id: str = typer.Argument(..., help="Target designer Cognito user_id (sub)."),
204
+ component_id: str = typer.Argument(..., help="Library component UUID."),
205
+ name: str | None = typer.Option(None, "--name", "-n", help="New display name."),
206
+ values: str | None = typer.Option(
207
+ None,
208
+ "--values",
209
+ help='Inline JSON object of {field key: value} to merge; "" or null clears a field.',
210
+ ),
211
+ values_file: Path | None = typer.Option(
212
+ None,
213
+ "--values-file",
214
+ "-f",
215
+ exists=True,
216
+ dir_okay=False,
217
+ readable=True,
218
+ help="Path to a UTF-8 JSON file holding the values object (instead of --values).",
219
+ ),
220
+ timeout: float = typer.Option(
221
+ 60.0,
222
+ "--timeout",
223
+ "-t",
224
+ help="HTTP timeout in seconds.",
225
+ min=10.0,
226
+ max=300.0,
227
+ ),
228
+ ) -> None:
229
+ """Update a library component's name and/or values."""
230
+ try:
231
+ body: dict[str, Any] = {}
232
+ if name is not None:
233
+ body["name"] = name
234
+ if values is not None or values_file is not None:
235
+ body["values"] = _load_values(values, values_file)
236
+ if not body:
237
+ raise ValueError("Nothing to update: pass --name and/or --values.")
238
+ except (OSError, json.JSONDecodeError, ValueError) as e:
239
+ console.print(f"[red]Invalid input: {e}[/red]")
240
+ raise typer.Exit(1) from e
241
+
242
+ print_json_or_exit(
243
+ console,
244
+ lambda: api_client.admin_update_library_component(
245
+ user_id, component_id, body, timeout=timeout
246
+ ),
247
+ )
248
+
249
+ @app.command(
250
+ "library-delete",
251
+ help=(
252
+ "DELETE /admin/designer/{user_id}/library_components/{component_id} — remove "
253
+ "a component from the designer's org library."
254
+ ),
255
+ epilog=_LIBRARY_EPILOG,
256
+ )
257
+ def library_delete(
258
+ user_id: str = typer.Argument(..., help="Target designer Cognito user_id (sub)."),
259
+ component_id: str = typer.Argument(..., help="Library component UUID."),
260
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt."),
261
+ timeout: float = typer.Option(
262
+ 60.0,
263
+ "--timeout",
264
+ "-t",
265
+ help="HTTP timeout in seconds.",
266
+ min=10.0,
267
+ max=300.0,
268
+ ),
269
+ ) -> None:
270
+ """Delete a library component from the target designer's org library."""
271
+ if not yes:
272
+ typer.confirm(
273
+ f"Delete library component {component_id} from {user_id}'s org library?",
274
+ abort=True,
275
+ )
276
+ print_json_or_exit(
277
+ console,
278
+ lambda: api_client.admin_delete_library_component(
279
+ user_id, component_id, timeout=timeout
280
+ ),
281
+ )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "makea-cli"
7
- version = "0.1.16"
7
+ version = "0.1.17"
8
8
  description = "Makea admin CLI (browser login + Makea admin HTTP API)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1 +0,0 @@
1
- __version__ = "0.1.16"
File without changes
File without changes
File without changes
File without changes