@jsm-mit/sultana-agent-tools-package 0.5.0 → 0.7.0
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/README.md +76 -0
- package/dist/booksy/fetch-page.d.ts +25 -0
- package/dist/booksy/fetch-page.d.ts.map +1 -0
- package/dist/booksy/fetch-page.js +93 -0
- package/dist/booksy/import-tools.d.ts +22 -0
- package/dist/booksy/import-tools.d.ts.map +1 -0
- package/dist/booksy/import-tools.js +413 -0
- package/dist/booksy/nuxt-payload.d.ts +15 -0
- package/dist/booksy/nuxt-payload.d.ts.map +1 -0
- package/dist/booksy/nuxt-payload.js +96 -0
- package/dist/booksy/parse-salon.d.ts +61 -0
- package/dist/booksy/parse-salon.d.ts.map +1 -0
- package/dist/booksy/parse-salon.js +212 -0
- package/dist/booksy/to-sultana.d.ts +62 -0
- package/dist/booksy/to-sultana.d.ts.map +1 -0
- package/dist/booksy/to-sultana.js +77 -0
- package/dist/booksy/tools.d.ts +57 -0
- package/dist/booksy/tools.d.ts.map +1 -0
- package/dist/booksy/tools.js +227 -0
- package/dist/customer/create-customer-tools.d.ts +4 -2
- package/dist/customer/create-customer-tools.d.ts.map +1 -1
- package/dist/customer/create-customer-tools.js +7 -3
- package/dist/customer/persona.d.ts +5 -3
- package/dist/customer/persona.d.ts.map +1 -1
- package/dist/customer/persona.js +21 -1
- package/dist/customer/texts.d.ts +79 -0
- package/dist/customer/texts.d.ts.map +1 -0
- package/dist/customer/texts.js +115 -0
- package/dist/customer/tools.d.ts +3 -0
- package/dist/customer/tools.d.ts.map +1 -1
- package/dist/customer/tools.js +80 -63
- package/dist/ic-salon-core-port.d.ts +4 -0
- package/dist/ic-salon-core-port.d.ts.map +1 -1
- package/dist/ic-salon-core-port.js +19 -1
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/persona.d.ts +5 -0
- package/dist/persona.d.ts.map +1 -1
- package/dist/persona.js +17 -0
- package/package.json +2 -1
package/dist/customer/tools.js
CHANGED
|
@@ -3,7 +3,11 @@ import { ArgumentError, readBoolean, readOptionalNumber, readOptionalString, rea
|
|
|
3
3
|
import { writeGate } from "../tools/write-gate.js";
|
|
4
4
|
import { err, ok } from "../types.js";
|
|
5
5
|
import { customerFormats } from "./format.js";
|
|
6
|
+
import { customerTexts } from "./texts.js";
|
|
6
7
|
import { foldStartRanges, freeStartMinutes, hostTimeZoneProblem, isDay, localDayOf, localStampOf, localTimeOf, minutesOf, momentOf, onSlotGrid, timeOfMinutes, } from "./time.js";
|
|
8
|
+
function textsOf(context) {
|
|
9
|
+
return context.texts ?? customerTexts();
|
|
10
|
+
}
|
|
7
11
|
/**
|
|
8
12
|
* The tools never hand the model a bare distance, price or duration: it would copy "8.3" with
|
|
9
13
|
* a dot into a Polish sentence. They hand it the text the customer's region writes.
|
|
@@ -21,24 +25,23 @@ const CONFIRMED_FIELD = {
|
|
|
21
25
|
type: "boolean",
|
|
22
26
|
description: "false = pokaż podgląd bez zapisu; true = wykonaj, dopiero po zgodzie klienta w jego następnej wiadomości.",
|
|
23
27
|
};
|
|
24
|
-
const NO_LOCATION = "Aplikacja nie podała pozycji klienta, więc nie wiem, gdzie szukać. Poproś klienta, żeby włączył lokalizację w aplikacji i otworzył czat ponownie.";
|
|
25
28
|
/** Refuses every time-dependent tool while the host clock runs in the wrong zone. */
|
|
26
29
|
function zoneRefusal(context) {
|
|
27
30
|
if (!context.timeZone)
|
|
28
31
|
return null;
|
|
29
32
|
const problem = hostTimeZoneProblem(context.timeZone, context.now());
|
|
30
|
-
return problem ? err("unavailable",
|
|
33
|
+
return problem ? err("unavailable", textsOf(context).bookingOff(problem)) : null;
|
|
31
34
|
}
|
|
32
|
-
function readDay(args, key) {
|
|
35
|
+
function readDay(texts, args, key) {
|
|
33
36
|
const day = readString(args, key);
|
|
34
37
|
if (!isDay(day))
|
|
35
|
-
throw new ArgumentError(
|
|
38
|
+
throw new ArgumentError(texts.badDay(key));
|
|
36
39
|
return day;
|
|
37
40
|
}
|
|
38
|
-
function readTimeMinutes(args, key) {
|
|
41
|
+
function readTimeMinutes(texts, args, key) {
|
|
39
42
|
const minutes = minutesOf(readString(args, key));
|
|
40
43
|
if (minutes === null)
|
|
41
|
-
throw new ArgumentError(
|
|
44
|
+
throw new ArgumentError(texts.badTime(key));
|
|
42
45
|
return minutes;
|
|
43
46
|
}
|
|
44
47
|
function serviceLine(formats) {
|
|
@@ -46,9 +49,10 @@ function serviceLine(formats) {
|
|
|
46
49
|
}
|
|
47
50
|
export function searchSlotsTool(context) {
|
|
48
51
|
const { port } = context;
|
|
52
|
+
const texts = textsOf(context);
|
|
49
53
|
return {
|
|
50
54
|
name: "search_slots",
|
|
51
|
-
progress:
|
|
55
|
+
progress: texts.progress.searchSlots,
|
|
52
56
|
description: "Szuka salonów w okolicy klienta, które mają wolny termin na usługę danego typu. Bez date i time szuka „jak najszybciej” (najbliższa godzina, a gdy nic nie ma — do 24 godzin naprzód). Z date i time sprawdza okolice tej godziny (pół godziny wcześniej do godziny później). Zwraca salony z usługami (serviceId, cena, czas) i wolnymi terminami. Odległość, cenę i czas trwania dostajesz jako gotowy tekst — przepisz go bez zmian. serviceTypeId weź z find_service_type.",
|
|
53
57
|
parameters: {
|
|
54
58
|
type: "object",
|
|
@@ -67,7 +71,7 @@ export function searchSlotsTool(context) {
|
|
|
67
71
|
if (refusal)
|
|
68
72
|
return refusal;
|
|
69
73
|
if (!port.location)
|
|
70
|
-
return err("invalid_arguments",
|
|
74
|
+
return err("invalid_arguments", texts.noLocation);
|
|
71
75
|
const formats = formatsOf(context);
|
|
72
76
|
const serviceTypeId = readString(args, "serviceTypeId");
|
|
73
77
|
const radiusKm = Math.min(Math.max(readOptionalNumber(args, "radiusKm") ?? DEFAULT_RADIUS_KM, 1), MAX_RADIUS_KM);
|
|
@@ -75,47 +79,46 @@ export function searchSlotsTool(context) {
|
|
|
75
79
|
const timeText = readOptionalString(args, "time");
|
|
76
80
|
const now = context.now();
|
|
77
81
|
if ((dayText === undefined) !== (timeText === undefined)) {
|
|
78
|
-
throw new ArgumentError(
|
|
82
|
+
throw new ArgumentError(texts.dateAndTimeTogether);
|
|
79
83
|
}
|
|
80
84
|
let found;
|
|
81
85
|
let lead;
|
|
82
86
|
if (dayText === undefined) {
|
|
83
87
|
found = await port.searchAsap(serviceTypeId, radiusKm, now);
|
|
84
|
-
lead = found.phase === "extended" ?
|
|
88
|
+
lead = found.phase === "extended" ? texts.leadExtended : texts.leadSoon;
|
|
85
89
|
}
|
|
86
90
|
else {
|
|
87
|
-
const day = readDay(args, "date");
|
|
88
|
-
const at = readTimeMinutes(args, "time");
|
|
91
|
+
const day = readDay(texts, args, "date");
|
|
92
|
+
const at = readTimeMinutes(texts, args, "time");
|
|
89
93
|
const moments = AROUND_TIME_MINUTES.map((shift) => momentOf(day, at) + shift * 60_000)
|
|
90
94
|
// Only moments of the asked day's grid that are still ahead.
|
|
91
95
|
.filter((moment) => moment > now.getTime() && onSlotGrid(new Date(moment).getMinutes()));
|
|
92
96
|
if (moments.length === 0)
|
|
93
|
-
return err("invalid_arguments",
|
|
97
|
+
return err("invalid_arguments", texts.momentPassed);
|
|
94
98
|
found = await port.searchAtMoments(serviceTypeId, radiusKm, moments);
|
|
95
|
-
lead =
|
|
99
|
+
lead = texts.leadAround(day, timeOfMinutes(at));
|
|
96
100
|
}
|
|
97
101
|
if (found.salons.length === 0) {
|
|
98
102
|
if (found.failedProbes > 0)
|
|
99
|
-
return err("unavailable",
|
|
100
|
-
return ok(
|
|
103
|
+
return err("unavailable", texts.probesFailed);
|
|
104
|
+
return ok(texts.nothingFree(formats.distance(radiusKm)), []);
|
|
101
105
|
}
|
|
102
106
|
const ranked = [...found.salons].sort(bySoonestThenNearest).slice(0, MAX_SALONS_SHOWN);
|
|
103
107
|
const names = new Map((await port.getSalons(ranked.map((salon) => salon.salonId))).map((salon) => [salon.id, salon]));
|
|
104
108
|
const data = ranked.map((salon) => ({
|
|
105
109
|
salonId: salon.salonId,
|
|
106
|
-
name: names.get(salon.salonId)?.name ??
|
|
110
|
+
name: names.get(salon.salonId)?.name ?? texts.salonFallbackName,
|
|
107
111
|
address: names.get(salon.salonId)?.address ?? "",
|
|
108
112
|
distance: formats.distance(salon.distanceKm),
|
|
109
113
|
services: salon.services.map(serviceLine(formats)),
|
|
110
114
|
freeAt: salon.availableAt.slice(0, MAX_TIMES_SHOWN).map(localStampOf),
|
|
111
115
|
}));
|
|
112
|
-
|
|
113
|
-
return ok(`${lead} ${ranked.length} salon(y).${more} Terminy to początek wizyty; inne godziny w danym salonie sprawdź przez get_free_times.`, data);
|
|
116
|
+
return ok(texts.found(lead, ranked.length, found.salons.length), data);
|
|
114
117
|
}
|
|
115
118
|
catch (error) {
|
|
116
119
|
if (error instanceof ArgumentError)
|
|
117
120
|
return err("invalid_arguments", error.message);
|
|
118
|
-
return toToolError(error,
|
|
121
|
+
return toToolError(error, texts.searchFailed);
|
|
119
122
|
}
|
|
120
123
|
},
|
|
121
124
|
};
|
|
@@ -128,9 +131,10 @@ function bySoonestThenNearest(a, b) {
|
|
|
128
131
|
}
|
|
129
132
|
export function getSalonTool(context) {
|
|
130
133
|
const { port } = context;
|
|
134
|
+
const texts = textsOf(context);
|
|
131
135
|
return {
|
|
132
136
|
name: "get_salon",
|
|
133
|
-
progress:
|
|
137
|
+
progress: texts.progress.getSalon,
|
|
134
138
|
description: "Zwraca salon: nazwę, adres, AKTYWNE usługi z cenami (serviceId) i aktywne promocje z kodami rabatowymi. salonId weź z search_slots albo list_my_appointments.",
|
|
135
139
|
parameters: {
|
|
136
140
|
type: "object",
|
|
@@ -143,9 +147,9 @@ export function getSalonTool(context) {
|
|
|
143
147
|
const salonId = readString(args, "salonId");
|
|
144
148
|
const [salon] = await port.getSalons([salonId]);
|
|
145
149
|
if (!salon)
|
|
146
|
-
return err("not_found",
|
|
150
|
+
return err("not_found", texts.noSuchSalon);
|
|
147
151
|
const [services, promos] = await Promise.all([port.listSalonServices(salonId), port.listSalonPromos(salonId)]);
|
|
148
|
-
return ok(
|
|
152
|
+
return ok(texts.salonSummary(salon.name, salon.address, services.length, promos.length), {
|
|
149
153
|
salonId,
|
|
150
154
|
name: salon.name,
|
|
151
155
|
address: salon.address,
|
|
@@ -156,19 +160,19 @@ export function getSalonTool(context) {
|
|
|
156
160
|
catch (error) {
|
|
157
161
|
if (error instanceof ArgumentError)
|
|
158
162
|
return err("invalid_arguments", error.message);
|
|
159
|
-
return toToolError(error,
|
|
163
|
+
return toToolError(error, texts.salonFailed);
|
|
160
164
|
}
|
|
161
165
|
},
|
|
162
166
|
};
|
|
163
167
|
}
|
|
164
168
|
/** The salon's active service, or the tool error that says why not. */
|
|
165
|
-
async function findService(port, salonId, serviceId) {
|
|
169
|
+
async function findService(port, texts, salonId, serviceId) {
|
|
166
170
|
const services = await port.listSalonServices(salonId);
|
|
167
171
|
const service = services.find((candidate) => candidate.id === serviceId);
|
|
168
172
|
if (!service)
|
|
169
|
-
return err("not_found",
|
|
173
|
+
return err("not_found", texts.noSuchService);
|
|
170
174
|
if (service.workerIds.length === 0)
|
|
171
|
-
return err("not_found",
|
|
175
|
+
return err("not_found", texts.nobodyDoesService(service.name));
|
|
172
176
|
return service;
|
|
173
177
|
}
|
|
174
178
|
/** Free start minutes of a service on a day → the first worker free then (the app's rule). */
|
|
@@ -192,9 +196,10 @@ async function freeStartsByWorker(port, service, day) {
|
|
|
192
196
|
}
|
|
193
197
|
export function getFreeTimesTool(context) {
|
|
194
198
|
const { port } = context;
|
|
199
|
+
const texts = textsOf(context);
|
|
195
200
|
return {
|
|
196
201
|
name: "get_free_times",
|
|
197
|
-
progress:
|
|
202
|
+
progress: texts.progress.getFreeTimes,
|
|
198
203
|
description: "Zwraca wolne godziny rozpoczęcia jednej usługi w jednym salonie w danym dniu, jako przedziały (wizyta może zacząć się co 5 minut od from do to włącznie).",
|
|
199
204
|
parameters: {
|
|
200
205
|
type: "object",
|
|
@@ -212,33 +217,34 @@ export function getFreeTimesTool(context) {
|
|
|
212
217
|
if (refusal)
|
|
213
218
|
return refusal;
|
|
214
219
|
const salonId = readString(args, "salonId");
|
|
215
|
-
const day = readDay(args, "date");
|
|
220
|
+
const day = readDay(texts, args, "date");
|
|
216
221
|
const now = context.now();
|
|
217
222
|
if (day < localDayOf(now))
|
|
218
|
-
return err("invalid_arguments",
|
|
219
|
-
const service = await findService(port, salonId, readString(args, "serviceId"));
|
|
223
|
+
return err("invalid_arguments", texts.dayPassed);
|
|
224
|
+
const service = await findService(port, texts, salonId, readString(args, "serviceId"));
|
|
220
225
|
if ("status" in service)
|
|
221
226
|
return service;
|
|
222
227
|
const nowMinutes = day === localDayOf(now) ? now.getHours() * 60 + now.getMinutes() : -1;
|
|
223
228
|
const starts = [...(await freeStartsByWorker(port, service, day)).keys()].filter((start) => start > nowMinutes).sort((a, b) => a - b);
|
|
224
229
|
if (starts.length === 0)
|
|
225
|
-
return ok(
|
|
226
|
-
return ok(
|
|
230
|
+
return ok(texts.noFreeTimes(service.name, day), []);
|
|
231
|
+
return ok(texts.freeTimes(service.name, day, formatsOf(context).duration(service.durationMinutes), formatsOf(context).price(service.pricePln)), foldStartRanges(starts));
|
|
227
232
|
}
|
|
228
233
|
catch (error) {
|
|
229
234
|
if (error instanceof ArgumentError)
|
|
230
235
|
return err("invalid_arguments", error.message);
|
|
231
|
-
return toToolError(error,
|
|
236
|
+
return toToolError(error, texts.freeTimesFailed);
|
|
232
237
|
}
|
|
233
238
|
},
|
|
234
239
|
};
|
|
235
240
|
}
|
|
236
241
|
export function bookAppointmentTool(context, confirmations) {
|
|
237
242
|
const { port } = context;
|
|
243
|
+
const texts = textsOf(context);
|
|
238
244
|
const gate = writeGate("book_appointment", confirmations);
|
|
239
245
|
return {
|
|
240
246
|
name: "book_appointment",
|
|
241
|
-
progress:
|
|
247
|
+
progress: texts.progress.book,
|
|
242
248
|
description: "Rezerwuje wizytę w imieniu klienta. Najpierw confirmed=false: dostaniesz podsumowanie do pokazania klientowi. Rezerwacja następuje dopiero przy confirmed=true po jego zgodzie. Pracownika dobiera system.",
|
|
243
249
|
parameters: gate.parameters({
|
|
244
250
|
type: "object",
|
|
@@ -261,12 +267,12 @@ export function bookAppointmentTool(context, confirmations) {
|
|
|
261
267
|
return zone;
|
|
262
268
|
const salonId = readString(args, "salonId");
|
|
263
269
|
const serviceId = readString(args, "serviceId");
|
|
264
|
-
const day = readDay(args, "date");
|
|
265
|
-
const startMinutes = readTimeMinutes(args, "time");
|
|
270
|
+
const day = readDay(texts, args, "date");
|
|
271
|
+
const startMinutes = readTimeMinutes(texts, args, "time");
|
|
266
272
|
const discountCode = readOptionalString(args, "discountCode");
|
|
267
273
|
const notes = readOptionalString(args, "notes");
|
|
268
274
|
if (!onSlotGrid(startMinutes))
|
|
269
|
-
throw new ArgumentError(
|
|
275
|
+
throw new ArgumentError(texts.offGrid);
|
|
270
276
|
// The echo is the change in its normal form, whichever spelling the model used.
|
|
271
277
|
const echo = { salonId, serviceId, date: day, time: timeOfMinutes(startMinutes) };
|
|
272
278
|
if (discountCode !== undefined)
|
|
@@ -278,45 +284,51 @@ export function bookAppointmentTool(context, confirmations) {
|
|
|
278
284
|
return refusal;
|
|
279
285
|
const startAt = momentOf(day, startMinutes);
|
|
280
286
|
if (startAt <= context.now().getTime())
|
|
281
|
-
return err("invalid_arguments",
|
|
282
|
-
const service = await findService(port, salonId, serviceId);
|
|
287
|
+
return err("invalid_arguments", texts.momentPassed);
|
|
288
|
+
const service = await findService(port, texts, salonId, serviceId);
|
|
283
289
|
if ("status" in service)
|
|
284
290
|
return service;
|
|
285
291
|
// Checked on BOTH calls: a refused booking still spends the customer's daily quota,
|
|
286
292
|
// and the slot may have gone between the preview and the "yes".
|
|
287
293
|
const workerId = (await freeStartsByWorker(port, service, day)).get(startMinutes);
|
|
288
294
|
if (workerId === undefined) {
|
|
289
|
-
return err("not_found",
|
|
295
|
+
return err("not_found", texts.slotGone(day, timeOfMinutes(startMinutes)));
|
|
290
296
|
}
|
|
291
297
|
const formats = formatsOf(context);
|
|
292
298
|
const [salon] = await port.getSalons([salonId]);
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
299
|
+
const what = texts.visit({
|
|
300
|
+
service: service.name,
|
|
301
|
+
where: salon ? `${salon.name} (${salon.address})` : texts.somewhere,
|
|
302
|
+
day,
|
|
303
|
+
time: timeOfMinutes(startMinutes),
|
|
304
|
+
duration: formats.duration(service.durationMinutes),
|
|
305
|
+
price: formats.price(service.pricePln),
|
|
306
|
+
code: discountCode === undefined ? "" : texts.withDiscountCode(discountCode),
|
|
307
|
+
});
|
|
296
308
|
if (!readBoolean(args, "confirmed"))
|
|
297
|
-
return gate.preview(
|
|
309
|
+
return gate.preview(texts.willBook(what), echo);
|
|
298
310
|
const refused = gate.redeem(args, echo);
|
|
299
311
|
if (refused)
|
|
300
312
|
return refused;
|
|
301
313
|
const appointmentId = await port.book({ salonId, serviceId, workerId, startAt, notes: notes ?? "", discountCode });
|
|
302
|
-
return ok(
|
|
314
|
+
return ok(texts.booked(what, await pricePaidNote(port, appointmentId, service.pricePln, formats, texts)), { appointmentId });
|
|
303
315
|
}
|
|
304
316
|
catch (error) {
|
|
305
317
|
if (error instanceof ArgumentError)
|
|
306
318
|
return err("invalid_arguments", error.message);
|
|
307
|
-
return toToolError(error,
|
|
319
|
+
return toToolError(error, texts.bookFailed);
|
|
308
320
|
}
|
|
309
321
|
},
|
|
310
322
|
};
|
|
311
323
|
}
|
|
312
324
|
/** What the customer really pays, when a discount code changed it. Never fails a booking that is
|
|
313
325
|
* already made — and a query right after an update may not see it yet. */
|
|
314
|
-
async function pricePaidNote(port, appointmentId, listPricePln, formats) {
|
|
326
|
+
async function pricePaidNote(port, appointmentId, listPricePln, formats, texts) {
|
|
315
327
|
try {
|
|
316
328
|
const booked = (await port.listMyAppointments()).find((appointment) => appointment.id === appointmentId);
|
|
317
329
|
if (!booked || booked.pricePln === listPricePln)
|
|
318
330
|
return "";
|
|
319
|
-
return
|
|
331
|
+
return texts.pricePaid(formats.price(booked.pricePln));
|
|
320
332
|
}
|
|
321
333
|
catch {
|
|
322
334
|
return "";
|
|
@@ -341,16 +353,20 @@ async function upcomingAppointments(port, now) {
|
|
|
341
353
|
}));
|
|
342
354
|
return upcoming.map((appointment) => ({ ...appointment, salon: salons.get(appointment.salonId), serviceName: serviceNames.get(appointment.serviceId) }));
|
|
343
355
|
}
|
|
344
|
-
function describeAppointment(appointment) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
function describeAppointment(texts, appointment) {
|
|
357
|
+
return texts.appointment({
|
|
358
|
+
what: appointment.serviceName ? `„${appointment.serviceName}”` : texts.visitFallbackName,
|
|
359
|
+
where: appointment.salon ? texts.at(`${appointment.salon.name} (${appointment.salon.address})`) : "",
|
|
360
|
+
day: localDayOf(appointment.startAt),
|
|
361
|
+
time: localTimeOf(appointment.startAt),
|
|
362
|
+
});
|
|
348
363
|
}
|
|
349
364
|
export function listMyAppointmentsTool(context) {
|
|
350
365
|
const { port } = context;
|
|
366
|
+
const texts = textsOf(context);
|
|
351
367
|
return {
|
|
352
368
|
name: "list_my_appointments",
|
|
353
|
-
progress:
|
|
369
|
+
progress: texts.progress.listAppointments,
|
|
354
370
|
description: "Zwraca nadchodzące wizyty klienta (potwierdzone, jeszcze nie odbyte): id wizyty, salon, usługę, termin i cenę.",
|
|
355
371
|
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
356
372
|
execute: async () => {
|
|
@@ -360,12 +376,12 @@ export function listMyAppointmentsTool(context) {
|
|
|
360
376
|
return zone;
|
|
361
377
|
const upcoming = await upcomingAppointments(port, context.now());
|
|
362
378
|
if (upcoming.length === 0)
|
|
363
|
-
return ok(
|
|
379
|
+
return ok(texts.noUpcoming, []);
|
|
364
380
|
const formats = formatsOf(context);
|
|
365
|
-
return ok(
|
|
381
|
+
return ok(texts.upcoming(upcoming.length), upcoming.map((appointment) => ({
|
|
366
382
|
appointmentId: appointment.id,
|
|
367
383
|
salonId: appointment.salonId,
|
|
368
|
-
salonName: appointment.salon?.name ??
|
|
384
|
+
salonName: appointment.salon?.name ?? texts.salonFallbackName,
|
|
369
385
|
address: appointment.salon?.address ?? "",
|
|
370
386
|
serviceName: appointment.serviceName ?? "",
|
|
371
387
|
start: localStampOf(appointment.startAt),
|
|
@@ -374,17 +390,18 @@ export function listMyAppointmentsTool(context) {
|
|
|
374
390
|
})));
|
|
375
391
|
}
|
|
376
392
|
catch (error) {
|
|
377
|
-
return toToolError(error,
|
|
393
|
+
return toToolError(error, texts.listFailed);
|
|
378
394
|
}
|
|
379
395
|
},
|
|
380
396
|
};
|
|
381
397
|
}
|
|
382
398
|
export function cancelAppointmentTool(context, confirmations) {
|
|
383
399
|
const { port } = context;
|
|
400
|
+
const texts = textsOf(context);
|
|
384
401
|
const gate = writeGate("cancel_appointment", confirmations);
|
|
385
402
|
return {
|
|
386
403
|
name: "cancel_appointment",
|
|
387
|
-
progress:
|
|
404
|
+
progress: texts.progress.cancel,
|
|
388
405
|
description: "Odwołuje nadchodzącą wizytę klienta. Najpierw confirmed=false (podsumowanie dla klienta), potem confirmed=true po jego zgodzie. appointmentId weź z list_my_appointments.",
|
|
389
406
|
parameters: gate.parameters({
|
|
390
407
|
type: "object",
|
|
@@ -407,19 +424,19 @@ export function cancelAppointmentTool(context, confirmations) {
|
|
|
407
424
|
return refusal;
|
|
408
425
|
const appointment = (await upcomingAppointments(port, context.now())).find((candidate) => candidate.id === appointmentId);
|
|
409
426
|
if (!appointment)
|
|
410
|
-
return err("not_found",
|
|
427
|
+
return err("not_found", texts.noSuchAppointment);
|
|
411
428
|
if (!readBoolean(args, "confirmed"))
|
|
412
|
-
return gate.preview(
|
|
429
|
+
return gate.preview(texts.willCancel(describeAppointment(texts, appointment)), echo);
|
|
413
430
|
const refused = gate.redeem(args, echo);
|
|
414
431
|
if (refused)
|
|
415
432
|
return refused;
|
|
416
433
|
await port.cancel(appointmentId);
|
|
417
|
-
return ok(
|
|
434
|
+
return ok(texts.cancelled(describeAppointment(texts, appointment)), { appointmentId });
|
|
418
435
|
}
|
|
419
436
|
catch (error) {
|
|
420
437
|
if (error instanceof ArgumentError)
|
|
421
438
|
return err("invalid_arguments", error.message);
|
|
422
|
-
return toToolError(error,
|
|
439
|
+
return toToolError(error, texts.cancelFailed);
|
|
423
440
|
}
|
|
424
441
|
},
|
|
425
442
|
};
|
|
@@ -30,6 +30,7 @@ export declare class IcSalonCorePort implements SalonCorePort {
|
|
|
30
30
|
private readonly availability;
|
|
31
31
|
private readonly assets;
|
|
32
32
|
private readonly promo;
|
|
33
|
+
private catalog;
|
|
33
34
|
private readonly discountCodes;
|
|
34
35
|
constructor(options: IcSalonCorePortOptions);
|
|
35
36
|
listServices(): Promise<ServiceView[]>;
|
|
@@ -38,6 +39,9 @@ export declare class IcSalonCorePort implements SalonCorePort {
|
|
|
38
39
|
removeService(serviceId: string): Promise<void>;
|
|
39
40
|
listWorkers(): Promise<WorkerView[]>;
|
|
40
41
|
findServiceTypes(query: string): Promise<ServiceTypeView[]>;
|
|
42
|
+
/** The catalogue changes when a superadmin edits it — rarely — while one Booksy import searches
|
|
43
|
+
* it dozens of times. One canister query a minute serves them all. */
|
|
44
|
+
private serviceTypesCatalog;
|
|
41
45
|
getWeeklyHours(workerId: string, day: number): Promise<TimeRange[]>;
|
|
42
46
|
setWeeklyHours(workerId: string, day: number, ranges: TimeRange[]): Promise<void>;
|
|
43
47
|
getDailyBusy(workerId: string, date: string): Promise<TimeRange[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ic-salon-core-port.d.ts","sourceRoot":"","sources":["../src/ic-salon-core-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAcpD,OAAO,KAAK,EACR,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,eAAe,EACf,WAAW,EACX,aAAa,EACb,SAAS,EACT,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,UAAU,EACb,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ic-salon-core-port.d.ts","sourceRoot":"","sources":["../src/ic-salon-core-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Principal } from "@icp-sdk/core/principal";
|
|
2
2
|
import { AssetsActor, AvailabilityActor, DiscountCodesActor, filterServiceTypes, HrActor, PromoActor, promoMusicOf, SalonServicesActor, ServiceTypesActor, } from "@jsm-mit/sultana-core-motoko-package";
|
|
3
|
+
const SERVICE_TYPES_CATALOG_TTL_MS = 60_000;
|
|
3
4
|
/**
|
|
4
5
|
* The port over the published canister wrapper. Conversions live here and nowhere else: the
|
|
5
6
|
* canister speaks `bigint` and `Principal`, the tools speak numbers and strings.
|
|
@@ -14,6 +15,7 @@ export class IcSalonCorePort {
|
|
|
14
15
|
availability;
|
|
15
16
|
assets;
|
|
16
17
|
promo;
|
|
18
|
+
catalog = null;
|
|
17
19
|
discountCodes;
|
|
18
20
|
constructor(options) {
|
|
19
21
|
this.salonId = options.salonId;
|
|
@@ -62,13 +64,29 @@ export class IcSalonCorePort {
|
|
|
62
64
|
}));
|
|
63
65
|
}
|
|
64
66
|
async findServiceTypes(query) {
|
|
65
|
-
const catalog = await this.
|
|
67
|
+
const catalog = await this.serviceTypesCatalog();
|
|
66
68
|
return filterServiceTypes(catalog, query).map((entry) => ({
|
|
67
69
|
id: entry.id,
|
|
68
70
|
label: entry.text,
|
|
69
71
|
category: entry.category,
|
|
70
72
|
}));
|
|
71
73
|
}
|
|
74
|
+
/** The catalogue changes when a superadmin edits it — rarely — while one Booksy import searches
|
|
75
|
+
* it dozens of times. One canister query a minute serves them all. */
|
|
76
|
+
serviceTypesCatalog() {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
if (!this.catalog || now - this.catalog.readAt > SERVICE_TYPES_CATALOG_TTL_MS) {
|
|
79
|
+
const reply = this.serviceTypes.getServiceTypesCatalogAsyncUnsafe(this.language);
|
|
80
|
+
const entry = { readAt: now, reply };
|
|
81
|
+
// A failed read is not kept: the next search asks the canister again.
|
|
82
|
+
reply.catch(() => {
|
|
83
|
+
if (this.catalog === entry)
|
|
84
|
+
this.catalog = null;
|
|
85
|
+
});
|
|
86
|
+
this.catalog = entry;
|
|
87
|
+
}
|
|
88
|
+
return this.catalog.reply;
|
|
89
|
+
}
|
|
72
90
|
async getWeeklyHours(workerId, day) {
|
|
73
91
|
return this.availability.getDayFromWeeklyTemplateAsyncUnsafe(BigInt(day), this.salonId, Principal.fromText(workerId));
|
|
74
92
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
export { ConfirmationLedger, DEFAULT_CONFIRMATION_TTL_MS } from "./confirmations.js";
|
|
2
2
|
export type { ConfirmationLedgerOptions, ConfirmationRefusal, RedeemResult, TurnConfirmations, } from "./confirmations.js";
|
|
3
|
+
export { BooksyReadError } from "./booksy/fetch-page.js";
|
|
4
|
+
export type { FetchFn } from "./booksy/fetch-page.js";
|
|
5
|
+
export type { BooksyImage, BooksyImageKind, BooksyOpenHours, BooksyPriceType, BooksySalon, BooksyService, BooksyServiceCategory, BooksyStaffer, BooksyVariant, } from "./booksy/parse-salon.js";
|
|
6
|
+
export { parseBooksySalon } from "./booksy/parse-salon.js";
|
|
7
|
+
export { toSultanaDraft } from "./booksy/to-sultana.js";
|
|
8
|
+
export type { SultanaDraftDay, SultanaPhotoDraft, SultanaSalonDraft, SultanaSalonDraftProfile, SultanaServiceDraft, SultanaWorkerDraft, } from "./booksy/to-sultana.js";
|
|
9
|
+
export { createBooksyImportTools, createBooksyTools } from "./booksy/import-tools.js";
|
|
10
|
+
export type { CreateBooksyImportToolsOptions, CreateBooksyToolsOptions } from "./booksy/import-tools.js";
|
|
11
|
+
export { BooksyProfileReader, createBooksyReaderTools, readBooksySalonDraft } from "./booksy/tools.js";
|
|
12
|
+
export type { BooksyPhotoFile, BooksyPhotoSink, CreateBooksyReaderToolsOptions } from "./booksy/tools.js";
|
|
3
13
|
export { createSalonReadTools, createSalonReadToolsFromPort, createSalonTools, createSalonToolsFromPort, } from "./create-salon-tools.js";
|
|
4
14
|
export type { CreateSalonReadToolsOptions, CreateSalonToolsFromPortOptions, CreateSalonToolsOptions, } from "./create-salon-tools.js";
|
|
5
15
|
export { createCustomerReadTools, createCustomerReadToolsFromPort, createCustomerTools, createCustomerToolsFromPort, DEFAULT_SALON_TIME_ZONE, } from "./customer/create-customer-tools.js";
|
|
@@ -9,11 +19,13 @@ export { IcCustomerCorePort } from "./customer/ic-customer-core-port.js";
|
|
|
9
19
|
export type { IcCustomerCorePortOptions } from "./customer/ic-customer-core-port.js";
|
|
10
20
|
export { customerFormats, DEFAULT_CUSTOMER_LOCALE } from "./customer/format.js";
|
|
11
21
|
export type { CustomerFormats } from "./customer/format.js";
|
|
12
|
-
export { CUSTOMER_AGENT_PERSONA_PL } from "./customer/persona.js";
|
|
22
|
+
export { CUSTOMER_AGENT_PERSONA_PL, customerAgentPersona } from "./customer/persona.js";
|
|
23
|
+
export { customerLanguageOf, customerTexts } from "./customer/texts.js";
|
|
24
|
+
export type { CustomerTexts } from "./customer/texts.js";
|
|
13
25
|
export { hostTimeZoneProblem } from "./customer/time.js";
|
|
14
26
|
export { IcSalonCorePort } from "./ic-salon-core-port.js";
|
|
15
27
|
export type { IcSalonCorePortOptions } from "./ic-salon-core-port.js";
|
|
16
|
-
export { SALON_AGENT_PERSONA_PL, SALON_READ_PERSONA_PL } from "./persona.js";
|
|
28
|
+
export { SALON_AGENT_PERSONA_PL, SALON_BOOKSY_PERSONA_PL, SALON_READ_PERSONA_PL } from "./persona.js";
|
|
17
29
|
export type { DiscountCodeInput, DiscountCodeView, MediaView, PromoView, PromoWriteInput, SalonAccess, SalonCorePort, SalonPromoPort, ServiceTypeView, ServiceView, ServiceWriteInput, SalonSchedulePort, TimeRange, WorkerView, } from "./salon-core-port.js";
|
|
18
30
|
export { err, needsConfirmation, ok } from "./types.js";
|
|
19
31
|
export type { AgentTool, ToolErrorCode, ToolParametersSchema, ToolResult } from "./types.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACrF,YAAY,EACR,yBAAyB,EACzB,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACH,oBAAoB,EACpB,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,GAC3B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACR,2BAA2B,EAC3B,+BAA+B,EAC/B,uBAAuB,GAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACH,uBAAuB,EACvB,+BAA+B,EAC/B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,GAC1B,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACR,8BAA8B,EAC9B,kCAAkC,EAClC,0BAA0B,GAC7B,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACR,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,SAAS,EACT,cAAc,GACjB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAChF,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export { ConfirmationLedger, DEFAULT_CONFIRMATION_TTL_MS } from "./confirmations.js";
|
|
2
|
+
export { BooksyReadError } from "./booksy/fetch-page.js";
|
|
3
|
+
export { parseBooksySalon } from "./booksy/parse-salon.js";
|
|
4
|
+
export { toSultanaDraft } from "./booksy/to-sultana.js";
|
|
5
|
+
export { createBooksyImportTools, createBooksyTools } from "./booksy/import-tools.js";
|
|
6
|
+
export { BooksyProfileReader, createBooksyReaderTools, readBooksySalonDraft } from "./booksy/tools.js";
|
|
2
7
|
export { createSalonReadTools, createSalonReadToolsFromPort, createSalonTools, createSalonToolsFromPort, } from "./create-salon-tools.js";
|
|
3
8
|
export { createCustomerReadTools, createCustomerReadToolsFromPort, createCustomerTools, createCustomerToolsFromPort, DEFAULT_SALON_TIME_ZONE, } from "./customer/create-customer-tools.js";
|
|
4
9
|
export { IcCustomerCorePort } from "./customer/ic-customer-core-port.js";
|
|
5
10
|
export { customerFormats, DEFAULT_CUSTOMER_LOCALE } from "./customer/format.js";
|
|
6
|
-
export { CUSTOMER_AGENT_PERSONA_PL } from "./customer/persona.js";
|
|
11
|
+
export { CUSTOMER_AGENT_PERSONA_PL, customerAgentPersona } from "./customer/persona.js";
|
|
12
|
+
export { customerLanguageOf, customerTexts } from "./customer/texts.js";
|
|
7
13
|
export { hostTimeZoneProblem } from "./customer/time.js";
|
|
8
14
|
export { IcSalonCorePort } from "./ic-salon-core-port.js";
|
|
9
|
-
export { SALON_AGENT_PERSONA_PL, SALON_READ_PERSONA_PL } from "./persona.js";
|
|
15
|
+
export { SALON_AGENT_PERSONA_PL, SALON_BOOKSY_PERSONA_PL, SALON_READ_PERSONA_PL } from "./persona.js";
|
|
10
16
|
export { err, needsConfirmation, ok } from "./types.js";
|
package/dist/persona.d.ts
CHANGED
|
@@ -18,4 +18,9 @@ export declare const SALON_AGENT_PERSONA_PL = "Jeste\u015B asystentem w\u0142a\u
|
|
|
18
18
|
* promising a change or reporting a hidden service as missing.
|
|
19
19
|
*/
|
|
20
20
|
export declare const SALON_READ_PERSONA_PL = "Jeste\u015B asystentem w\u0142a\u015Bciciela salonu w aplikacji Sultana.\nRozmawiasz po polsku, kr\u00F3tko i konkretnie. Odpowiadasz na pytania o salon za pomoc\u0105 narz\u0119dzi.\n\nZasady pracy:\n- Zanim odpowiesz o us\u0142ugach, promocjach albo typach us\u0142ug, ZAWSZE wywo\u0142aj narz\u0119dzie\n (list_services, list_promos, find_service_type). Nigdy nie zgaduj.\n- Widzisz tylko AKTYWNE us\u0142ugi i AKTYWNE promocje \u2014 to, co widz\u0105 klientki.\n Powiedz o tym, gdy ma to znaczenie, np. gdy w\u0142a\u015Bciciel pyta o us\u0142ug\u0119, kt\u00F3rej nie ma na li\u015Bcie:\n mo\u017Ce by\u0107 wy\u0142\u0105czona.\n- Nie mo\u017Cesz jeszcze niczego zmienia\u0107. Gdy w\u0142a\u015Bciciel prosi o dodanie, zmian\u0119 albo usuni\u0119cie,\n powiedz, \u017Ce edycja przez czat b\u0119dzie dost\u0119pna wkr\u00F3tce, i wska\u017C stron\u0119 w panelu:\n \u201EUs\u0142ugi\u201D dla us\u0142ug, \u201EPromocje\u201D dla promocji.\n- Ceny podawaj w pe\u0142nych z\u0142otych, czas trwania w minutach.\n- Je\u015Bli narz\u0119dzie zwr\u00F3ci status \"error\", przeczytaj summary i powiedz w\u0142a\u015Bcicielowi,\n co posz\u0142o nie tak. Nie powtarzaj tego samego wywo\u0142ania w k\u00F3\u0142ko.\n- Nie obiecuj zmian, kt\u00F3rych nie mo\u017Cesz wykona\u0107.";
|
|
21
|
+
/**
|
|
22
|
+
* The rules for the Booksy set (`createBooksyTools`) — appended to `SALON_AGENT_PERSONA_PL` by a
|
|
23
|
+
* host that hands those tools to the agent. Separate, because a host may run without them.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SALON_BOOKSY_PERSONA_PL = "Kopiowanie salonu z Booksy:\n- Gdy w\u0142a\u015Bcicielka chce przenie\u015B\u0107 co\u015B z Booksy, popro\u015B o adres jej profilu\n (https://booksy.com/pl-pl/<numer>_<nazwa>\u2026), je\u015Bli go nie poda\u0142a.\n- read_booksy_salon i read_booksy_services tylko czytaj\u0105 profil \u2014 niczego nie zapisuj\u0105.\n- Us\u0142ugi kopiuj WY\u0141\u0104CZNIE przez import_booksy_services (wszystkie naraz, jedno potwierdzenie).\n Nie przepisuj ich pojedynczo przez add_service.\n- Podgl\u0105d importu poka\u017C w\u0142a\u015Bcicielce w ca\u0142o\u015Bci, razem z uwagami pod list\u0105. Poprawki\n (\u201Epomi\u0144 X\u201D, \u201EX to inny typ\u201D) podaj w overrides i poka\u017C nowy podgl\u0105d.\n- Gdy import wypisze us\u0142ugi, kt\u00F3rym nie dobra\u0142 typu, dobierz typy przez find_service_type,\n podaj je w overrides i poka\u017C nowy podgl\u0105d.\n- Kopiowania pracownik\u00F3w jeszcze nie ma: wywo\u0142aj import_booksy_workers i przeka\u017C, co odpowie.\n- Nazwy, adresu, opisu i godzin otwarcia salonu nie da si\u0119 jeszcze zmieni\u0107 przez czat \u2014 powiedz to\n wprost. Nie wymy\u015Blaj innego powodu.";
|
|
21
26
|
//# sourceMappingURL=persona.d.ts.map
|
package/dist/persona.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persona.d.ts","sourceRoot":"","sources":["../src/persona.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,i2EAyBoB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,6wCAee,CAAC"}
|
|
1
|
+
{"version":3,"file":"persona.d.ts","sourceRoot":"","sources":["../src/persona.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,i2EAyBoB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,6wCAee,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,2nCAYE,CAAC"}
|
package/dist/persona.js
CHANGED
|
@@ -58,3 +58,20 @@ Zasady pracy:
|
|
|
58
58
|
- Jeśli narzędzie zwróci status "error", przeczytaj summary i powiedz właścicielowi,
|
|
59
59
|
co poszło nie tak. Nie powtarzaj tego samego wywołania w kółko.
|
|
60
60
|
- Nie obiecuj zmian, których nie możesz wykonać.`;
|
|
61
|
+
/**
|
|
62
|
+
* The rules for the Booksy set (`createBooksyTools`) — appended to `SALON_AGENT_PERSONA_PL` by a
|
|
63
|
+
* host that hands those tools to the agent. Separate, because a host may run without them.
|
|
64
|
+
*/
|
|
65
|
+
export const SALON_BOOKSY_PERSONA_PL = `Kopiowanie salonu z Booksy:
|
|
66
|
+
- Gdy właścicielka chce przenieść coś z Booksy, poproś o adres jej profilu
|
|
67
|
+
(https://booksy.com/pl-pl/<numer>_<nazwa>…), jeśli go nie podała.
|
|
68
|
+
- read_booksy_salon i read_booksy_services tylko czytają profil — niczego nie zapisują.
|
|
69
|
+
- Usługi kopiuj WYŁĄCZNIE przez import_booksy_services (wszystkie naraz, jedno potwierdzenie).
|
|
70
|
+
Nie przepisuj ich pojedynczo przez add_service.
|
|
71
|
+
- Podgląd importu pokaż właścicielce w całości, razem z uwagami pod listą. Poprawki
|
|
72
|
+
(„pomiń X”, „X to inny typ”) podaj w overrides i pokaż nowy podgląd.
|
|
73
|
+
- Gdy import wypisze usługi, którym nie dobrał typu, dobierz typy przez find_service_type,
|
|
74
|
+
podaj je w overrides i pokaż nowy podgląd.
|
|
75
|
+
- Kopiowania pracowników jeszcze nie ma: wywołaj import_booksy_workers i przekaż, co odpowie.
|
|
76
|
+
- Nazwy, adresu, opisu i godzin otwarcia salonu nie da się jeszcze zmienić przez czat — powiedz to
|
|
77
|
+
wprost. Nie wymyślaj innego powodu.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsm-mit/sultana-agent-tools-package",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Agent tool layer for Sultana: framework-agnostic tools over the Sultana core canister wrapper — a salon owner's set (services, promos, schedule) and a customer's set (search, free times, booking), each with a read-only set that needs no identity.",
|
|
5
5
|
"homepage": "https://github.com/JSM-Sultana/sultana-agent-tools-package#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"prepare": "npm run build",
|
|
33
33
|
"test-tools": "npx tsx --env-file-if-exists=.env --test tests/integration/test-salon-tools.ts",
|
|
34
34
|
"test-customer-tools": "npx tsx --env-file-if-exists=.env --test tests/integration/test-customer-tools.ts",
|
|
35
|
+
"booksy": "npx tsx sandbox/booksy.ts",
|
|
35
36
|
"whoami": "npx tsx --env-file-if-exists=.env sandbox/whoami.ts",
|
|
36
37
|
"publish-public": "bash scripts/publish-public.sh"
|
|
37
38
|
},
|