makea-cli 0.2.26__py3-none-any.whl → 0.2.28__py3-none-any.whl

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.
makea_cli/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.26"
1
+ __version__ = "0.2.28"
makea_cli/api_client.py CHANGED
@@ -1195,8 +1195,12 @@ def admin_list_tasks(
1195
1195
  type_: str | None = None,
1196
1196
  user: str | None = None,
1197
1197
  product: str | None = None,
1198
+ wrong: bool = False,
1198
1199
  ) -> Any:
1199
- """GET /admin/tasks — the team's open tasks; filters are comma-separated."""
1200
+ """GET /admin/tasks — the team's open tasks; filters are comma-separated.
1201
+ ``wrong``: the tasks the team marked wrong instead, newest first."""
1202
+ if wrong:
1203
+ return get_json("/admin/tasks", params={"wrong": "1"})
1200
1204
  params = {
1201
1205
  k: v
1202
1206
  for k, v in (("kind", kind), ("type", type_), ("user", user), ("product", product))
@@ -1211,7 +1215,8 @@ def admin_get_task(task_id: str) -> Any:
1211
1215
 
1212
1216
 
1213
1217
  def admin_close_task(task_id: str, verb: str, body: dict[str, Any]) -> Any:
1214
- """POST /admin/tasks/{task_id}/{done|dismiss} — {note, reason?, caused_by?, recurs?}."""
1218
+ """POST /admin/tasks/{task_id}/{done|dismiss|note} — {note, reason?, caused_by?,
1219
+ recurs?, wrong?}. ``note`` adds a note and leaves the task open."""
1215
1220
  return post_json(f"/admin/tasks/{quote(task_id, safe='')}/{verb}", body)
1216
1221
 
1217
1222
 
@@ -11,6 +11,7 @@ from makea_cli.commands.directory.cmd import register as register_directory
11
11
  from makea_cli.commands.document.cmd import register as register_document
12
12
  from makea_cli.commands.email.cmd import register as register_email
13
13
  from makea_cli.commands.factory.cmd import register as register_factory
14
+ from makea_cli.commands.files.cmd import register as register_files
14
15
  from makea_cli.commands.flags.cmd import register as register_flags
15
16
  from makea_cli.commands.library.cmd import register as register_library
16
17
  from makea_cli.commands.metrics.cmd import register as register_metrics
@@ -38,6 +39,7 @@ def register_all(app: typer.Typer) -> None:
38
39
  register_email(app)
39
40
  register_library(app)
40
41
  register_factory(app)
42
+ register_files(app)
41
43
  register_brand(app)
42
44
  register_playbook(app)
43
45
  register_about(app)
File without changes
@@ -0,0 +1,84 @@
1
+ """``makea-cli <persona> get file PATH``: read a file a brand sent, locally.
2
+
3
+ Pattern DXF/RUL, Illustrator, EPS, Photoshop, embroidery (PES/DST/EMB), ZIP,
4
+ AVIF/TIFF/SVG. No API call: the file never leaves the machine. Pictures of what
5
+ the file draws are saved next to it so the caller can look at them (an agent
6
+ reads them with its image tool).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from pathlib import Path
13
+
14
+ import typer
15
+ from rich.console import Console
16
+
17
+ from makea_cli.commands._util import print_json_or_exit
18
+ from makea_cli.file_reader import read_path
19
+ from makea_cli.registry import command
20
+
21
+ console = Console()
22
+
23
+ _HELP = (
24
+ "Read a pattern (DXF/RUL), artwork (AI/EPS/PSD/SVG), embroidery (PES/DST/EMB), ZIP or "
25
+ "AVIF/TIFF file on this machine: what it states plus PNG pictures of what it draws."
26
+ )
27
+
28
+
29
+ def _slug(text: str) -> str:
30
+ return re.sub(r"[^A-Za-z0-9]+", "-", text).strip("-").lower()[:40] or "picture"
31
+
32
+
33
+ def read_file_payload(path: Path, pictures_dir: Path | None) -> dict:
34
+ if not path.is_file():
35
+ raise FileNotFoundError(f"{path} is not a file")
36
+ reading = read_path(str(path))
37
+ out_dir = pictures_dir or path.parent
38
+ saved = []
39
+ pictures = reading.all_pictures()
40
+ if pictures:
41
+ out_dir.mkdir(parents=True, exist_ok=True)
42
+ for i, pic in enumerate(pictures, 1):
43
+ target = out_dir / f"{path.stem}.{i}-{_slug(pic.label)}.png"
44
+ target.write_bytes(pic.png)
45
+ saved.append({"label": pic.label, "path": str(target)})
46
+ payload = {
47
+ "file": str(path),
48
+ "kind": reading.kind,
49
+ "format": reading.format,
50
+ "readable": reading.readable,
51
+ "readout": reading.markdown(),
52
+ "pictures": saved,
53
+ }
54
+ if saved:
55
+ payload["next"] = (
56
+ "Look at each picture to read what it shows; the readout lists only stated facts."
57
+ )
58
+ return payload
59
+
60
+
61
+ def register(app: typer.Typer) -> None:
62
+ for persona in ("designer", "supplier", "admin"):
63
+ _register_for(app, persona)
64
+
65
+
66
+ def _register_for(app: typer.Typer, persona: str) -> None:
67
+ @command(
68
+ app,
69
+ persona,
70
+ "get",
71
+ "file",
72
+ api="LOCAL file_reader",
73
+ external="local",
74
+ help=_HELP,
75
+ )
76
+ def get_file(
77
+ path: Path = typer.Argument(..., help="The file to read."),
78
+ pictures_dir: Path | None = typer.Option(
79
+ None,
80
+ "--pictures-dir",
81
+ help="Where to save the PNG pictures (default: next to the file).",
82
+ ),
83
+ ) -> None:
84
+ print_json_or_exit(console, lambda: read_file_payload(path, pictures_dir))
@@ -12,7 +12,10 @@ read as one order's problem), ``note`` (a slip of Makea's or the platform's),
12
12
 
13
13
  The change a task asks for is made with the command that owns it (``admin
14
14
  create rule``, ``admin update user``, ``admin update about`` …); ``update
15
- task --done`` then records that it was handled. Only notes the team handles
15
+ task --done`` then records that it was handled. ``update task --note`` alone
16
+ adds a note and leaves the task open; ``--dismiss --wrong`` says the task
17
+ should never have been raised (Beeboo's next reading is told, and ``list
18
+ task --wrong`` lists every such task). Only notes the team handles
16
19
  off the platform can be marked done; the others close by themselves when the
17
20
  fact behind them lands, and ``get task`` says which fact.
18
21
  """
@@ -44,6 +47,10 @@ Examples
44
47
  makea-cli admin update task <task_id> --done --note "Asked the brand on WhatsApp" \\
45
48
  --reason "The brand changed the cuff after approval" --caused-by brand --once
46
49
  makea-cli admin update task <task_id> --dismiss --note "Brand keeps GBP"
50
+ makea-cli admin update task <task_id> --note "Asked the brand, waiting"
51
+ makea-cli admin update task <task_id> --dismiss --wrong \
52
+ --note "The factory was confirming the file, not reporting a mistake"
53
+ makea-cli admin list task --wrong
47
54
  """.strip()
48
55
 
49
56
 
@@ -56,8 +63,9 @@ def register(app: typer.Typer) -> None:
56
63
  api="GET /admin/tasks",
57
64
  help=(
58
65
  "GET /admin/tasks — the Makea team's open tasks: task_id, type, kind, title, "
59
- "owner (the account), product, due date, can_mark_done; counts by kind. "
60
- "--user adds that account's own cards the team may settle (account.*)."
66
+ "owner (the account), product, due date, can_mark_done, notes; counts by kind. "
67
+ "--user adds that account's own cards the team may settle (account.*). "
68
+ "--wrong lists the tasks the team marked wrong instead, newest first."
61
69
  ),
62
70
  epilog=_TASKS_EPILOG,
63
71
  )
@@ -72,11 +80,18 @@ def register(app: typer.Typer) -> None:
72
80
  None, "--user", "-u", help="One account: user_id or email."
73
81
  ),
74
82
  product: str | None = typer.Option(None, "--product", "-p", help="One product id."),
83
+ wrong: bool = typer.Option(
84
+ False, "--wrong", help="The tasks marked wrong (closed), with why, newest first."
85
+ ),
75
86
  ) -> None:
76
- """List the Makea team's open tasks."""
87
+ """List the Makea team's open tasks, or the ones marked wrong."""
88
+ if wrong and (kind or type_ or user or product):
89
+ raise typer.BadParameter("--wrong takes no filters")
77
90
  print_json_or_exit(
78
91
  console,
79
- lambda: api_client.admin_list_tasks(kind=kind, type_=type_, user=user, product=product),
92
+ lambda: api_client.admin_list_tasks(
93
+ kind=kind, type_=type_, user=user, product=product, wrong=wrong
94
+ ),
80
95
  )
81
96
 
82
97
  @command(
@@ -101,19 +116,23 @@ def register(app: typer.Typer) -> None:
101
116
  "admin",
102
117
  "update",
103
118
  "task",
104
- api="POST /admin/tasks/{task_id}/{done|dismiss}",
119
+ api="POST /admin/tasks/{task_id}/{done|dismiss|note}",
105
120
  side_effects=[
106
- "closes the task for the whole Makea team",
121
+ "with --done or --dismiss, closes the task for the whole Makea team",
122
+ "with --note alone, adds a note the team sees; the task stays open",
107
123
  "with --reason, Beeboo's next reading of the chats uses that reason",
124
+ "with --wrong, Beeboo's next reading is told the finding was wrong",
108
125
  ],
109
126
  approval="ask",
110
127
  reversible=False,
111
128
  idempotent=False,
112
129
  help=(
113
- "POST /admin/tasks/{task_id}/{done|dismiss} — close a task: --done once it is "
114
- "handled (notes the team handles off the platform only), --dismiss when it "
115
- "will not be done. --note required. On a held lesson, --reason records why "
116
- "it happened for Beeboo's next reading."
130
+ "POST /admin/tasks/{task_id}/{done|dismiss|note} — close a task: --done once "
131
+ "it is handled (notes the team handles off the platform only), --dismiss when "
132
+ "it will not be done, --dismiss --wrong when it should never have been raised "
133
+ "(the note says why). --note required; --note alone adds a note and leaves the "
134
+ "task open. On a held lesson, --reason records why it happened for Beeboo's "
135
+ "next reading."
117
136
  ),
118
137
  epilog=_TASKS_EPILOG,
119
138
  )
@@ -121,7 +140,17 @@ def register(app: typer.Typer) -> None:
121
140
  task_id: str = typer.Argument(..., help="Task id (from list task)."),
122
141
  done: bool = typer.Option(False, "--done", help="It was handled."),
123
142
  dismiss: bool = typer.Option(False, "--dismiss", help="It will not be done."),
124
- note: str = typer.Option(..., "--note", "-n", help="What was done, or why not (required)."),
143
+ wrong: bool = typer.Option(
144
+ False,
145
+ "--wrong",
146
+ help="With --dismiss: it should never have been raised; --note says why.",
147
+ ),
148
+ note: str = typer.Option(
149
+ ...,
150
+ "--note",
151
+ "-n",
152
+ help="What was done, or why not (required). Alone: a note; the task stays open.",
153
+ ),
125
154
  reason: str | None = typer.Option(
126
155
  None,
127
156
  "--reason",
@@ -136,13 +165,22 @@ def register(app: typer.Typer) -> None:
136
165
  help="With --reason: it will happen again, or it was this order only.",
137
166
  ),
138
167
  ) -> None:
139
- """Mark a task done, or dismiss it."""
140
- if done == dismiss:
141
- raise typer.BadParameter("pass exactly one of --done, --dismiss")
168
+ """Mark a task done, dismiss it, or add a note."""
169
+ if done and dismiss:
170
+ raise typer.BadParameter("pass at most one of --done, --dismiss")
171
+ if wrong and not dismiss:
172
+ raise typer.BadParameter("--wrong goes with --dismiss")
142
173
  clean_note = " ".join(note.split())
143
174
  if not clean_note:
144
175
  raise typer.BadParameter("--note must not be empty")
145
176
  body: dict[str, Any] = {"note": clean_note}
177
+ if not (done or dismiss):
178
+ if reason is not None or caused_by is not None or recurs is not None:
179
+ raise typer.BadParameter("--reason, --caused-by, --recurs/--once go with --done")
180
+ print_json_or_exit(console, lambda: api_client.admin_close_task(task_id, "note", body))
181
+ return
182
+ if wrong:
183
+ body["wrong"] = True
146
184
  if reason is not None:
147
185
  if not done:
148
186
  raise typer.BadParameter("--reason goes with --done")