@lahat-group/cli 1.1.5 → 1.1.7

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
@@ -3128,10 +3128,12 @@ var HELP_KEYS = {
3128
3128
  },
3129
3129
  orderStatus: {
3130
3130
  created: "orderStatus.created",
3131
- pending_contact: "orderStatus.pending_contact",
3131
+ pending_payment: "orderStatus.pending_payment",
3132
3132
  paid: "orderStatus.paid",
3133
+ pending_measurement: "orderStatus.pending_measurement",
3133
3134
  sent_to_manufacturing: "orderStatus.sent_to_manufacturing",
3134
3135
  in_manufacturing: "orderStatus.in_manufacturing",
3136
+ pending_assembly: "orderStatus.pending_assembly",
3135
3137
  provided: "orderStatus.provided"
3136
3138
  }
3137
3139
  };
@@ -3156,10 +3158,12 @@ var ALL_HELP_KEYS = [
3156
3158
  HELP_KEYS.inspector.layout,
3157
3159
  HELP_KEYS.inspector.column,
3158
3160
  HELP_KEYS.orderStatus.created,
3159
- HELP_KEYS.orderStatus.pending_contact,
3161
+ HELP_KEYS.orderStatus.pending_payment,
3160
3162
  HELP_KEYS.orderStatus.paid,
3163
+ HELP_KEYS.orderStatus.pending_measurement,
3161
3164
  HELP_KEYS.orderStatus.sent_to_manufacturing,
3162
3165
  HELP_KEYS.orderStatus.in_manufacturing,
3166
+ HELP_KEYS.orderStatus.pending_assembly,
3163
3167
  HELP_KEYS.orderStatus.provided
3164
3168
  ];
3165
3169
 
@@ -9442,7 +9446,8 @@ var AGENT_SCOPES = {
9442
9446
  "orders:read": { he: "\u05DC\u05E8\u05D0\u05D5\u05EA \u05D0\u05EA \u05D4\u05D4\u05D6\u05DE\u05E0\u05D5\u05EA \u05E9\u05DC\u05DA", en: "See your orders" },
9443
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" },
9444
9448
  "orders:commit": { he: "\u05DC\u05D0\u05E9\u05E8 \u05D4\u05D6\u05DE\u05E0\u05D4 \u05D1\u05E9\u05DE\u05DA", en: "Confirm an order on your behalf" },
9445
- "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" }
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" }
9446
9451
  };
9447
9452
  var ALL_AGENT_SCOPES = Object.keys(AGENT_SCOPES);
9448
9453
 
@@ -9453,10 +9458,12 @@ init_esm_shims();
9453
9458
  init_esm_shims();
9454
9459
  var ORDER_STATUSES = [
9455
9460
  "created",
9456
- "pending_contact",
9461
+ "pending_payment",
9457
9462
  "paid",
9463
+ "pending_measurement",
9458
9464
  "sent_to_manufacturing",
9459
9465
  "in_manufacturing",
9466
+ "pending_assembly",
9460
9467
  "provided",
9461
9468
  "cancelled"
9462
9469
  ];
@@ -9465,25 +9472,66 @@ var LOGISTICS_STATUSES = ["pending_coordination", "coordinated", "done"];
9465
9472
  var OFFICE = ["admin", "support"];
