makea-cli 0.2.28__tar.gz → 0.2.29__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 (85) hide show
  1. {makea_cli-0.2.28 → makea_cli-0.2.29}/CHANGELOG.md +12 -0
  2. {makea_cli-0.2.28 → makea_cli-0.2.29}/PKG-INFO +1 -1
  3. makea_cli-0.2.29/makea_cli/__init__.py +1 -0
  4. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/file_reader.py +121 -10
  5. {makea_cli-0.2.28 → makea_cli-0.2.29}/pyproject.toml +1 -1
  6. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_file_reader.py +48 -0
  7. {makea_cli-0.2.28 → makea_cli-0.2.29}/uv.lock +1 -1
  8. makea_cli-0.2.28/makea_cli/__init__.py +0 -1
  9. {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
  10. {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
  11. {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/link-supplier-user/SKILL.md +0 -0
  12. {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
  13. {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
  14. {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
  15. {makea_cli-0.2.28 → makea_cli-0.2.29}/.gitignore +0 -0
  16. {makea_cli-0.2.28 → makea_cli-0.2.29}/README.md +0 -0
  17. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea-collection-upload/SKILL.md +0 -0
  18. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea-collection-upload/scripts/create_products.py +0 -0
  19. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/__main__.py +0 -0
  20. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/api_client.py +0 -0
  21. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/auth_pkce.py +0 -0
  22. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/broker.py +0 -0
  23. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/__init__.py +0 -0
  24. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/_util.py +0 -0
  25. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/about/__init__.py +0 -0
  26. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/about/cmd.py +0 -0
  27. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/analytics/__init__.py +0 -0
  28. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/analytics/cmd.py +0 -0
  29. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/auth/__init__.py +0 -0
  30. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/auth/cmd.py +0 -0
  31. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/brand/__init__.py +0 -0
  32. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/brand/cmd.py +0 -0
  33. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/designer/__init__.py +0 -0
  34. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/designer/cmd.py +0 -0
  35. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/directory/__init__.py +0 -0
  36. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/directory/cmd.py +0 -0
  37. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/document/__init__.py +0 -0
  38. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/document/cmd.py +0 -0
  39. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/email/__init__.py +0 -0
  40. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/email/cmd.py +0 -0
  41. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/factory/__init__.py +0 -0
  42. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/factory/cmd.py +0 -0
  43. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/files/__init__.py +0 -0
  44. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/files/cmd.py +0 -0
  45. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/flags/__init__.py +0 -0
  46. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/flags/cmd.py +0 -0
  47. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/library/__init__.py +0 -0
  48. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/library/cmd.py +0 -0
  49. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/metrics/__init__.py +0 -0
  50. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/metrics/cmd.py +0 -0
  51. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/orders/__init__.py +0 -0
  52. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/orders/cmd.py +0 -0
  53. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/playbook/__init__.py +0 -0
  54. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/playbook/cmd.py +0 -0
  55. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/product/__init__.py +0 -0
  56. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/product/cmd.py +0 -0
  57. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/production/__init__.py +0 -0
  58. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/production/cmd.py +0 -0
  59. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/production/pricing_spec.py +0 -0
  60. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/shipping/__init__.py +0 -0
  61. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/shipping/cmd.py +0 -0
  62. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/supplier/__init__.py +0 -0
  63. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/supplier/cmd.py +0 -0
  64. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/tasks/__init__.py +0 -0
  65. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/tasks/cmd.py +0 -0
  66. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/companion.py +0 -0
  67. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/config.py +0 -0
  68. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/errors.py +0 -0
  69. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/identity.py +0 -0
  70. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/main.py +0 -0
  71. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/output.py +0 -0
  72. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/posthog_client.py +0 -0
  73. {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/registry.py +0 -0
  74. {makea_cli-0.2.28 → makea_cli-0.2.29}/npm/makea-cli/README.md +0 -0
  75. {makea_cli-0.2.28 → makea_cli-0.2.29}/npm/makea-cli/makea-cli.mjs +0 -0
  76. {makea_cli-0.2.28 → makea_cli-0.2.29}/npm/makea-cli/package.json +0 -0
  77. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/golden_manifest.json +0 -0
  78. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_broker.py +0 -0
  79. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_companion.py +0 -0
  80. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_currency_conversion.py +0 -0
  81. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_output_contract.py +0 -0
  82. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_persona_commands.py +0 -0
  83. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_pm_reads.py +0 -0
  84. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_registry.py +0 -0
  85. {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_supplier_scorecard.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.29
4
+
5
+ The file reader stays inside a web worker's memory (TECH-145).
6
+
7
+ - It no longer raises Pillow's global decompression limit, and decodes no
8
+ image over 120 MP (the same bound as the backend); JPEGs decode at reduced
9
+ scale. A DXF sheet draws at most 40 pieces. Pictures shrink before they are
10
+ flattened, so a large PSD is not copied at full size several times.
11
+ - `python -m makea_cli.file_reader <name>` reads the file on stdin and prints
12
+ the reading as JSON; `read_in_child()` runs it that way, so a long-lived
13
+ worker gets its memory back when the child exits.
14
+
3
15
  ## 0.2.28
4
16
 
5
17
  Read the files brands send that no model reads (TECH-143).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: makea-cli
3
- Version: 0.2.28
3
+ Version: 0.2.29
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
@@ -0,0 +1 @@
1
+ __version__ = "0.2.29"
@@ -33,6 +33,26 @@ _MAX_MEMBERS = 40
33
33
  _MAX_PICTURES = 8
34
34
  _PICTURE_SIDE = 1800
35
35
 
36
+ # Never decode an image bigger than this. A decoded image costs w x h x 4
37
+ # bytes whatever the file size, and this runs in web workers (previews) as
38
+ # well as Celery: a 410 MB decode killed one and 502'd the site (TECH-145).
39
+ # Same bound as garmentby_service/common/image_limits.MAX_DECODE_PIXELS.
40
+ MAX_DECODE_PIXELS = 120_000_000
41
+ _MAX_PIECES_DRAWN = 40
42
+
43
+
44
+ def _open_bounded(data: bytes):
45
+ """Open an image and decode it only when it fits MAX_DECODE_PIXELS; JPEGs
46
+ are decoded at reduced scale. Raises ValueError when it is too big."""
47
+ Image = _pil()
48
+ im = Image.open(io.BytesIO(data))
49
+ if im.width * im.height > MAX_DECODE_PIXELS:
50
+ raise ValueError(f"{im.width} x {im.height} px is too large to draw here")
51
+ if im.format == "JPEG":
52
+ im.draft("RGB", (_PICTURE_SIDE, _PICTURE_SIDE))
53
+ im.load()
54
+ return im
55
+
36
56
 
37
57
  @dataclass
38
58
  class Picture:
@@ -109,20 +129,24 @@ def _pil():
109
129
  Image,
110
130
  ) # noqa: PLC0415 - optional at import time, required to draw
111
131
 
112
- Image.MAX_IMAGE_PIXELS = 400_000_000
113
132
  return Image
114
133
  except ImportError: # pragma: no cover - Pillow is a dependency
115
134
  return None
116
135
 
117
136
 
118
137
  def _png(im) -> bytes:
138
+ """Shrink first, then flatten: every conversion of the full-size image is
139
+ another full-size copy in memory."""
119
140
  Image = _pil()
141
+ if max(im.size) > _PICTURE_SIDE:
142
+ if im.mode in ("P", "PA", "1"):
143
+ im = im.convert("RGBA") # palette images cannot be resampled
144
+ im.thumbnail((_PICTURE_SIDE, _PICTURE_SIDE))
120
145
  if im.mode not in ("RGB", "L"):
121
146
  rgba = im.convert("RGBA")
122
147
  bg = Image.new("RGB", rgba.size, "white")
123
148
  bg.paste(rgba, mask=rgba.split()[-1])
124
149
  im = bg
125
- im.thumbnail((_PICTURE_SIDE, _PICTURE_SIDE))
126
150
  buf = io.BytesIO()
127
151
  im.save(buf, "PNG", optimize=True)
128
152
  return buf.getvalue()
@@ -499,8 +523,7 @@ def _read_eps(data: bytes, name: str, depth: int) -> Reading:
499
523
  Image = _pil()
500
524
  if tif_len and Image:
501
525
  try:
502
- im = Image.open(io.BytesIO(_fix_preview_tiff(data[tif_off : tif_off + tif_len])))
503
- im.load()
526
+ im = _open_bounded(_fix_preview_tiff(data[tif_off : tif_off + tif_len]))
504
527
  pictures.append(Picture("preview", _png(im)))
505
528
  except Exception: # noqa: BLE001 - no preview is still a reading
506
529
  pass
@@ -574,8 +597,11 @@ def _read_psd(data: bytes, name: str, depth: int) -> Reading:
574
597
  layers = [layer[0] for layer in (getattr(im, "layers", None) or []) if layer[0]]
575
598
  if layers:
576
599
  lines.append(f"{len(layers)} layer(s): " + "; ".join(layers[:30]) + ".")
577
- im.load()
578
- pictures.append(Picture("flattened image", _png(im)))
600
+ if width * height > MAX_DECODE_PIXELS:
601
+ lines.append("Too large to draw here.")
602
+ else:
603
+ im.load()
604
+ pictures.append(Picture("flattened image", _png(im)))
579
605
  except Exception: # noqa: BLE001
580
606
  lines.append("The flattened image could not be decoded.")
581
607
  return Reading("psd", "PSD", " ".join(lines), pictures, readable=bool(pictures))
@@ -711,8 +737,7 @@ def _read_wilcom(data: bytes, name: str, depth: int) -> Reading:
711
737
  if Image and ole.exists(stream):
712
738
  raw = ole.openstream(stream).read()
713
739
  try:
714
- im = Image.open(io.BytesIO(zlib.decompress(raw[4:])))
715
- im.load()
740
+ im = _open_bounded(zlib.decompress(raw[4:]))
716
741
  pictures.append(Picture("stitch-out preview", _png(im)))
717
742
  break
718
743
  except Exception: # noqa: BLE001
@@ -974,10 +999,12 @@ def _seam_allowance(b: _Block, cut: Tuple[float, float, float, float]) -> str:
974
999
 
975
1000
 
976
1001
  def _draw_dxf(items: List[Tuple[str, _Block]]) -> List[Picture]:
977
- """One sheet with every piece of the base size, numbered."""
1002
+ """One sheet with every piece of the base size, numbered (the first
1003
+ _MAX_PIECES_DRAWN, so the sheet stays a bounded size)."""
978
1004
  Image = _pil()
979
1005
  if not Image or not items:
980
1006
  return []
1007
+ items = items[:_MAX_PIECES_DRAWN]
981
1008
  from PIL import ImageDraw # noqa: PLC0415
982
1009
 
983
1010
  shapes = []
@@ -1171,8 +1198,17 @@ def _read_image(data: bytes, name: str, depth: int) -> Reading:
1171
1198
  ext = os.path.splitext(name.lower())[1]
1172
1199
  im = Image.open(io.BytesIO(data))
1173
1200
  frames = getattr(im, "n_frames", 1)
1174
- im.load()
1175
1201
  text = f"{im.format or _format_label(ext)} image, {im.width} x {im.height} px"
1202
+ if im.width * im.height > MAX_DECODE_PIXELS:
1203
+ return Reading(
1204
+ "image",
1205
+ im.format or _format_label(ext),
1206
+ text + ". Too large to draw here.",
1207
+ readable=False,
1208
+ )
1209
+ if im.format == "JPEG":
1210
+ im.draft("RGB", (_PICTURE_SIDE, _PICTURE_SIDE))
1211
+ im.load()
1176
1212
  dpi = im.info.get("dpi")
1177
1213
  if dpi and dpi[0]:
1178
1214
  text += f", {dpi[0]:.0f} dpi ({im.width / dpi[0] * 25.4:.0f} x {im.height / dpi[1] * 25.4:.0f} mm)"
@@ -1234,3 +1270,78 @@ _READERS: List[Tuple[Callable[[bytes, str], bool], Callable[[bytes, str, int], R
1234
1270
  (_is_svg, _read_svg),
1235
1271
  (_is_image, _read_image),
1236
1272
  ]
1273
+
1274
+
1275
+ # ── Run in a child process ───────────────────────────────────────────────
1276
+ #
1277
+ # A long-lived worker never gives decoded-image memory back to the system, so
1278
+ # the backend reads through `python -m makea_cli.file_reader <name>` (file on
1279
+ # stdin, JSON on stdout): whatever the read costs is returned when the child
1280
+ # exits (TECH-145).
1281
+
1282
+
1283
+ def to_json(reading: Reading) -> dict:
1284
+ import base64 # noqa: PLC0415
1285
+
1286
+ return {
1287
+ "kind": reading.kind,
1288
+ "format": reading.format,
1289
+ "text": reading.text,
1290
+ "readable": reading.readable,
1291
+ "pictures": [
1292
+ {"label": p.label, "png": base64.b64encode(p.png).decode("ascii")}
1293
+ for p in reading.pictures
1294
+ ],
1295
+ "members": [[name, to_json(sub)] for name, sub in reading.members],
1296
+ "raw": base64.b64encode(reading.raw).decode("ascii") if reading.raw is not None else None,
1297
+ }
1298
+
1299
+
1300
+ def from_json(data: dict) -> Reading:
1301
+ import base64 # noqa: PLC0415
1302
+
1303
+ return Reading(
1304
+ kind=data["kind"],
1305
+ format=data["format"],
1306
+ text=data["text"],
1307
+ readable=data["readable"],
1308
+ pictures=[Picture(p["label"], base64.b64decode(p["png"])) for p in data["pictures"]],
1309
+ members=[(name, from_json(sub)) for name, sub in data["members"]],
1310
+ raw=base64.b64decode(data["raw"]) if data.get("raw") is not None else None,
1311
+ )
1312
+
1313
+
1314
+ def read_in_child(data: bytes, name: str, *, timeout: float = 90.0) -> Reading:
1315
+ """read_bytes in a child Python process; a failure or a timeout is a
1316
+ reading that says so, never an exception."""
1317
+ import json # noqa: PLC0415
1318
+ import subprocess # noqa: PLC0415
1319
+ import sys # noqa: PLC0415
1320
+
1321
+ try:
1322
+ done = subprocess.run(
1323
+ [sys.executable, "-m", "makea_cli.file_reader", name],
1324
+ input=data,
1325
+ capture_output=True,
1326
+ timeout=timeout,
1327
+ check=True,
1328
+ )
1329
+ return from_json(json.loads(done.stdout))
1330
+ except Exception as e: # noqa: BLE001 - the file stays uploaded either way
1331
+ detail = (
1332
+ "it took too long" if isinstance(e, subprocess.TimeoutExpired) else type(e).__name__
1333
+ )
1334
+ ext = os.path.splitext(name.lower())[1]
1335
+ return Reading(
1336
+ "unknown",
1337
+ _format_label(ext),
1338
+ f"{_format_label(ext)} file; it could not be read here ({detail}).",
1339
+ readable=False,
1340
+ )
1341
+
1342
+
1343
+ if __name__ == "__main__":
1344
+ import json
1345
+ import sys
1346
+
1347
+ sys.stdout.write(json.dumps(to_json(read_bytes(sys.stdin.buffer.read(), sys.argv[1]))))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "makea-cli"
7
- version = "0.2.28"
7
+ version = "0.2.29"
8
8
  description = "Makea admin CLI (browser login + Makea admin HTTP API)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -223,3 +223,51 @@ class IllustratorNativeTest(unittest.TestCase):
223
223
 
224
224
  def test_ai_with_real_pdf_content_stays_a_pdf(self):
225
225
  self.assertEqual(read_bytes(_blank_pdf(), "logo.ai").kind, "pdf")
226
+
227
+
228
+ class DecodeBoundTest(unittest.TestCase):
229
+ """Nothing here decodes an image too big for a web worker (TECH-145)."""
230
+
231
+ def test_pillow_global_limit_is_left_alone(self):
232
+ before = Image.MAX_IMAGE_PIXELS
233
+ read_bytes(b"x", "a.dxf")
234
+ self.assertEqual(Image.MAX_IMAGE_PIXELS, before)
235
+
236
+ def test_an_image_over_the_bound_is_described_not_drawn(self):
237
+ from unittest import mock
238
+
239
+ from makea_cli import file_reader
240
+
241
+ buf = io.BytesIO()
242
+ Image.new("RGB", (400, 300), "white").save(buf, "TIFF")
243
+ with mock.patch.object(file_reader, "MAX_DECODE_PIXELS", 1000):
244
+ r = read_bytes(buf.getvalue(), "scan.tif")
245
+ self.assertEqual(r.pictures, [])
246
+ self.assertIn("400 x 300 px", r.text)
247
+ self.assertIn("Too large", r.text)
248
+
249
+
250
+ class ChildProcessTest(unittest.TestCase):
251
+ def test_a_reading_comes_back_whole_from_the_child(self):
252
+ from makea_cli.file_reader import read_in_child
253
+
254
+ buf = io.BytesIO()
255
+ with zipfile.ZipFile(buf, "w") as z:
256
+ z.writestr("pattern.dxf", _dxf())
257
+ z.writestr("spec.docx", b"PK\x03\x04 docx")
258
+ r = read_in_child(buf.getvalue(), "pack.zip")
259
+ self.assertEqual(r.kind, "archive")
260
+ kinds = {n: m.kind for n, m in r.members}
261
+ self.assertEqual(kinds, {"pattern.dxf": "pattern", "spec.docx": "document"})
262
+ self.assertEqual(dict(r.members)["spec.docx"].raw, b"PK\x03\x04 docx")
263
+ self.assertTrue(dict(r.members)["pattern.dxf"].pictures[0].png.startswith(b"\x89PNG"))
264
+
265
+ def test_a_child_that_fails_is_a_reading(self):
266
+ from unittest import mock
267
+
268
+ from makea_cli import file_reader
269
+
270
+ with mock.patch("subprocess.run", side_effect=OSError("no python")):
271
+ r = file_reader.read_in_child(b"x", "a.dxf")
272
+ self.assertFalse(r.readable)
273
+ self.assertIn("could not be read", r.text)
@@ -119,7 +119,7 @@ wheels = [
119
119
 
120
120
  [[package]]
121
121
  name = "makea-cli"
122
- version = "0.2.28"
122
+ version = "0.2.29"
123
123
  source = { editable = "." }
124
124
  dependencies = [
125
125
  { name = "click" },
@@ -1 +0,0 @@
1
- __version__ = "0.2.28"
File without changes
File without changes
File without changes