@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 +228 -24
- package/package.json +1 -1
- package/skill/SKILL.md +83 -10
package/dist/index.js
CHANGED
|
@@ -3128,10 +3128,12 @@ var HELP_KEYS = {
|
|
|
3128
3128
|
},
|
|
3129
3129
|
orderStatus: {
|
|
3130
3130
|
created: "orderStatus.created",
|
|
3131
|
-
|
|
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.
|
|
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
|
-
"
|
|
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: "
|
|
9469
|
-
{ track: "main", from: "
|
|
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) =>
|
|
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) =>
|
|
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
|
-
...[
|
|
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
|
-
|
|
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
|
|
11782
|
-
const me = await
|
|
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
|
|
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
|
|
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
|
|
11816
|
-
|
|
11817
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
11942
|
-
const design = await
|
|
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
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
|
|
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
|
-
##
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
69
|
-
`
|
|
70
|
-
|
|
71
|
-
|
|
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
|