@lahat-group/cli 1.1.6 → 1.1.8

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.
package/dist/index.js CHANGED
@@ -9447,7 +9447,8 @@ var AGENT_SCOPES = {
9447
9447
  "orders:write": { he: "\u05DC\u05D4\u05DB\u05D9\u05DF \u05D8\u05D9\u05D5\u05D8\u05EA \u05D4\u05D6\u05DE\u05E0\u05D4 \u05DC\u05D0\u05D9\u05E9\u05D5\u05E8\u05DA", en: "Draft an order for you to confirm" },
9448
9448
  "orders:commit": { he: "\u05DC\u05D0\u05E9\u05E8 \u05D4\u05D6\u05DE\u05E0\u05D4 \u05D1\u05E9\u05DE\u05DA", en: "Confirm an order on your behalf" },
9449
9449
  "invitations:write": { he: "\u05DC\u05D4\u05D6\u05DE\u05D9\u05DF \u05D0\u05E0\u05E9\u05D9\u05DD \u05DC\u05E4\u05E8\u05D5\u05D9\u05E7\u05D8\u05D9\u05DD \u05E9\u05DC\u05DA", en: "Invite people to your projects" },
9450
- "staff:read": { he: "\u05DC\u05E7\u05E8\u05D5\u05D0 \u05D0\u05EA \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD \u05E9\u05DC \u05DB\u05DC \u05D4\u05DE\u05E9\u05EA\u05DE\u05E9\u05D9\u05DD \u05D1\u05DE\u05E2\u05E8\u05DB\u05EA", en: "Read every user's data in the system" }
9450
+ "staff:read": { he: "\u05DC\u05E7\u05E8\u05D5\u05D0 \u05D0\u05EA \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD \u05E9\u05DC \u05DB\u05DC \u05D4\u05DE\u05E9\u05EA\u05DE\u05E9\u05D9\u05DD \u05D1\u05DE\u05E2\u05E8\u05DB\u05EA", en: "Read every user's data in the system" },
9451
+ "staff:write": { he: "\u05DC\u05E2\u05E8\u05D5\u05DA \u05D0\u05EA \u05D4\u05E0\u05EA\u05D5\u05E0\u05D9\u05DD \u05E9\u05DC \u05DB\u05DC \u05D4\u05DE\u05E9\u05EA\u05DE\u05E9\u05D9\u05DD \u05D1\u05DE\u05E2\u05E8\u05DB\u05EA", en: "Edit every user's data in the system" }
9451
9452
  };
9452
9453
  var ALL_AGENT_SCOPES = Object.keys(AGENT_SCOPES);
9453
9454
 
@@ -11831,8 +11832,8 @@ To authorize this CLI, visit:
11831
11832
  // src/commands/account.ts
11832
11833
  init_esm_shims();
