@kevin5251984/guild 0.2.17 → 0.2.19

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.
@@ -840,23 +840,27 @@ a.btn:active { transform: scale(var(--press)); }
840
840
  color: var(--text);
841
841
  font-size: 0.92rem;
842
842
  }
843
- .aux-head-row,
844
- .aux-row {
843
+ #aux {
845
844
  display: grid;
846
- grid-template-columns: minmax(8.5rem, 0.85fr) minmax(0, 1.5fr) auto;
847
- gap: 0.55rem 1rem;
845
+ grid-template-columns: 10.5rem minmax(0, 1fr) max-content;
846
+ column-gap: 1rem;
848
847
  align-items: center;
849
- padding: 0.8rem 0;
850
- border-top: 1px solid var(--line);
851
848
  }
852
- .aux-head-row {
853
- border-top: 0;
849
+ .aux-head-row,
850
+ .aux-row { display: contents; }
851
+ .aux-head-row > span {
854
852
  padding: 0 0 0.45rem;
855
853
  font-size: 0.7rem;
856
854
  letter-spacing: 0.07em;
857
855
  text-transform: uppercase;
858
856
  color: #8b8b8b;
859
857
  }
858
+ .aux-task,
859
+ .aux-now,
860
+ .aux-act {
861
+ padding: 0.8rem 0;
862
+ border-top: 1px solid var(--line);
863
+ }
860
864
  .aux-task strong { display: block; font-size: 0.95rem; }
861
865
  .aux-row .tag {
862
866
  display: inline-block;
@@ -867,10 +871,12 @@ a.btn:active { transform: scale(var(--press)); }
867
871
  color: #aaa;
868
872
  font-size: 0.72rem;
869
873
  }
874
+ .aux-now { min-width: 0; }
870
875
  .aux-now b {
871
876
  display: block;
872
877
  font-weight: 600;
873
878
  color: var(--text);
879
+ overflow-wrap: break-word;
874
880
  }
875
881
  .aux-now .id {
876
882
  display: block;
@@ -878,22 +884,37 @@ a.btn:active { transform: scale(var(--press)); }
878
884
  color: #8b8b8b;
879
885
  font-size: 0.75rem;
880
886
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
887
+ overflow-wrap: anywhere;
881
888
  }
882
889
  .aux-act {
883
890
  display: flex;
884
891
  align-items: center;
885
892
  justify-content: flex-end;
886
893
  gap: 0.55rem;
887
- flex-wrap: wrap;
894
+ flex-wrap: nowrap;
888
895
  }
889
896
  .aux-act .badge { margin-left: 0; }
897
+ .aux-act .linkish { margin: 0; }
890
898
  @media (max-width: 720px) {
899
+ #aux { display: block; }
891
900
  .aux-head-row { display: none; }
892
901
  .aux-row {
902
+ display: grid;
893
903
  grid-template-columns: 1fr;
894
904
  gap: 0.35rem;
905
+ padding: 0.8rem 0;
906
+ border-top: 1px solid var(--line);
907
+ }
908
+ .aux-task,
909
+ .aux-now,
910
+ .aux-act {
911
+ padding: 0;
912
+ border-top: 0;
913
+ }
914
+ .aux-act {
915
+ justify-content: flex-start;
916
+ flex-wrap: wrap;
895
917
  }
896
- .aux-act { justify-content: flex-start; }
897
918
  }
898
919
  .linkish {
899
920
  background: none;
@@ -912,6 +933,38 @@ a.btn:active { transform: scale(var(--press)); }
912
933
  border-radius: 10px;
913
934
  letter-spacing: 0.08em;
914
935
  }
936
+ .model-head {
937
+ display: flex;
938
+ align-items: center;
939
+ justify-content: flex-start;
940
+ gap: 0.7rem;
941
+ margin: 0.65rem 0 0.15rem;
942
+ }
943
+ .model-head label { margin: 0; }
944
+ .model-head .sync-free {
945
+ margin: 0;
946
+ padding: 0.45rem 1.05rem;
947
+ min-height: 2.2rem;
948
+ min-width: 5.6rem;
949
+ border: 0;
950
+ border-radius: 999px;
951
+ background: var(--paper);
952
+ color: var(--ink);
953
+ font-weight: 650;
954
+ letter-spacing: 0.03em;
955
+ display: inline-flex;
956
+ align-items: center;
957
+ justify-content: center;
958
+ gap: 0.35rem;
959
+ }
960
+ .model-head .sync-free .sync-ico {
961
+ font-size: 1.1rem;
962
+ line-height: 1;
963
+ }
964
+ .model-head .sync-free:hover:not(:disabled) {
965
+ background: color-mix(in srgb, var(--paper) 88%, white);
966
+ color: var(--ink);
967
+ }
915
968
  .model-row {
916
969
  display: grid;
917
970
  grid-template-columns: 1fr 1fr auto;
@@ -919,6 +972,10 @@ a.btn:active { transform: scale(var(--press)); }
919
972
  margin-top: 0.4rem;
920
973
  align-items: center;
921
974
  }
975
+ .model-row:has(.probe-flag) {
976
+ grid-template-columns: 1fr 1fr auto auto;
977
+ }
978
+ .model-row .probe-flag { margin: 0; }
922
979
  .model-row button { margin: 0; min-width: 2.2rem; padding: 0.4rem 0.55rem; }
923
980
  .row-2 { display: grid; grid-template-columns: 1fr; gap: 0.6rem; }
924
981
  @media (min-width: 720px) {
@@ -1032,6 +1089,12 @@ dialog.pick.add-provider { width: min(32rem, 92vw); }
1032
1089
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1033
1090
  letter-spacing: 0.02em;
1034
1091
  }
1092
+ .provider-card input[readonly],
1093
+ .provider-card select:disabled {
1094
+ opacity: 0.72;
1095
+ cursor: default;
1096
+ color: var(--muted);
1097
+ }
1035
1098
  .card-head {
1036
1099
  display: flex;
1037
1100
  justify-content: space-between;
@@ -1084,15 +1147,28 @@ body.app.bar-page .shell {
1084
1147
  padding: 0 0 0.4rem;
1085
1148
  }
1086
1149
  .rpg-screen {
1150
+ display: grid;
1151
+ grid-template-rows: auto auto;
1087
1152
  position: relative;
1088
- aspect-ratio: 16 / 9;
1089
1153
  overflow: hidden;
1090
1154
  background: #000;
1091
1155
  border: 4px solid #0a0a12;
1092
1156
  box-shadow: 8px 8px 0 #000;
1093
1157
  image-rendering: pixelated;
1094
1158
  }
1095
- .rpg-scene { position: absolute; inset: 0; }
1159
+ body.bar-page .rpg-screen {
1160
+ background: #1a1410;
1161
+ border-color: #1a1410;
1162
+ box-shadow: 8px 8px 0 #1a1410;
1163
+ }
1164
+ .rpg-scene {
1165
+ grid-row: 1;
1166
+ grid-column: 1;
1167
+ position: relative;
1168
+ aspect-ratio: 16 / 9;
1169
+ min-height: 0;
1170
+ overflow: hidden;
1171
+ }
1096
1172
  .rpg-scene[hidden] { display: none; }
1097
1173
  .rpg-bg {
1098
1174
  display: block;
@@ -1213,27 +1289,115 @@ body.app.bar-page .shell {
1213
1289
  background: #2a1c18;
1214
1290
  box-shadow: inset 0 0 0 4px #1a100c, inset 0 0 12px #e07020;
1215
1291
  }
1216
- .inn-actors { position: absolute; inset: 0; z-index: 2; }
1217
- .rpg-npc {
1292
+ .inn-actors {
1293
+ position: absolute;
1294
+ top: 56px;
1295
+ left: 56px;
1296
+ right: 56px;
1297
+ bottom: 0;
1298
+ z-index: 2;
1299
+ display: grid;
1300
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1301
+ align-content: start;
1302
+ justify-items: center;
1303
+ gap: 12px 6px;
1304
+ padding: 22px 0 10px;
1305
+ overflow: auto;
1306
+ }
1307
+ .inn-station {
1308
+ position: relative;
1309
+ display: flex;
1310
+ flex-direction: column;
1311
+ align-items: center;
1312
+ width: 88px;
1313
+ z-index: 2;
1314
+ }
1315
+ .inn-desk {
1316
+ order: 2;
1317
+ position: relative;
1318
+ width: 72px;
1319
+ height: 20px;
1320
+ margin-top: -8px;
1321
+ background: #8a5a32;
1322
+ box-shadow: 0 3px 0 #6a4020, 0 5px 0 #3a2414, inset 0 2px 0 #b07a48;
1323
+ z-index: 1;
1324
+ }
1325
+ .inn-monitor {
1326
+ position: absolute;
1327
+ left: 50%;
1328
+ bottom: 14px;
1329
+ width: 28px;
1330
+ height: 18px;
1331
+ margin-left: -14px;
1332
+ background: #1a1a1a;
1333
+ box-shadow: 0 0 0 2px #111;
1334
+ }
1335
+ .inn-monitor::after {
1336
+ content: "";
1218
1337
  position: absolute;
1338
+ inset: 3px 3px 4px;
1339
+ background: repeating-linear-gradient(#7ec8e8 0 2px, #4aa3c8 2px 4px);
1340
+ }
1341
+ .inn-station:has(.rpg-npc.vacant) .inn-monitor::after { background: #2a2a2a; }
1342
+ .rpg-npc {
1343
+ position: relative;
1219
1344
  display: flex;
1220
1345
  flex-direction: column;
1221
1346
  align-items: center;
1347
+ order: 1;
1222
1348
  margin: 0;
1223
1349
  padding: 0;
1224
1350
  border: 0;
1225
1351
  background: transparent;
1226
1352
  color: inherit;
1227
- transform: translate(-50%, -88%);
1228
1353
  cursor: pointer;
1229
1354
  z-index: 2;
1230
1355
  }
1231
- .rpg-npc canvas {
1232
- width: 48px;
1356
+ .rpg-npc .npc-bust,
1357
+ .rpg-npc .npc-blank {
1358
+ width: 72px;
1233
1359
  height: 72px;
1360
+ box-sizing: border-box;
1361
+ border: 3px solid #1a1410;
1362
+ background: #2a2118;
1363
+ box-shadow: 2px 3px 0 #0006;
1364
+ }
1365
+ .rpg-npc .npc-bust {
1366
+ object-fit: cover;
1367
+ object-position: center 18%;
1368
+ display: block;
1234
1369
  image-rendering: pixelated;
1235
- filter: drop-shadow(2px 3px 0 #0008);
1236
- animation: bob 1.1s steps(2) infinite;
1370
+ }
1371
+ .rpg-npc .npc-blank {
1372
+ display: grid;
1373
+ place-items: center;
1374
+ color: #f0d9a0;
1375
+ font-family: VT323, ui-monospace, monospace;
1376
+ font-size: 28px;
1377
+ line-height: 1;
1378
+ }
1379
+ .rpg-npc.drawing .npc-bust,
1380
+ .rpg-npc.drawing .npc-blank {
1381
+ filter: brightness(1.25) drop-shadow(0 0 6px #f0d060);
1382
+ }
1383
+ .npc-state {
1384
+ position: absolute;
1385
+ top: 0;
1386
+ left: 50%;
1387
+ z-index: 4;
1388
+ padding: 0 5px;
1389
+ background: #f4ead7;
1390
+ color: #1a1410;
1391
+ font-family: VT323, ui-monospace, monospace;
1392
+ font-size: 14px;
1393
+ line-height: 1.2;
1394
+ white-space: nowrap;
1395
+ box-shadow: 2px 2px 0 #1a1410;
1396
+ transform: translate(-50%, calc(-100% - 4px));
1397
+ }
1398
+ .npc-state.live {
1399
+ background: var(--signal);
1400
+ color: #1a1410;
1237
1401
  }
1238
1402
  .rpg-npc .tag {
1239
1403
  margin-top: 2px;
@@ -1245,10 +1409,22 @@ body.app.bar-page .shell {
1245
1409
  line-height: 1.1;
1246
1410
  white-space: nowrap;
1247
1411
  }
1248
- .rpg-npc:hover canvas { filter: brightness(1.18) drop-shadow(0 0 6px #f0d060); }
1249
- .rpg-npc.on canvas { outline: 2px solid #f0d060; outline-offset: 1px; }
1250
- .rpg-npc.vacant canvas { opacity: 0.35; }
1412
+ .rpg-npc:hover .npc-bust,
1413
+ .rpg-npc:hover .npc-blank { filter: brightness(1.18) drop-shadow(0 0 6px #f0d060); }
1414
+ .rpg-npc.on .npc-bust,
1415
+ .rpg-npc.on .npc-blank { outline: 2px solid #f0d060; outline-offset: 1px; }
1416
+ .rpg-npc.vacant .npc-blank { opacity: 0.55; }
1251
1417
  .rpg-npc.vacant .tag { color: #f0d060; }
1418
+ @media (max-width: 720px) {
1419
+ .inn-actors {
1420
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1421
+ left: 24px;
1422
+ right: 24px;
1423
+ }
1424
+ }
1425
+ @media (max-width: 520px) {
1426
+ .inn-actors { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1427
+ }
1252
1428
  .rpg-exit {
1253
1429
  position: absolute;
1254
1430
  left: 10px;
@@ -1270,20 +1446,34 @@ body.app.bar-page .shell {
1270
1446
  transform: none;
1271
1447
  }
1272
1448
  .rpg-dialog {
1273
- position: absolute;
1274
- left: 3.5%;
1275
- right: 3.5%;
1276
- bottom: 3%;
1449
+ grid-row: 2;
1450
+ position: relative;
1277
1451
  z-index: 5;
1452
+ margin: 8px 12px 10px;
1278
1453
  padding: 3px;
1279
1454
  background: #08080f;
1280
1455
  box-shadow: 0 0 0 2px #c9a227, inset 0 0 0 2px #3a4a8a;
1281
1456
  }
1282
1457
  .rpg-dialog-inner {
1458
+ display: flex;
1459
+ align-items: flex-start;
1460
+ gap: 10px;
1283
1461
  min-height: 92px;
1284
1462
  padding: 10px 14px 8px;
1285
1463
  background: linear-gradient(#2a3668, #1a2248 55%, #141a3a);
1286
1464
  }
1465
+ .rpg-face {
1466
+ flex: none;
1467
+ width: 56px;
1468
+ height: 56px;
1469
+ object-fit: cover;
1470
+ object-position: center 18%;
1471
+ border: 3px solid #1a1410;
1472
+ background: #2a2118;
1473
+ image-rendering: pixelated;
1474
+ }
1475
+ .rpg-face[hidden] { display: none; }
1476
+ .rpg-copy { min-width: 0; flex: 1; }
1287
1477
  .rpg-name {
1288
1478
  margin: 0 0 6px;
1289
1479
  color: #f0d060;
@@ -1298,18 +1488,22 @@ body.app.bar-page .shell {
1298
1488
  font-family: VT323, ui-monospace, monospace;
1299
1489
  font-size: 22px;
1300
1490
  line-height: 1.2;
1491
+ overflow-wrap: anywhere;
1301
1492
  }
1302
1493
  .rpg-acts {
1303
1494
  display: flex;
1495
+ flex-wrap: wrap;
1304
1496
  gap: 8px;
1305
1497
  margin-top: 8px;
1306
1498
  min-height: 22px;
1307
1499
  }
1308
1500
  .rpg-acts a,
1309
1501
  .rpg-acts button {
1502
+ appearance: none;
1310
1503
  margin: 0;
1311
1504
  padding: 3px 8px;
1312
1505
  border: 2px solid #c9a227;
1506
+ border-radius: 0;
1313
1507
  background: #141a3a;
1314
1508
  color: #f4f0e0;
1315
1509
  font-family: VT323, ui-monospace, monospace;
@@ -91,7 +91,12 @@
91
91
  return;
92
92
  }
93
93
  const button = document.getElementById("ai-generate");
94
+ if (button.disabled || button.getAttribute("aria-busy") === "true") return;
94
95
  button.disabled = true;
96
+ button.setAttribute("aria-busy", "true");
97
+ button.classList.add("is-busy");
98
+ const idle = button.textContent;
99
+ button.textContent = t("studio.generating");
95
100
  try {
96
101
  const response = await fetch("/generate", {
97
102
  method: "POST",
@@ -115,6 +120,9 @@
115
120
  setFlash("error", err.message);
116
121
  } finally {
117
122
  button.disabled = false;
123
+ button.removeAttribute("aria-busy");
124
+ button.classList.remove("is-busy");
125
+ button.textContent = idle;
118
126
  }
119
127
  });
120
128
  document.getElementById("ai-save").addEventListener("click", async () => {
package/src/router.ts CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  addChannelMember,
7
7
  createBot,
8
8
  createChannel,
9
+ createBranch,
10
+ closeBranch,
9
11
  deleteBot,
10
12
  deleteChannel,
11
13
  renameChannel,
@@ -18,6 +20,7 @@ import {
18
20
  setChannelMemory,
19
21
  generateKind,
20
22
  pickBotSkills,
23
+ generateBotLook,
21
24
  getBotDetail,
22
25
  getLiveTurn,
23
26
  abortLiveTurn,
@@ -25,6 +28,7 @@ import {
25
28
  importSkills,
26
29
  mergeModelsFile,
27
30
  publicModels,
31
+ refreshOpenCodeFreeCatalog,
28
32
  listBench,
29
33
  listLibrary,
30
34
  listMcpServers,
@@ -73,10 +77,13 @@ const PAGES: Record<string, { file: string; type: string }> = {
73
77
  "/studio": { file: "studio.html", type: "text/html; charset=utf-8" },
74
78
  "/skills/add": { file: "skills-add.html", type: "text/html; charset=utf-8" },
75
79
  "/chat": { file: "chat.html", type: "text/html; charset=utf-8" },
80
+ "/m": { file: "mobile.html", type: "text/html; charset=utf-8" },
76
81
  "/style.css": { file: "style.css", type: "text/css; charset=utf-8" },
77
82
  "/chat.css": { file: "chat.css", type: "text/css; charset=utf-8" },
83
+ "/mobile.css": { file: "mobile.css", type: "text/css; charset=utf-8" },
78
84
  "/md.js": { file: "md.js", type: "text/javascript; charset=utf-8" },
79
85
  "/i18n.js": { file: "i18n.js", type: "text/javascript; charset=utf-8" },
86
+ "/buddy.js": { file: "buddy.js", type: "text/javascript; charset=utf-8" },
80
87
  "/favicon.ico": { file: "favicon.ico", type: "image/x-icon" },
81
88
  "/favicon.svg": { file: "favicon.svg", type: "image/svg+xml" },
82
89
  "/favicon-16.svg": { file: "favicon-16.svg", type: "image/svg+xml" },
@@ -180,6 +187,16 @@ function strList(record: Record<string, unknown>, key: string): string[] {
180
187
  return [];
181
188
  }
182
189
 
190
+ function extrasWithMentions(
191
+ extras: HandlerExtras,
192
+ body: Record<string, unknown>,
193
+ ): HandlerExtras {
194
+ const mentions = strList(body, "mentions")
195
+ .map((id) => id.trim())
196
+ .filter(Boolean);
197
+ return mentions.length ? { ...extras, mentions } : extras;
198
+ }
199
+
183
200
  function skillPickCatalog(
184
201
  record: Record<string, unknown>,
185
202
  ): { id: string; name: string; description?: string; tags?: string[]; slug?: string }[] {
@@ -258,7 +275,12 @@ export async function handleRequest(
258
275
  }
259
276
 
260
277
  if (method === "GET" && page) {
261
- const body = readFileSync(`${PUBLIC}${page.file}`);
278
+ const file = `${PUBLIC}${page.file}`;
279
+ if (!existsSync(file)) {
280
+ json(res, 404, { error: "not_found", path });
281
+ return;
282
+ }
283
+ const body = readFileSync(file);
262
284
  const extra = page.type.startsWith("image/")
263
285
  ? { "cache-control": "public, max-age=86400" }
264
286
  : {};
@@ -352,6 +374,36 @@ export async function handleRequest(
352
374
  json(res, 201, createChannel(store, str(body, "name")));
353
375
  return;
354
376
  }
377
+ const channelBranch = path.match(/^\/channels\/([^/]+)\/branches$/);
378
+ if (channelBranch && method === "POST") {
379
+ const body = asRecord(await readJson(req));
380
+ json(
381
+ res,
382
+ 201,
383
+ createBranch(
384
+ store,
385
+ decodeURIComponent(channelBranch[1]),
386
+ str(body, "messageId"),
387
+ str(body, "name") || undefined,
388
+ ),
389
+ );
390
+ return;
391
+ }
392
+ const channelClose = path.match(/^\/channels\/([^/]+)\/close$/);
393
+ if (channelClose && method === "POST") {
394
+ const body = asRecord(await readJson(req));
395
+ json(
396
+ res,
397
+ 200,
398
+ await closeBranch(
399
+ store,
400
+ decodeURIComponent(channelClose[1]),
401
+ body.merge === true,
402
+ env,
403
+ ),
404
+ );
405
+ return;
406
+ }
355
407
  const channelOne = path.match(/^\/channels\/([^/]+)$/);
356
408
  if (channelOne && method === "DELETE") {
357
409
  json(res, 200, deleteChannel(store, decodeURIComponent(channelOne[1])));
@@ -480,7 +532,7 @@ export async function handleRequest(
480
532
  str(body, "body") || undefined,
481
533
  env,
482
534
  str(body, "assigneeId") || undefined,
483
- extras,
535
+ extrasWithMentions(extras, body),
484
536
  ),
485
537
  );
486
538
  return;
@@ -502,7 +554,7 @@ export async function handleRequest(
502
554
  str(body, "body") || undefined,
503
555
  env,
504
556
  str(body, "assigneeId") || undefined,
505
- extras,
557
+ extrasWithMentions(extras, body),
506
558
  ),
507
559
  );
508
560
  return;
@@ -639,7 +691,7 @@ export async function handleRequest(
639
691
  str(body, "replyTo") || undefined,
640
692
  parseAttachments(body.attachments),
641
693
  str(body, "assigneeId") || undefined,
642
- extras,
694
+ extrasWithMentions(extras, body),
643
695
  ),
644
696
  );
645
697
  return;
@@ -665,7 +717,7 @@ export async function handleRequest(
665
717
  str(body, "replyTo") || undefined,
666
718
  parseAttachments(body.attachments),
667
719
  str(body, "assigneeId") || undefined,
668
- extras,
720
+ extrasWithMentions(extras, body),
669
721
  ),
670
722
  );
671
723
  return;
@@ -806,9 +858,18 @@ export async function handleRequest(
806
858
 
807
859
  if (method === "GET" && path === "/settings/models") {
808
860
  await refreshCopilotCatalog(store.dataDir);
861
+ await refreshOpenCodeFreeCatalog(store.dataDir);
809
862
  json(res, 200, publicModels(store.dataDir));
810
863
  return;
811
864
  }
865
+ if (method === "POST" && path === "/settings/models/opencode-free/sync") {
866
+ const refreshed = await refreshOpenCodeFreeCatalog(store.dataDir, true);
867
+ json(res, 200, {
868
+ ...publicModels(store.dataDir),
869
+ probe: refreshed.probe ?? [],
870
+ });
871
+ return;
872
+ }
812
873
  if (method === "PUT" && path === "/settings/models") {
813
874
  const body = asRecord(await readJson(req));
814
875
  const providers = body.providers;
@@ -891,6 +952,12 @@ export async function handleRequest(
891
952
  return;
892
953
  }
893
954
 
955
+ const lookId = path.match(/^\/bots\/([^/]+)\/look$/)?.[1];
956
+ if (lookId && method === "POST") {
957
+ json(res, 200, await generateBotLook(store, decodeURIComponent(lookId), env));
958
+ return;
959
+ }
960
+
894
961
  const botId = path.match(/^\/bots\/([^/]+)$/)?.[1];
895
962
  if (botId && method === "GET") {
896
963
  json(res, 200, getBotDetail(store, botId));
@@ -906,6 +973,9 @@ export async function handleRequest(
906
973
  name: str(body, "name") || undefined,
907
974
  handle: str(body, "handle") || undefined,
908
975
  oneLiner: str(body, "oneLiner") || undefined,
976
+ ...(Object.hasOwn(body, "portrait")
977
+ ? { portrait: body.portrait == null ? null : str(body, "portrait") }
978
+ : {}),
909
979
  skillIds: Object.hasOwn(body, "skillIds")
910
980
  ? strList(body, "skillIds")
911
981
  : undefined,
@@ -962,6 +1032,7 @@ export async function handleRequest(
962
1032
  json(res, error.status, { error: error.message });
963
1033
  return;
964
1034
  }
1035
+ console.error(error);
965
1036
  json(res, 500, { error: "internal_error" });
966
1037
  }
967
1038
  }