9466
9473
  var TRANSITIONS = [
9467
9474
  // Main pipeline
9468
- { track: "main", from: "created", to: "pending_contact", roles: ["admin", "support", "designer", "client"], viaVerification: true },
9469
- { track: "main", from: "pending_contact", to: "paid", roles: OFFICE },
9475
+ { track: "main", from: "created", to: "pending_payment", roles: ["admin", "support", "designer", "client"], viaVerification: true },
9476
+ { track: "main", from: "pending_payment", to: "paid", roles: OFFICE },
9477
+ // Exactly one of the next two is unblocked, decided by whether the customer
9478
+ // bought the measurement service. `pending_measurement` is never entered — and
9479
+ // so never appears in statusHistory — when they didn't.
9480
+ {
9481
+ track: "main",
9482
+ from: "paid",
9483
+ to: "pending_measurement",
9484
+ roles: OFFICE,
9485
+ guard: (o) => o.services.measurement ? null : "measurementNotPurchased"
9486
+ },
9470
9487
  {
9471
9488
  track: "main",
9472
9489
  from: "paid",
9473
9490
  to: "sent_to_manufacturing",
9474
9491
  roles: OFFICE,
9475
- guard: (o) => !o.services.measurement || o.measurement?.status === "measured" ? null : "measurementNotDone"
9492
+ guard: (o) => o.services.measurement ? "measurementRequired" : null
9493
+ },
9494
+ {
9495
+ track: "main",
9496
+ from: "pending_measurement",
9497
+ to: "sent_to_manufacturing",
9498
+ roles: OFFICE,
9499
+ guard: (o) => o.measurement?.status === "measured" ? null : "measurementNotDone"
9476
9500
  },
9477
9501
  { track: "main", from: "sent_to_manufacturing", to: "in_manufacturing", roles: OFFICE, requires: "estimatedDeliveryDate" },
9502
+ // Same either/or shape as measurement, for transport & installation.
9503
+ {
9504
+ track: "main",
9505
+ from: "in_manufacturing",
9506
+ to: "pending_assembly",
9507
+ roles: OFFICE,
9508
+ guard: (o) => o.services.logistics ? null : "logisticsNotPurchased"
9509
+ },
9478
9510
  {
9479
9511
  track: "main",
9480
9512
  from: "in_manufacturing",
9481
9513
  to: "provided",
9482
9514
  roles: OFFICE,
9483
- guard: (o) => !o.services.logistics || o.logistics?.status === "done" ? null : "logisticsNotDone"
9515
+ guard: (o) => o.services.logistics ? "logisticsRequired" : null
9516
+ },
9517
+ {
9518
+ track: "main",
9519
+ from: "pending_assembly",
9520
+ to: "provided",
9521
+ roles: OFFICE,
9522
+ guard: (o) => o.logistics?.status === "done" ? null : "logisticsNotDone"
9484
9523
  },
9485
9524
  // Cancel — reachable from any non-terminal main status, office only
9486
- ...["created", "pending_contact", "paid", "sent_to_manufacturing", "in_manufacturing", "provided"].map(
9525
+ ...[
9526
+ "created",
9527
+ "pending_payment",
9528
+ "paid",
9529
+ "pending_measurement",
9530
+ "sent_to_manufacturing",
9531
+ "in_manufacturing",
9532
+ "pending_assembly",
9533
+ "provided"
9534
+ ].map(
9487
9535
  (from) => ({ track: "main", from, to: "cancelled", roles: OFFICE })
9488
9536
  ),
9489
9537
  // Measurement sub-track
@@ -9494,14 +9542,19 @@ var TRANSITIONS = [
9494
9542
  { track: "logistics", from: "coordinated", to: "done", roles: OFFICE }
9495
9543
  ];
9496
9544
 
9545
+ // ../../packages/shared/src/quotations.ts
9546
+ init_esm_shims();
9547
+
9497
9548
  // ../../packages/shared/src/orderEmailEvents.ts
9498
9549
  init_esm_shims();
9499
9550
  var MAIN_LABELS = {
9500
9551
  created: "\u05D4\u05D6\u05DE\u05E0\u05D4 \u05E0\u05D5\u05E6\u05E8\u05D4",
9501
- pending_contact: "\u05DE\u05DE\u05EA\u05D9\u05DF \u05DC\u05D9\u05E6\u05D9\u05E8\u05EA \u05E7\u05E9\u05E8",
9552
+ pending_payment: "\u05DE\u05DE\u05EA\u05D9\u05DF \u05DC\u05EA\u05E9\u05DC\u05D5\u05DD",
9502
9553
  paid: "\u05E9\u05D5\u05DC\u05DD",
9554
+ pending_measurement: "\u05DE\u05DE\u05EA\u05D9\u05DF \u05DC\u05DE\u05D3\u05D9\u05D3\u05D4",
9503
9555
  sent_to_manufacturing: "\u05E0\u05E9\u05DC\u05D7 \u05DC\u05D9\u05D9\u05E6\u05D5\u05E8",
9504
9556
  in_manufacturing: "\u05D1\u05D9\u05D9\u05E6\u05D5\u05E8",
9557
+ pending_assembly: "\u05DE\u05DE\u05EA\u05D9\u05DF \u05DC\u05D4\u05E8\u05DB\u05D1\u05D4",
9505
9558
  provided: "\u05E1\u05D5\u05E4\u05E7",
9506
9559
  cancelled: "\u05D1\u05D5\u05D8\u05DC"
9507
9560
  };
@@ -11778,8 +11831,8 @@ To authorize this CLI, visit:
11778
11831
  // src/commands/account.ts
11779
11832
  init_esm_shims();
11780
11833
  async function whoami(opts = {}) {
11781
- const client3 = createClient(readConfig()?.accessToken ?? null);
11782
- const me = await client3.get("auth/me").json();
11834
+ const client4 = createClient(readConfig()?.accessToken ?? null);
11835
+ const me = await client4.get("auth/me").json();
11783
11836
  emit(me, !!opts.json, () => {
11784
11837
  process.stdout.write(`${me.displayName} <${me.email}> \u2014 account role: ${me.role}
11785
11838
  `);
@@ -11794,39 +11847,159 @@ function logout(opts = {}) {
11794
11847
 
11795
11848
  // src/commands/network.ts
11796
11849
  init_esm_shims();
11850
+
11851
+ // src/commands/read.ts
11852
+ init_esm_shims();
11853
+ import { writeFileSync as writeFileSync2 } from "fs";
11797
11854
  function client() {
11798
11855
  return createClient(readConfig()?.accessToken ?? null);
11799
11856
  }
11857
+ function phoneKey(value) {
11858
+ const digits = (value ?? "").replace(/\D/g, "");
11859
+ return digits.startsWith("972") ? `0${digits.slice(3)}` : digits;
11860
+ }
11861
+ async function getProject(id, opts = {}) {
11862
+ const p = await client().get(`projects/${id}`).json();
11863
+ emit(p, !!opts.json, () => {
11864
+ process.stdout.write(`${p.id} ${p.name}
11865
+ `);
11866
+ if (p.customer) {
11867
+ process.stdout.write(`customer: ${p.customer.customerName ?? "\u2014"} ${p.customer.phoneNumber ?? ""}
11868
+ `);
11869
+ }
11870
+ if (p.createdBy) process.stdout.write(`designer: ${p.createdBy.displayName ?? "\u2014"} (${p.createdBy.id ?? ""})
11871
+ `);
11872
+ });
11873
+ }
11874
+ async function projectMembers(id, opts = {}) {
11875
+ const res = await client().get(`projects/${id}/members`).json();
11876
+ emit(res.members, !!opts.json, () => {
11877
+ for (const m of res.members) {
11878
+ process.stdout.write(`${m.userId} ${m.role} ${m.displayName ?? m.email ?? ""}
11879
+ `);
11880
+ }
11881
+ });
11882
+ }
11883
+ async function listCustomers(opts = {}) {
11884
+ const res = await client().get("customers").json();
11885
+ emit(res.customers, !!opts.json, () => {
11886
+ for (const c of res.customers) {
11887
+ process.stdout.write(`${c.id} ${c.customerName ?? ""} ${c.phoneNumber ?? ""}
11888
+ `);
11889
+ }
11890
+ });
11891
+ }
11892
+ async function getCustomer(id, opts = {}) {
11893
+ const c = await client().get(`customers/${id}`).json();
11894
+ emit(
11895
+ c,
11896
+ !!opts.json,
11897
+ () => process.stdout.write(`${c.id} ${c.customerName ?? ""} ${c.phoneNumber ?? ""} ${c.email ?? ""}
11898
+ `)
11899
+ );
11900
+ }
11901
+ async function customerProjects(id, opts = {}) {
11902
+ const res = await client().get(`customers/${id}/projects`).json();
11903
+ const projects2 = res.projects ?? [];
11904
+ emit(projects2, !!opts.json, () => {
11905
+ for (const p of projects2) process.stdout.write(`${p.id} ${p.name}
11906
+ `);
11907
+ });
11908
+ }
11909
+ async function listOrders(opts = {}) {
11910
+ if (!opts.project) throw new Error("--project <id> is required.");
11911
+ const path2 = opts.open ? "orders/open" : "orders";
11912
+ const res = await client().get(path2, { searchParams: { projectId: opts.project } }).json();
11913
+ const orders2 = Array.isArray(res?.orders) ? res.orders : res ? [res] : [];
11914
+ emit(opts.open ? res : orders2, !!opts.json, () => {
11915
+ if (orders2.length === 0) {
11916
+ process.stdout.write("no orders\n");
11917
+ return;
11918
+ }
11919
+ for (const o of orders2) {
11920
+ const total = o.totalPrice !== void 0 ? `${o.totalPrice} ${o.currency ?? "NIS"}` : "";
11921
+ process.stdout.write(`${o.id ?? ""} #${o.orderNumber ?? "\u2014"} rev${o.revisionNumber ?? 1} ${o.status ?? ""} ${total}
11922
+ `);
11923
+ }
11924
+ });
11925
+ }
11926
+ async function orderPdf(id, opts = {}) {
11927
+ const out = opts.out ?? `order-${id}.pdf`;
11928
+ const buf = Buffer.from(await client().get(`orders/${id}/pdf`).arrayBuffer());
11929
+ writeFileSync2(out, buf);
11930
+ emit(
11931
+ { path: out, bytes: buf.length },
11932
+ !!opts.json,
11933
+ () => process.stdout.write(`${out} ${buf.length} bytes
11934
+ `)
11935
+ );
11936
+ }
11937
+ async function designThumbnail(id, opts = {}) {
11938
+ const out = opts.out ?? `design-${id}.jpg`;
11939
+ const buf = Buffer.from(await client().get(`designs/${id}/thumbnail`).arrayBuffer());
11940
+ writeFileSync2(out, buf);
11941
+ emit(
11942
+ { path: out, bytes: buf.length },
11943
+ !!opts.json,
11944
+ () => process.stdout.write(`${out} ${buf.length} bytes
11945
+ `)
11946
+ );
11947
+ }
11948
+ async function helpTexts(opts = {}) {
11949
+ const res = await client().get("help-texts").json();
11950
+ emit(res, !!opts.json, () => process.stdout.write(`${Object.keys(res).length} help text(s)
11951
+ `));
11952
+ }
11953
+
11954
+ // src/commands/network.ts
11955
+ function client2() {
11956
+ return createClient(readConfig()?.accessToken ?? null);
11957
+ }
11800
11958
  async function price(file, opts = {}) {
11801
11959
  const config = await readJsonInput(file);
11802
- const res = await client().post("price", { json: { config } }).json();
11960
+ const res = await client2().post("price", { json: { config } }).json();
11803
11961
  emit(res, !!opts.json, () => process.stdout.write(`${res.total} ${res.currency}
11804
11962
  `));
11805
11963
  }
11806
11964
  async function listDesigns(opts = {}) {
11807
11965
  if (!opts.project) throw new Error("--project <id> is required.");
11808
- const res = await client().get("designs", { searchParams: { projectId: opts.project } }).json();
11966
+ const res = await client2().get("designs", { searchParams: { projectId: opts.project } }).json();
11809
11967
  emit(res.designs, !!opts.json, () => {
11810
11968
  for (const d of res.designs) process.stdout.write(`${d.id} ${d.name}
11811
11969
  `);
11812
11970
  });
11813
11971
  }
11814
11972
  async function listProjects(opts = {}) {
11815
- const res = await client().get("projects").json();
11816
- emit(res.projects, !!opts.json, () => {
11817
- for (const p of res.projects) process.stdout.write(`${p.id} ${p.name}
11973
+ const res = await client2().get("projects").json();
11974
+ let projects2 = res.projects;
11975
+ if (opts.phone) {
11976
+ const want = phoneKey(opts.phone);
11977
+ projects2 = projects2.filter((p) => phoneKey(p.customer?.phoneNumber) === want);
11978
+ }
11979
+ if (opts.designer) {
11980
+ const needle = opts.designer.toLowerCase();
11981
+ projects2 = projects2.filter(
11982
+ (p) => p.createdBy?.id === opts.designer || (p.createdBy?.displayName ?? "").toLowerCase().includes(needle)
11983
+ );
11984
+ }
11985
+ emit(projects2, !!opts.json, () => {
11986
+ for (const p of projects2) {
11987
+ const who = p.createdBy?.displayName ? ` ${p.createdBy.displayName}` : "";
11988
+ const cust = p.customer?.customerName ? ` ${p.customer.customerName}` : "";
11989
+ process.stdout.write(`${p.id} ${p.name}${cust}${who}
11818
11990
  `);
11991
+ }
11819
11992
  });
11820
11993
  }
11821
11994
 
11822
11995
  // src/commands/mutate.ts
11823
11996
  init_esm_shims();
11824
- function client2() {
11997
+ function client3() {
11825
11998
  return createClient(readConfig()?.accessToken ?? null);
11826
11999
  }
11827
12000
  async function plan(file, opts = {}) {
11828
12001
  const config = await readJsonInput(file);
11829
- const res = await client2().post("price", { json: { config }, searchParams: { preview: "bom" } }).json();
12002
+ const res = await client3().post("price", { json: { config }, searchParams: { preview: "bom" } }).json();
11830
12003
  emit(res, !!opts.json, () => {
11831
12004
  process.stdout.write(`${res.total} ${res.currency}
11832
12005
  `);
@@ -11839,12 +12012,30 @@ async function plan(file, opts = {}) {
11839
12012
  async function apply(file, opts = {}) {
11840
12013
  if (!opts.project) throw new Error("--project <id> is required.");
11841
12014
  const config = await readJsonInput(file);
11842
- const res = await client2().post("designs", {
12015
+ const res = await client3().post("designs", {
11843
12016
  json: { projectId: opts.project, name: opts.name ?? "CLI design", config }
11844
12017
  }).json();
11845
12018
  emit(res, !!opts.json, () => process.stdout.write(`${res.id}
11846
12019
  `));
11847
12020
  }
12021
+ async function updateDesign(id, opts = {}) {
12022
+ if (!opts.file && !opts.name) {
12023
+ throw new Error("Nothing to update \u2014 pass --file <config> and/or --name <name>.");
12024
+ }
12025
+ const api = client3();
12026
+ const current = await api.get(`designs/${id}`).json();
12027
+ const body = {};
12028
+ if (opts.file) body.config = await readJsonInput(opts.file);
12029
+ if (opts.name) body.name = opts.name;
12030
+ if (current.version !== void 0) body.version = current.version;
12031
+ const res = await api.put(`designs/${id}`, { json: body }).json();
12032
+ emit(
12033
+ res,
12034
+ !!opts.json,
12035
+ () => process.stdout.write(`${res.id} v${res.version ?? "?"}
12036
+ `)
12037
+ );
12038
+ }
11848
12039
 
11849
12040
  // src/commands/skill.ts
11850
12041
  init_esm_shims();
@@ -11913,7 +12104,7 @@ async function formatCliError(err, json) {
11913
12104
 
11914
12105
  // src/version.ts
11915
12106
  init_esm_shims();
11916
- var CLI_VERSION = "1.1.5";
12107
+ var CLI_VERSION = "1.1.7";
11917
12108
 
11918
12109
  // src/index.ts
11919
12110
  program.name("wardrobe").description("Wardrobe configurator CLI").version(CLI_VERSION);
@@ -11938,8 +12129,8 @@ program.command("describe <idOrFile>").description("Describe a config (local fil
11938
12129
  if (idOrFile === "-" || existsSync3(idOrFile)) {
11939
12130
  config = await readJsonInput(idOrFile);
11940
12131
  } else {
11941
- const client3 = createClient(readConfig()?.accessToken ?? null);
11942
- const design = await client3.get(`designs/${idOrFile}`).json();
12132
+ const client4 = createClient(readConfig()?.accessToken ?? null);
12133
+ const design = await client4.get(`designs/${idOrFile}`).json();
11943
12134
  config = design.config;
11944
12135
  }
11945
12136
  const lines = describeInput(config, o.locale ?? "en");
@@ -11950,8 +12141,21 @@ program.command("plan <file>").description("Dry-run: price a config + BOM previe
11950
12141
  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));
11951
12142
  var designs = program.command("designs").description("Manage saved designs");
11952
12143
  designs.command("list").description("List designs in a project").requiredOption("--project <id>", "project id").option("--json", "machine-readable output").action((o) => listDesigns(o));
12144
+ 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));
12145
+ 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));
12146
+ 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));
11953
12147
  var projects = program.command("projects").description("Manage projects");
11954
- projects.command("list").description("List accessible projects").option("--json", "machine-readable output").action((o) => listProjects(o));
12148
+ 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));
12149
+ projects.command("get <id>").description("Show one project").option("--json", "machine-readable output").action((id, o) => getProject(id, o));
12150
+ projects.command("members <id>").description("List a project's owner and members").option("--json", "machine-readable output").action((id, o) => projectMembers(id, o));
12151
+ var customers = program.command("customers").description("Read customers");
12152
+ customers.command("list").description("List your customers").option("--json", "machine-readable output").action((o) => listCustomers(o));
12153
+ customers.command("get <id>").description("Show one customer").option("--json", "machine-readable output").action((id, o) => getCustomer(id, o));
12154
+ customers.command("projects <id>").description("List a customer's projects").option("--json", "machine-readable output").action((id, o) => customerProjects(id, o));
12155
+ var orders = program.command("orders").description("Read orders");
12156
+ 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));
12157
+ 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));
12158
+ program.command("help-texts").description("Read the help-text table").option("--json", "machine-readable output").action((o) => helpTexts(o));
11955
12159
  var skill = program.command("skill").description("Manage the bundled Claude Skill");
11956
12160
  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));
11957
12161
  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.5",
3
+ "version": "1.1.7",
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,86 @@ 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.
103
+
104
+ Never tell a user something does not exist when you only know your key cannot see it.
105
+ Say which it is: `403 Forbidden` means the row is someone else's, `404` means it is gone.
106
+
107
+ ### Not available here
108
+
109
+ Don't reach for these; they are not CLI verbs and not agent-reachable:
110
+
111
+ - **Anything under `/admin`**, plus commissions and invitations — barred to every agent
112
+ key at the mount (`403 agent_not_permitted`). No scope unlocks them.
113
+ - **Quotation history.** `POST /quotations/pdf` is open to other clients, but reading a
114
+ project's past quotes is JWT-only — a human in the web app.
115
+ - **Manufacturing-files export.** Staff-only, through the web app.
45
116
 
46
117
  ## WardrobeConfig shape (top-level)
47
118
 
@@ -65,10 +136,12 @@ Prefer generating the skeleton with `wardrobe init` rather than hand-writing it
65
136
 
66
137
  ## Guardrails — committing to an order requires a human
67
138
 
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.
139
+ - **The CLI can read orders but never place one.** Its key carries `profile:read`,
140
+ `projects:read`, `customers:read`, `designs:read`, `designs:write`, `orders:read`,
141
+ `price:read` (+ `staff:read` for staff) — **every write beyond designs is absent**,
142
+ and so is every admin scope. `wardrobe orders list` and `orders pdf` work; creating
143
+ or changing an order, a customer or a quotation answers `403 insufficient_scope`.
144
+ Don't attempt it, and don't attempt admin/catalog operations.
72
145
  - To turn a saved design into an order, direct the **human** to the web app: they
73
146
  open the project, review the design, and complete the **verify-details** flow —
74
147
  which computes the final price, freezes the PDF, and advances the order. Only a