11833
11834
  async function whoami(opts = {}) {
11834
- const client3 = createClient(readConfig()?.accessToken ?? null);
11835
- const me = await client3.get("auth/me").json();
11835
+ const client4 = createClient(readConfig()?.accessToken ?? null);
11836
+ const me = await client4.get("auth/me").json();
11836
11837
  emit(me, !!opts.json, () => {
11837
11838
  process.stdout.write(`${me.displayName} <${me.email}> \u2014 account role: ${me.role}
11838
11839
  `);
@@ -11847,39 +11848,159 @@ function logout(opts = {}) {
11847
11848
 
11848
11849
  // src/commands/network.ts
11849
11850
  init_esm_shims();
11851
+
11852
+ // src/commands/read.ts
11853
+ init_esm_shims();
11854
+ import { writeFileSync as writeFileSync2 } from "fs";
11850
11855
  function client() {
11851
11856
  return createClient(readConfig()?.accessToken ?? null);
11852
11857
  }
11858
+ function phoneKey(value) {
11859
+ const digits = (value ?? "").replace(/\D/g, "");
11860
+ return digits.startsWith("972") ? `0${digits.slice(3)}` : digits;
11861
+ }
11862
+ async function getProject(id, opts = {}) {
11863
+ const p = await client().get(`projects/${id}`).json();
11864
+ emit(p, !!opts.json, () => {
11865
+ process.stdout.write(`${p.id} ${p.name}
11866
+ `);
11867
+ if (p.customer) {
11868
+ process.stdout.write(`customer: ${p.customer.customerName ?? "\u2014"} ${p.customer.phoneNumber ?? ""}
11869
+ `);
11870
+ }
11871
+ if (p.createdBy) process.stdout.write(`designer: ${p.createdBy.displayName ?? "\u2014"} (${p.createdBy.id ?? ""})
11872
+ `);
11873
+ });
11874
+ }
11875
+ async function projectMembers(id, opts = {}) {
11876
+ const res = await client().get(`projects/${id}/members`).json();
11877
+ emit(res.members, !!opts.json, () => {
11878
+ for (const m of res.members) {
11879
+ process.stdout.write(`${m.userId} ${m.role} ${m.displayName ?? m.email ?? ""}
11880
+ `);
11881
+ }
11882
+ });
11883
+ }
11884
+ async function listCustomers(opts = {}) {
11885
+ const res = await client().get("customers").json();
11886
+ emit(res.customers, !!opts.json, () => {
11887
+ for (const c of res.customers) {
11888
+ process.stdout.write(`${c.id} ${c.customerName ?? ""} ${c.phoneNumber ?? ""}
11889
+ `);
11890
+ }
11891
+ });
11892
+ }
11893
+ async function getCustomer(id, opts = {}) {
11894
+ const c = await client().get(`customers/${id}`).json();
11895
+ emit(
11896
+ c,
11897
+ !!opts.json,
11898
+ () => process.stdout.write(`${c.id} ${c.customerName ?? ""} ${c.phoneNumber ?? ""} ${c.email ?? ""}
11899
+ `)
11900
+ );
11901
+ }
11902
+ async function customerProjects(id, opts = {}) {
11903
+ const res = await client().get(`customers/${id}/projects`).json();
11904
+ const projects2 = res.projects ?? [];
11905
+ emit(projects2, !!opts.json, () => {
11906
+ for (const p of projects2) process.stdout.write(`${p.id} ${p.name}
11907
+ `);
11908
+ });
11909
+ }
11910
+ async function listOrders(opts = {}) {
11911
+ if (!opts.project) throw new Error("--project <id> is required.");
11912
+ const path2 = opts.open ? "orders/open" : "orders";
11913
+ const res = await client().get(path2, { searchParams: { projectId: opts.project } }).json();
11914
+ const orders2 = Array.isArray(res?.orders) ? res.orders : res ? [res] : [];
11915
+ emit(opts.open ? res : orders2, !!opts.json, () => {
11916
+ if (orders2.length === 0) {
11917
+ process.stdout.write("no orders\n");
11918
+ return;
11919
+ }
11920
+ for (const o of orders2) {
11921
+ const total = o.totalPrice !== void 0 ? `${o.totalPrice} ${o.currency ?? "NIS"}` : "";
11922
+ process.stdout.write(`${o.id ?? ""} #${o.orderNumber ?? "\u2014"} rev${o.revisionNumber ?? 1} ${o.status ?? ""} ${total}
11923
+ `);
11924
+ }
11925
+ });
11926
+ }
11927
+ async function orderPdf(id, opts = {}) {
11928
+ const out = opts.out ?? `order-${id}.pdf`;
11929
+ const buf = Buffer.from(await client().get(`orders/${id}/pdf`).arrayBuffer());
11930
+ writeFileSync2(out, buf);
11931
+ emit(
11932
+ { path: out, bytes: buf.length },
11933
+ !!opts.json,
11934
+ () => process.stdout.write(`${out} ${buf.length} bytes
11935
+ `)
11936
+ );
11937
+ }
11938
+ async function designThumbnail(id, opts = {}) {
11939
+ const out = opts.out ?? `design-${id}.jpg`;
11940
+ const buf = Buffer.from(await client().get(`designs/${id}/thumbnail`).arrayBuffer());
11941
+ writeFileSync2(out, buf);
11942
+ emit(
11943
+ { path: out, bytes: buf.length },
11944
+ !!opts.json,
11945
+ () => process.stdout.write(`${out} ${buf.length} bytes
11946
+ `)
11947
+ );
11948
+ }
11949
+ async function helpTexts(opts = {}) {
11950
+ const res = await client().get("help-texts").json();
11951
+ emit(res, !!opts.json, () => process.stdout.write(`${Object.keys(res).length} help text(s)
11952
+ `));
11953
+ }
11954
+
11955
+ // src/commands/network.ts
11956
+ function client2() {
11957
+ return createClient(readConfig()?.accessToken ?? null);
11958
+ }
11853
11959
  async function price(file, opts = {}) {
11854
11960
  const config = await readJsonInput(file);
11855
- const res = await client().post("price", { json: { config } }).json();
11961
+ const res = await client2().post("price", { json: { config } }).json();
11856
11962
  emit(res, !!opts.json, () => process.stdout.write(`${res.total} ${res.currency}
11857
11963
  `));
11858
11964
  }
11859
11965
  async function listDesigns(opts = {}) {
11860
11966
  if (!opts.project) throw new Error("--project <id> is required.");
11861
- const res = await client().get("designs", { searchParams: { projectId: opts.project } }).json();
11967
+ const res = await client2().get("designs", { searchParams: { projectId: opts.project } }).json();
11862
11968
  emit(res.designs, !!opts.json, () => {
11863
11969
  for (const d of res.designs) process.stdout.write(`${d.id} ${d.name}
11864
11970
  `);
11865
11971
  });
11866
11972
  }
11867
11973
  async function listProjects(opts = {}) {
11868
- const res = await client().get("projects").json();
11869
- emit(res.projects, !!opts.json, () => {
11870
- for (const p of res.projects) process.stdout.write(`${p.id} ${p.name}
11974
+ const res = await client2().get("projects").json();
11975
+ let projects2 = res.projects;
11976
+ if (opts.phone) {
11977
+ const want = phoneKey(opts.phone);
11978
+ projects2 = projects2.filter((p) => phoneKey(p.customer?.phoneNumber) === want);
11979
+ }
11980
+ if (opts.designer) {
11981
+ const needle = opts.designer.toLowerCase();
11982
+ projects2 = projects2.filter(
11983
+ (p) => p.createdBy?.id === opts.designer || (p.createdBy?.displayName ?? "").toLowerCase().includes(needle)
11984
+ );
11985
+ }
11986
+ emit(projects2, !!opts.json, () => {
11987
+ for (const p of projects2) {
11988
+ const who = p.createdBy?.displayName ? ` ${p.createdBy.displayName}` : "";
11989
+ const cust = p.customer?.customerName ? ` ${p.customer.customerName}` : "";
11990
+ process.stdout.write(`${p.id} ${p.name}${cust}${who}
11871
11991
  `);
11992
+ }
11872
11993
  });
11873
11994
  }
11874
11995
 
11875
11996
  // src/commands/mutate.ts
11876
11997
  init_esm_shims();
11877
- function client2() {
11998
+ function client3() {
11878
11999
  return createClient(readConfig()?.accessToken ?? null);
11879
12000
  }
11880
12001
  async function plan(file, opts = {}) {
11881
12002
  const config = await readJsonInput(file);
11882
- const res = await client2().post("price", { json: { config }, searchParams: { preview: "bom" } }).json();
12003
+ const res = await client3().post("price", { json: { config }, searchParams: { preview: "bom" } }).json();
11883
12004
  emit(res, !!opts.json, () => {
11884
12005
  process.stdout.write(`${res.total} ${res.currency}
11885
12006
  `);
@@ -11892,12 +12013,30 @@ async function plan(file, opts = {}) {
11892
12013
  async function apply(file, opts = {}) {
11893
12014
  if (!opts.project) throw new Error("--project <id> is required.");
11894
12015
  const config = await readJsonInput(file);
11895
- const res = await client2().post("designs", {
12016
+ const res = await client3().post("designs", {
11896
12017
  json: { projectId: opts.project, name: opts.name ?? "CLI design", config }
11897
12018
  }).json();
11898
12019
  emit(res, !!opts.json, () => process.stdout.write(`${res.id}
11899
12020
  `));
11900
12021
  }
12022
+ async function updateDesign(id, opts = {}) {
12023
+ if (!opts.file && !opts.name) {
12024
+ throw new Error("Nothing to update \u2014 pass --file <config> and/or --name <name>.");
12025
+ }
12026
+ const api = client3();
12027
+ const current = await api.get(`designs/${id}`).json();
12028
+ const body = {};
12029
+ if (opts.file) body.config = await readJsonInput(opts.file);
12030
+ if (opts.name) body.name = opts.name;
12031
+ if (current.version !== void 0) body.version = current.version;
12032
+ const res = await api.put(`designs/${id}`, { json: body }).json();
12033
+ emit(
12034
+ res,
12035
+ !!opts.json,
12036
+ () => process.stdout.write(`${res.id} v${res.version ?? "?"}
12037
+ `)
12038
+ );
12039
+ }
11901
12040
 
11902
12041
  // src/commands/skill.ts
11903
12042
  init_esm_shims();
@@ -11966,7 +12105,7 @@ async function formatCliError(err, json) {
11966
12105
 
11967
12106
  // src/version.ts
11968
12107
  init_esm_shims();
11969
- var CLI_VERSION = "1.1.6";
12108
+ var CLI_VERSION = "1.1.8";
11970
12109
 
11971
12110
  // src/index.ts
11972
12111
  program.name("wardrobe").description("Wardrobe configurator CLI").version(CLI_VERSION);
@@ -11991,8 +12130,8 @@ program.command("describe <idOrFile>").description("Describe a config (local fil
11991
12130
  if (idOrFile === "-" || existsSync3(idOrFile)) {
11992
12131
  config = await readJsonInput(idOrFile);
11993
12132
  } else {
11994
- const client3 = createClient(readConfig()?.accessToken ?? null);
11995
- const design = await client3.get(`designs/${idOrFile}`).json();
12133
+ const client4 = createClient(readConfig()?.accessToken ?? null);
12134
+ const design = await client4.get(`designs/${idOrFile}`).json();
11996
12135
  config = design.config;
11997
12136
  }
11998
12137
  const lines = describeInput(config, o.locale ?? "en");
@@ -12003,8 +12142,21 @@ program.command("plan <file>").description("Dry-run: price a config + BOM previe
12003
12142
  program.command("apply <file>").description("Save a config as a design (file or - for stdin)").requiredOption("--project <id>", "project id").option("--name <name>", "design name").option("--json", "machine-readable output").action((file, o) => apply(file, o));
12004
12143
  var designs = program.command("designs").description("Manage saved designs");
12005
12144
  designs.command("list").description("List designs in a project").requiredOption("--project <id>", "project id").option("--json", "machine-readable output").action((o) => listDesigns(o));
12145
+ designs.command("create <file>").description("Save a config as a new design in a project (same as `apply`)").requiredOption("--project <id>", "project id").option("--name <name>", "design name").option("--json", "machine-readable output").action((file, o) => apply(file, o));
12146
+ designs.command("update <id>").description("Revise a saved design (config and/or name)").option("--file <file>", "new config JSON (file or - for stdin)").option("--name <name>", "new design name").option("--json", "machine-readable output").action((id, o) => updateDesign(id, o));
12147
+ designs.command("thumbnail <id>").description("Download a design's thumbnail image").option("--out <file>", "destination path").option("--json", "machine-readable output").action((id, o) => designThumbnail(id, o));
12006
12148
  var projects = program.command("projects").description("Manage projects");
12007
- projects.command("list").description("List accessible projects").option("--json", "machine-readable output").action((o) => listProjects(o));
12149
+ projects.command("list").description("List accessible projects").option("--phone <number>", "filter by the customer's phone number").option("--designer <idOrName>", "filter by the designer who owns the project").option("--json", "machine-readable output").action((o) => listProjects(o));
12150
+ projects.command("get <id>").description("Show one project").option("--json", "machine-readable output").action((id, o) => getProject(id, o));
12151
+ projects.command("members <id>").description("List a project's owner and members").option("--json", "machine-readable output").action((id, o) => projectMembers(id, o));
12152
+ var customers = program.command("customers").description("Read customers");
12153
+ customers.command("list").description("List your customers").option("--json", "machine-readable output").action((o) => listCustomers(o));
12154
+ customers.command("get <id>").description("Show one customer").option("--json", "machine-readable output").action((id, o) => getCustomer(id, o));
12155
+ customers.command("projects <id>").description("List a customer's projects").option("--json", "machine-readable output").action((id, o) => customerProjects(id, o));
12156
+ var orders = program.command("orders").description("Read orders");
12157
+ orders.command("list").description("List a project's orders").requiredOption("--project <id>", "project id").option("--open", "only the project's active (non-cancelled) order").option("--json", "machine-readable output").action((o) => listOrders(o));
12158
+ orders.command("pdf <id>").description("Download an order PDF").option("--out <file>", "destination path").option("--json", "machine-readable output").action((id, o) => orderPdf(id, o));
12159
+ program.command("help-texts").description("Read the help-text table").option("--json", "machine-readable output").action((o) => helpTexts(o));
12008
12160
  var skill = program.command("skill").description("Manage the bundled Claude Skill");
12009
12161
  skill.command("install").description("Install the wardrobe-configurator Skill for Claude Code").option("--project", "install into ./.claude/skills instead of ~/.claude/skills").option("--json", "machine-readable output").action((o) => skillInstall(o));
12010
12162
  program.parseAsync().catch(async (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lahat-group/cli",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "CLI + Claude Skill to build, validate, price, and save wardrobe designs.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: wardrobe-configurator
3
- description: Use when the user wants to build, configure, validate, price, or order a wardrobe/closet design from the terminal — phrases like "configure a wardrobe", "price a design", "build a closet layout", "validate my wardrobe config". Drives the `wardrobe` CLI (HTTP client over the configurator API); a WardrobeConfig JSON file on disk is the unit of work.
3
+ description: Use when the user wants to build, configure, validate, price, or order a wardrobe/closet design from the terminal — phrases like "configure a wardrobe", "price a design", "build a closet layout", "validate my wardrobe config" — OR wants to look up existing wardrobe data: projects, customers, designers, orders and their PDFs, e.g. "which projects does this designer have", "find the customer with phone 05x", "who is on this project", "what is the status of this order", "download the order PDF". Drives the `wardrobe` CLI (HTTP client over the configurator API); a WardrobeConfig JSON file on disk is the unit of work for design verbs, and every lookup verb is read-only.
4
4
  ---
5
5
 
6
6
  # Wardrobe Configurator CLI
@@ -33,15 +33,89 @@ agent loop is: **init → edit → validate → price → plan → apply**.
33
33
  5. **plan** — `wardrobe plan wardrobe.json` → dry-run price **plus a BOM preview**.
34
34
  Mutates nothing. Use this to preview before applying.
35
35
  6. **apply** — `wardrobe apply wardrobe.json --project <id> [--name <name>]` → saves
36
- the config as a design and prints its `{id}`. **`--project` is required.**
36
+ the config as a design in that project and prints its `{id}`. **`--project` is
37
+ required.** `wardrobe designs create <file> --project <id>` is the same verb under
38
+ the `designs` group, if that reads more clearly in context.
39
+
40
+ ## Revising a saved design
41
+
42
+ `wardrobe designs update <id> [--file <config>] [--name <name>]` revises a design in
43
+ place — pass either or both. Prefer this over creating a second design when the user
44
+ is iterating; a project accumulating `CLI design`, `CLI design 2`, `CLI design 3` is
45
+ almost always a bug in how the agent worked, not what the user wanted.
46
+
47
+ Validate and price the new config **before** updating, same as for a create.
48
+
49
+ Two refusals to expect, neither of which is retryable:
50
+
51
+ - **`409 stale_version`** — someone edited that design (usually the human, in the web
52
+ app) after you read it. The CLI sends the version it last saw precisely so this
53
+ fails loudly instead of overwriting their edit. Re-read the design, re-apply the
54
+ user's intent on top, and tell them it changed underneath you. **Never** loop.
55
+ - **`423 projectLocked`** — the project has a live (non-cancelled) order, which froze
56
+ its designs. No design in it can be created, updated or deleted until a human
57
+ cancels or completes that order. Say so; don't try another design or another verb.
58
+
59
+ Note that updating a config **clears the design's thumbnail** (the stored image was of
60
+ the old config); the web app re-captures it on next view. That is expected, not an error.
37
61
 
38
62
  `wardrobe describe <idOrFile>` gives a human-readable summary of a local file/`-`
39
63
  or a saved design id.
40
64
 
41
- ## Listing context
42
-
43
- - `wardrobe projects list` — projects you can access (needed for `--project`).
44
- - `wardrobe designs list --project <id>` — designs saved in a project.
65
+ ## Reading what exists
66
+
67
+ Every read the key can reach has a verb. **All of them are GETs** — the CLI can read
68
+ customers and orders but never create or change them (`customers:write`,
69
+ `orders:write` and `quotations:write` are not on the CLI's key, so those routes
70
+ answer `403 insufficient_scope` whoever signed in).
71
+
72
+ | Verb | Shows |
73
+ |---|---|
74
+ | `wardrobe projects list` | Projects you can access (needed for `--project`) |
75
+ | `wardrobe projects list --phone <number>` | Only projects whose **customer** has that number |
76
+ | `wardrobe projects list --designer <idOrName>` | Only projects owned by that designer |
77
+ | `wardrobe projects get <id>` | One project: customer + owning designer |
78
+ | `wardrobe projects members <id>` | Owner plus every invited member, with roles |
79
+ | `wardrobe customers list` | Your customers |
80
+ | `wardrobe customers get <id>` | One customer |
81
+ | `wardrobe customers projects <id>` | That customer's projects |
82
+ | `wardrobe designs list --project <id>` | Designs saved in a project |
83
+ | `wardrobe designs thumbnail <id> --out f.jpg` | The design's rendered thumbnail |
84
+ | `wardrobe orders list --project <id>` | Every order the project has had |
85
+ | `wardrobe orders list --project <id> --open` | Only its active (non-cancelled) order |
86
+ | `wardrobe orders pdf <id> --out f.pdf` | The order PDF |
87
+ | `wardrobe help-texts` | The help-text table |
88
+
89
+ `projects list` already returns each project's **customer and owning designer inline** —
90
+ so "who is the customer on each of this designer's projects" is one call plus a filter,
91
+ never one call per project. Prefer `--phone` / `--designer` over fetching and grepping.
92
+
93
+ ### What you can see is not what exists
94
+
95
+ The list is bounded by the key, so an empty result is as often a **visibility** answer as
96
+ a data one. `wardrobe whoami --json` reports the truth under `agent.scopes` and `role`:
97
+
98
+ - An ordinary designer or client sees **their own** projects plus ones they were invited
99
+ to. A project belonging to someone else answers `403 Forbidden`, not `404`.
100
+ - A key approved by an **admin or support** user *and* carrying **`staff:read`** sees
101
+ **every** project, customer and order in the system. That scope is granted only to
102
+ staff, only for GETs, and is listed by name on the consent screen. The same approver
103
+ also receives **`staff:write`**, which lets `designs create` / `designs update` land in
104
+ a project the admin does not own. A key logged in before 2026-09-05 lacks it — a `403
105
+ Forbidden` on a write to someone else's project means `wardrobe login` again.
106
+
107
+ Never tell a user something does not exist when you only know your key cannot see it.
108
+ Say which it is: `403 Forbidden` means the row is someone else's, `404` means it is gone.
109
+
110
+ ### Not available here
111
+
112
+ Don't reach for these; they are not CLI verbs and not agent-reachable:
113
+
114
+ - **Anything under `/admin`**, plus commissions and invitations — barred to every agent
115
+ key at the mount (`403 agent_not_permitted`). No scope unlocks them.
116
+ - **Quotation history.** `POST /quotations/pdf` is open to other clients, but reading a
117
+ project's past quotes is JWT-only — a human in the web app.
118
+ - **Manufacturing-files export.** Staff-only, through the web app.
45
119
 
46
120
  ## WardrobeConfig shape (top-level)
47
121
 
@@ -65,10 +139,12 @@ Prefer generating the skeleton with `wardrobe init` rather than hand-writing it
65
139
 
66
140
  ## Guardrails — committing to an order requires a human
67
141
 
68
- - **The CLI cannot place orders.** The `wardrobe-cli` client is registered with
69
- `profile:read`, `projects:read`, `designs:read`, `designs:write`, `price:read` —
70
- **no order or admin scope** — so every order route answers `403 insufficient_scope`.
71
- Don't attempt to order, or to run admin/catalog operations.
142
+ - **The CLI can read orders but never place one.** Its key carries `profile:read`,
143
+ `projects:read`, `customers:read`, `designs:read`, `designs:write`, `orders:read`,
144
+ `price:read` (+ `staff:read` and `staff:write` for staff) — **every write beyond designs is absent**,
145
+ and so is every admin scope. `wardrobe orders list` and `orders pdf` work; creating
146
+ or changing an order, a customer or a quotation answers `403 insufficient_scope`.
147
+ Don't attempt it, and don't attempt admin/catalog operations.
72
148
  - To turn a saved design into an order, direct the **human** to the web app: they
73
149
  open the project, review the design, and complete the **verify-details** flow —
74
150
  which computes the final price, freezes the PDF, and advances the order. Only a