@bonginkan/maria-lite 6.3.0 → 6.3.1

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/cli.cjs CHANGED
@@ -45614,7 +45614,7 @@ var require_src8 = __commonJS({
45614
45614
  */
45615
45615
  }, {
45616
45616
  key: "getToken",
45617
- value: function getToken2(callback) {
45617
+ value: function getToken3(callback) {
45618
45618
  var opts = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
45619
45619
  if (_typeof(callback) === "object") {
45620
45620
  opts = callback;
@@ -97578,7 +97578,7 @@ function resolvePackageJsonNearEntrypoint() {
97578
97578
  }
97579
97579
  function resolveMariaLiteVersionInfo() {
97580
97580
  const fallbackName = EXPECTED_PKG_NAME;
97581
- const fallbackVersion = String(process.env.MARIA_LITE_VERSION || "").trim() || "6.3.0";
97581
+ const fallbackVersion = String(process.env.MARIA_LITE_VERSION || "").trim() || "6.3.1";
97582
97582
  const near = resolvePackageJsonNearEntrypoint();
97583
97583
  if (near) {
97584
97584
  const name = fallbackName;
@@ -122583,7 +122583,7 @@ async function handleToolCalls(params) {
122583
122583
  ...written.map((w) => `- ${w.rel}`)
122584
122584
  ].join("\n");
122585
122585
  await params.ctxStore.appendCommon({ actorId: params.actorId, text: header });
122586
- const clamp = (s2, max) => {
122586
+ const clamp2 = (s2, max) => {
122587
122587
  const t2 = String(s2 || "");
122588
122588
  if (t2.length <= max) return t2;
122589
122589
  return `${t2.slice(0, max)}
@@ -122599,15 +122599,15 @@ async function handleToolCalls(params) {
122599
122599
  if (fileRel) lines.push(`file=${fileRel}`);
122600
122600
  lines.push("");
122601
122601
  lines.push("description:");
122602
- lines.push(clamp(p.description || "", 2e3));
122602
+ lines.push(clamp2(p.description || "", 2e3));
122603
122603
  lines.push("");
122604
122604
  lines.push("entries:");
122605
122605
  for (const e2 of p.entries.slice(0, 20)) {
122606
122606
  lines.push(`- id=${e2.id} kind=${e2.kind} importance=${e2.importance} title=${e2.title}`);
122607
122607
  if (e2.tags && e2.tags.length) lines.push(` tags=${e2.tags.slice().sort((a, b) => a.localeCompare(b)).join(",")}`);
122608
- if (e2.summary) lines.push(` summary=${clamp(e2.summary, 800).replaceAll("\n", " ")}`);
122608
+ if (e2.summary) lines.push(` summary=${clamp2(e2.summary, 800).replaceAll("\n", " ")}`);
122609
122609
  if (e2.body) lines.push(` body=
122610
- ${clamp(e2.body, 2200)}`);
122610
+ ${clamp2(e2.body, 2200)}`);
122611
122611
  }
122612
122612
  await params.ctxStore.appendCommon({ actorId: params.actorId, text: lines.join("\n") });
122613
122613
  }
@@ -127245,7 +127245,7 @@ function categorizeCommand(id) {
127245
127245
  const dataSet = /* @__PURE__ */ new Set(["kp-sync", "kp-generate", "kp-update", "kp-synthesis", "import-origin", "reorganize", "origin-fetch"]);
127246
127246
  const creativeSet = /* @__PURE__ */ new Set(["blog", "slides", "docs", "proposal", "pdf", "manga", "novel", "film", "spreadsheet"]);
127247
127247
  const academicSet = /* @__PURE__ */ new Set(["paper", "exam"]);
127248
- const googleSet = /* @__PURE__ */ new Set(["gmail", "gcal", "gdrive", "gmeet"]);
127248
+ const googleSet = /* @__PURE__ */ new Set(["gmail", "gcal", "gdrive", "gmeet", "resource-manager"]);
127249
127249
  const phoneSet = /* @__PURE__ */ new Set(["phone-tenant", "phone-dept", "phone-prompt", "phone-dict", "phone-hp-import", "phone-deploy", "phone-init"]);
127250
127250
  const systemSet = /* @__PURE__ */ new Set(["help", "version", "gui", "desktop", "login", "logout", "account", "vup", "schedule", "task-manager", "log-viewer", "google-connect"]);
127251
127251
  if (dailySet.has(id)) return "daily";
@@ -127368,7 +127368,8 @@ function commandIcon(id) {
127368
127368
  "competitors": "\u{1F3C6}",
127369
127369
  "task-distribution": "\u{1F4CB}",
127370
127370
  "billing-pl": "\u{1F4B0}",
127371
- "dev-adviser": "\u{1F468}\u200D\u{1F4BB}"
127371
+ "dev-adviser": "\u{1F468}\u200D\u{1F4BB}",
127372
+ "resource-manager": "\u{1F3E2}"
127372
127373
  };
127373
127374
  return icons[id] || "\u{1F4CE}";
127374
127375
  }
@@ -192002,7 +192003,7 @@ function normalizeImageCli(ctx) {
192002
192003
  const planOnly = hasLiteFlag(ctx.parsed, "plan-only") || hasLiteFlag(ctx.parsed, "dry-run");
192003
192004
  const applyExplicit = hasLiteFlag(ctx.parsed, "apply");
192004
192005
  const apply = planOnly ? false : applyExplicit || true;
192005
- const outDirRel = ctx.parsed.options.out ? String(ctx.parsed.options.out).trim() : "outputs/images";
192006
+ const outDirRel = ctx.parsed.options.out ? String(ctx.parsed.options.out).trim() : ".maria/desktop/images";
192006
192007
  const outPathRel = normalizeRelOutPath(ctx.parsed.options.path, `.${format2}`) || void 0;
192007
192008
  const concurrency = ctx.parsed.options.concurrency ? Math.max(1, Math.min(16, Math.floor(Number(ctx.parsed.options.concurrency)))) : void 0;
192008
192009
  const retry = ctx.parsed.options.retry ? Math.max(0, Math.min(10, Math.floor(Number(ctx.parsed.options.retry)))) : void 0;
@@ -192335,7 +192336,7 @@ function normalizeVideoCli(ctx) {
192335
192336
  const planOnly = hasLiteFlag(ctx.parsed, "plan-only") || hasLiteFlag(ctx.parsed, "dry-run");
192336
192337
  const applyExplicit = hasLiteFlag(ctx.parsed, "apply");
192337
192338
  const apply = planOnly ? false : applyExplicit || true;
192338
- const outDirRel = ctx.parsed.options.out ? String(ctx.parsed.options.out).trim() : "outputs/videos";
192339
+ const outDirRel = ctx.parsed.options.out ? String(ctx.parsed.options.out).trim() : ".maria/desktop/videos";
192339
192340
  const outPathRel = normalizeRelOutPath2(ctx.parsed.options.path, `.${format2}`) || void 0;
192340
192341
  const concurrency = ctx.parsed.options.concurrency ? Math.max(1, Math.min(16, Math.floor(Number(ctx.parsed.options.concurrency)))) : void 0;
192341
192342
  const retry = ctx.parsed.options.retry ? Math.max(0, Math.min(10, Math.floor(Number(ctx.parsed.options.retry)))) : void 0;
@@ -193314,8 +193315,8 @@ function parseSlidesTextShiftOpt(raw) {
193314
193315
  if (parts.length !== 4) return null;
193315
193316
  const nums = parts.map((p) => Number(p));
193316
193317
  if (!nums.every((n) => Number.isFinite(n))) return null;
193317
- const clamp = (x2) => Math.max(0, Math.min(20, Math.floor(x2 * 1e3) / 1e3));
193318
- const [up, right, down, left] = nums.map((n) => clamp(Number(n)));
193318
+ const clamp2 = (x2) => Math.max(0, Math.min(20, Math.floor(x2 * 1e3) / 1e3));
193319
+ const [up, right, down, left] = nums.map((n) => clamp2(Number(n)));
193319
193320
  return { up, right, down, left };
193320
193321
  }
193321
193322
  function slidesTextShiftToDxDy(shift) {
@@ -193404,7 +193405,7 @@ function validateDeckScriptV1(raw, expectedSlides, opts) {
193404
193405
  if (!s1Bullets) return { ok: false, error: "DECK_TITLE_SLIDE_BULLETS_INVALID" };
193405
193406
  if (s1Bullets.length !== 0) return { ok: false, error: "DECK_TITLE_SLIDE_BULLETS_NOT_EMPTY" };
193406
193407
  }
193407
- const clamp = (s2, maxLen) => {
193408
+ const clamp2 = (s2, maxLen) => {
193408
193409
  const t2 = String(s2 || "").trim();
193409
193410
  if (t2.length <= maxLen) return t2;
193410
193411
  return t2.slice(0, Math.max(0, maxLen - 1)).trimEnd() + "\u2026";
@@ -193418,12 +193419,12 @@ function validateDeckScriptV1(raw, expectedSlides, opts) {
193418
193419
  for (const s2 of slides) {
193419
193420
  if (!isRecord(s2)) return { ok: false, error: "DECK_SLIDE_NOT_OBJECT" };
193420
193421
  const st0 = typeof s2.title === "string" ? s2.title.trim() : "";
193421
- const st = clamp(st0, 48);
193422
+ const st = clamp2(st0, 48);
193422
193423
  if (!st) return { ok: false, error: "DECK_SLIDE_TITLE_EMPTY" };
193423
193424
  if (hasTerminalPunct(st)) return { ok: false, error: "DECK_SLIDE_TITLE_SENTENCE_LIKE_FORBIDDEN" };
193424
193425
  s2.title = st;
193425
193426
  const sub0 = typeof s2.subtitle === "string" ? String(s2.subtitle).trim() : "";
193426
- const sub = sub0 ? clamp(sub0, 80) : "";
193427
+ const sub = sub0 ? clamp2(sub0, 80) : "";
193427
193428
  const kind0 = typeof s2.kind === "string" ? String(s2.kind).trim().toLowerCase() : "";
193428
193429
  const kind = kind0 === "title" || kind0 === "agenda" || kind0 === "section" || kind0 === "content" || kind0 === "appendix" ? kind0 : "";
193429
193430
  if (kind) s2.kind = kind;
@@ -193459,7 +193460,7 @@ function validateDeckScriptV1(raw, expectedSlides, opts) {
193459
193460
  const name = srec && typeof srec.name === "string" ? String(srec.name).trim().slice(0, 40) : "";
193460
193461
  const values = srec && Array.isArray(srec.values) ? srec.values.map((v) => typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" ? Number(v) : NaN).filter((v) => Number.isFinite(v)).slice(0, 6) : [];
193461
193462
  const caption0 = typeof c.caption === "string" ? String(c.caption).trim() : "";
193462
- const caption = caption0 ? clamp(caption0, 140) : "";
193463
+ const caption = caption0 ? clamp2(caption0, 140) : "";
193463
193464
  const okChart = type2 === "bar" && categories.length >= 2 && values.length === categories.length;
193464
193465
  if (!okChart) {
193465
193466
  s2.chart = void 0;
@@ -193483,7 +193484,7 @@ function validateDeckScriptV1(raw, expectedSlides, opts) {
193483
193484
  const headers = Array.isArray(headersRaw) ? headersRaw.map((x2) => typeof x2 === "string" ? x2.trim() : "").filter(Boolean) : [];
193484
193485
  const rows = Array.isArray(rowsRaw) ? rowsRaw.filter((r2) => Array.isArray(r2)).map((r2) => r2.map((c) => typeof c === "string" ? c.trim() : "").map((c) => c.slice(0, 80))) : [];
193485
193486
  const caption0 = typeof t0.caption === "string" ? String(t0.caption).trim() : "";
193486
- const caption = caption0 ? clamp(caption0, 140) : "";
193487
+ const caption = caption0 ? clamp2(caption0, 140) : "";
193487
193488
  const isEmptyTable = headers.length === 0 && rows.length === 0 && !caption;
193488
193489
  if (isEmptyTable) {
193489
193490
  s2.table = void 0;
@@ -193623,10 +193624,10 @@ function pickFigureCanvas(params) {
193623
193624
  const hint = typeof params.hint === "string" ? params.hint.trim() : "";
193624
193625
  const baseW = params.slideCanvas.width;
193625
193626
  const baseH = params.slideCanvas.height;
193626
- const clamp = (x2) => Math.max(512, Math.min(2048, Math.floor(x2)));
193627
- if (hint === "tall") return { width: clamp(baseW * 0.38), height: clamp(baseH * 0.72) };
193628
- if (hint === "square") return { width: clamp(baseW * 0.5), height: clamp(baseH * 0.5) };
193629
- return { width: clamp(baseW * 0.62), height: clamp(baseH * 0.42) };
193627
+ const clamp2 = (x2) => Math.max(512, Math.min(2048, Math.floor(x2)));
193628
+ if (hint === "tall") return { width: clamp2(baseW * 0.38), height: clamp2(baseH * 0.72) };
193629
+ if (hint === "square") return { width: clamp2(baseW * 0.5), height: clamp2(baseH * 0.5) };
193630
+ return { width: clamp2(baseW * 0.62), height: clamp2(baseH * 0.42) };
193630
193631
  }
193631
193632
  async function saveImageFromApiResult(params) {
193632
193633
  return await saveImageFromApiResultShared({
@@ -194534,7 +194535,7 @@ ${prompt}` : prompt,
194534
194535
  const replace = has2("replace") || has2("overwrite");
194535
194536
  const newSeries = has2("new-series") || has2("newSeries") || Boolean(envInputs.newSeries);
194536
194537
  const seriesRef = String(envInputs.series ?? pickOpt4("series") ?? "").trim();
194537
- const outDirRel = String(envInputs.outDir ?? pickOpt4("out") ?? "artifacts/novel").trim() || "artifacts/novel";
194538
+ const outDirRel = String(envInputs.outDir ?? pickOpt4("out") ?? ".maria/desktop/novel").trim() || ".maria/desktop/novel";
194538
194539
  const lang = normalizeLang(String(envInputs.lang ?? pickOpt4("lang") ?? "en"));
194539
194540
  const format2 = normalizeFormat4(String(envInputs.format ?? pickOpt4("format") ?? "md"));
194540
194541
  const genre = String(envInputs.genre ?? pickOpt4("genre") ?? "").trim() || void 0;
@@ -227615,8 +227616,8 @@ ${this.help.usage}` };
227615
227616
  const format2 = normalizeFormat5(ctx.parsed.options.format);
227616
227617
  const model = ctx.parsed.options.model ? String(ctx.parsed.options.model).trim() : void 0;
227617
227618
  const seed = ctx.parsed.options.seed ? parseSeed4(ctx.parsed.options.seed) : void 0;
227618
- const outDirRel = normalizeMangaOutDir(ctx.parsed.options.out ? String(ctx.parsed.options.out).trim() : "manga");
227619
- const outRootAbs = path87__namespace.resolve(ctx.cwd, outDirRel || "manga");
227619
+ const outDirRel = normalizeMangaOutDir(ctx.parsed.options.out ? String(ctx.parsed.options.out).trim() : ".maria/desktop/manga");
227620
+ const outRootAbs = path87__namespace.resolve(ctx.cwd, outDirRel || ".maria/desktop/manga");
227620
227621
  const stamp = nowStamp5();
227621
227622
  const inBase = inParts.length === 1 ? path87__namespace.basename(inParts[0]) : inParts.length > 1 ? "sources" : "";
227622
227623
  const folder = `${stamp}_${safeSlug3((theme || inBase || "source").slice(0, 64))}`;
@@ -228543,7 +228544,7 @@ function createSlidesField() {
228543
228544
  const format2 = normalizeFormat3(ctx.parsed.options.format);
228544
228545
  const model = typeof ctx.parsed.options.model === "string" ? String(ctx.parsed.options.model).trim() : void 0;
228545
228546
  const seed = typeof ctx.parsed.options.seed === "string" ? parseSeed3(ctx.parsed.options.seed) : void 0;
228546
- const outBaseRel = normalizeSlidesOutDir(typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "slides");
228547
+ const outBaseRel = normalizeSlidesOutDir(typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : ".maria/desktop/slides");
228547
228548
  const wantsPdf = hasLiteFlag(ctx.parsed, "pdf");
228548
228549
  const wantsPptx = hasLiteFlag(ctx.parsed, "pptx");
228549
228550
  const titleTitleShiftRaw = typeof ctx.parsed.options["title-title-shift"] === "string" ? String(ctx.parsed.options["title-title-shift"]).trim() : "";
@@ -230075,7 +230076,7 @@ function createFilmField() {
230075
230076
  const model = modelRaw || "sora-2";
230076
230077
  const provider = typeof ctx.parsed.options.provider === "string" ? String(ctx.parsed.options.provider).trim() : void 0;
230077
230078
  const seed = ctx.parsed.options.seed ? parseSeed5(ctx.parsed.options.seed) : void 0;
230078
- const outBaseRel = normalizeFilmOutDir(typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "film");
230079
+ const outBaseRel = normalizeFilmOutDir(typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : ".maria/desktop/film");
230079
230080
  const res = typeof ctx.parsed.options.res === "string" && String(ctx.parsed.options.res).trim() ? String(ctx.parsed.options.res).trim() : pickDefaultResForAspect({ aspect });
230080
230081
  const planOnly = hasLiteFlag(ctx.parsed, "plan-only") || hasLiteFlag(ctx.parsed, "dry-run");
230081
230082
  const applyExplicit = hasLiteFlag(ctx.parsed, "apply");
@@ -231001,7 +231002,7 @@ function createDocsField() {
231001
231002
  const wantsDocx = hasLiteFlag(ctx.parsed, "docx") || !hasLiteFlag(ctx.parsed, "docx") && !hasLiteFlag(ctx.parsed, "pdf");
231002
231003
  const wantsPdf = hasLiteFlag(ctx.parsed, "pdf") || !hasLiteFlag(ctx.parsed, "docx") && !hasLiteFlag(ctx.parsed, "pdf");
231003
231004
  const outBase = typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "";
231004
- const outBaseRel = (outBase || "docs").replace(/\\/g, "/");
231005
+ const outBaseRel = (outBase || ".maria/desktop/docs").replace(/\\/g, "/");
231005
231006
  const src = await resolveInputSourceText(ctx);
231006
231007
  if (!src.sourceText.trim()) return { text: `Usage:
231007
231008
  ${this.help.usage}` };
@@ -231308,8 +231309,8 @@ async function loadCommonSettings(cwd) {
231308
231309
  if (!r2) return null;
231309
231310
  const nums = ["up", "right", "down", "left"].map((k) => Number(r2[k]));
231310
231311
  if (!nums.every((n) => Number.isFinite(n))) return null;
231311
- const clamp = (x2) => Math.max(0, Math.min(20, Math.floor(x2 * 1e3) / 1e3));
231312
- return { up: clamp(nums[0]), right: clamp(nums[1]), down: clamp(nums[2]), left: clamp(nums[3]) };
231312
+ const clamp2 = (x2) => Math.max(0, Math.min(20, Math.floor(x2 * 1e3) / 1e3));
231313
+ return { up: clamp2(nums[0]), right: clamp2(nums[1]), down: clamp2(nums[2]), left: clamp2(nums[3]) };
231313
231314
  };
231314
231315
  const titleTitleShift = normShift(v.titleTitleShift);
231315
231316
  const titleSubtitleShift = normShift(v.titleSubtitleShift);
@@ -256282,6 +256283,1492 @@ var init_phone_routes = __esm({
256282
256283
  init_desktop_server_helpers();
256283
256284
  }
256284
256285
  });
256286
+
256287
+ // services/desktop/routes/calendar-api-routes.ts
256288
+ async function getToken2() {
256289
+ const mgr = new GoogleOAuthManager();
256290
+ return mgr.getValidToken();
256291
+ }
256292
+ function notConnected2(res) {
256293
+ respondJson2(res, 200, { ok: false, error: "not_connected" });
256294
+ return true;
256295
+ }
256296
+ async function gFetch2(token, url, init) {
256297
+ const headers = {
256298
+ Authorization: `Bearer ${token}`,
256299
+ ...init?.headers || {}
256300
+ };
256301
+ return fetch(url, { ...init, headers });
256302
+ }
256303
+ function resolveProjectId2() {
256304
+ return process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT || process.env.FIREBASE_PROJECT_ID || process.env.GCP_PROJECT_ID || "";
256305
+ }
256306
+ async function getFirestore2() {
256307
+ if (_firestoreInstance2) return _firestoreInstance2;
256308
+ if (_firestoreInitPromise2) return _firestoreInitPromise2;
256309
+ _firestoreInitPromise2 = (async () => {
256310
+ const projectId = resolveProjectId2();
256311
+ if (!projectId) return null;
256312
+ try {
256313
+ const { Firestore: Firestore3 } = await Promise.resolve().then(() => __toESM(require_src21(), 1));
256314
+ _firestoreInstance2 = new Firestore3({ projectId });
256315
+ return _firestoreInstance2;
256316
+ } catch {
256317
+ return null;
256318
+ }
256319
+ })();
256320
+ return _firestoreInitPromise2;
256321
+ }
256322
+ function firestoreUnavailable(res) {
256323
+ respondJson2(res, 503, {
256324
+ ok: false,
256325
+ error: "firestore_unavailable",
256326
+ message: "Firestore is not configured. Set GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT env var."
256327
+ });
256328
+ return true;
256329
+ }
256330
+ function generateReservationId() {
256331
+ const now = /* @__PURE__ */ new Date();
256332
+ const datePart = [
256333
+ now.getFullYear(),
256334
+ String(now.getMonth() + 1).padStart(2, "0"),
256335
+ String(now.getDate()).padStart(2, "0")
256336
+ ].join("");
256337
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
256338
+ let random = "";
256339
+ for (let i2 = 0; i2 < 5; i2++) {
256340
+ random += chars[Math.floor(Math.random() * chars.length)];
256341
+ }
256342
+ return `R-${datePart}-${random}`;
256343
+ }
256344
+ function buildRrule(type2, count, until) {
256345
+ const freqMap = {
256346
+ daily: "DAILY",
256347
+ weekly: "WEEKLY",
256348
+ biweekly: "WEEKLY",
256349
+ monthly: "MONTHLY"
256350
+ };
256351
+ const freq = freqMap[type2];
256352
+ if (!freq) return null;
256353
+ let rule = `RRULE:FREQ=${freq}`;
256354
+ if (type2 === "biweekly") rule += ";INTERVAL=2";
256355
+ if (count) rule += `;COUNT=${count}`;
256356
+ else if (until) rule += `;UNTIL=${until.replace(/-/g, "")}T235959Z`;
256357
+ return rule;
256358
+ }
256359
+ async function fetchResources(token, buildingId, minCapacity) {
256360
+ let url = `${ADMIN_RESOURCES_BASE}/calendars?maxResults=500`;
256361
+ if (buildingId) {
256362
+ url += `&query=buildingId="${encodeURIComponent(buildingId)}"`;
256363
+ }
256364
+ const apiRes = await gFetch2(token, url);
256365
+ if (!apiRes.ok) return [];
256366
+ const data = await apiRes.json();
256367
+ const items = Array.isArray(data.items) ? data.items : [];
256368
+ const resources = items.map((item) => ({
256369
+ resourceId: String(item.resourceId || ""),
256370
+ resourceEmail: String(item.resourceEmail || ""),
256371
+ resourceName: String(item.resourceName || ""),
256372
+ resourceType: String(item.resourceType || ""),
256373
+ capacity: Number(item.capacity || 0),
256374
+ buildingId: String(item.buildingId || ""),
256375
+ floorName: String(item.floorName || ""),
256376
+ userVisibleDescription: String(item.userVisibleDescription || "")
256377
+ }));
256378
+ if (minCapacity != null && minCapacity > 0) {
256379
+ return resources.filter((r2) => r2.capacity >= minCapacity);
256380
+ }
256381
+ return resources;
256382
+ }
256383
+ function findSlots(params) {
256384
+ const {
256385
+ busyMap,
256386
+ required,
256387
+ optional,
256388
+ resourceEmails,
256389
+ durationMs,
256390
+ dateFrom,
256391
+ dateTo,
256392
+ workStartHour,
256393
+ workEndHour,
256394
+ offset,
256395
+ bufferMinutes = DEFAULT_BUFFER_MINUTES,
256396
+ timeZone = DEFAULT_TIMEZONE
256397
+ } = params;
256398
+ const minBufferMs = bufferMinutes * 60 * 1e3;
256399
+ const allTargets = [...resourceEmails, ...required, ...optional];
256400
+ const candidates = [];
256401
+ const [fromY, fromM, fromD] = dateFrom.split("-").map(Number);
256402
+ const [toY, toM, toD] = dateTo.split("-").map(Number);
256403
+ const dStart = new Date(Date.UTC(fromY, fromM - 1, fromD));
256404
+ const dEnd = new Date(Date.UTC(toY, toM - 1, toD));
256405
+ for (let d = new Date(dStart); d <= dEnd; d.setUTCDate(d.getUTCDate() + 1)) {
256406
+ const yy = d.getUTCFullYear();
256407
+ const mm = d.getUTCMonth() + 1;
256408
+ const dd = d.getUTCDate();
256409
+ const dayStr = `${yy}-${String(mm).padStart(2, "0")}-${String(dd).padStart(2, "0")}`;
256410
+ const weekdayRef = new Date(Date.UTC(yy, mm - 1, dd, 12, 0, 0));
256411
+ if (weekdayRef.getUTCDay() === 0 || weekdayRef.getUTCDay() === 6) continue;
256412
+ const slotDayStart = makeDateInTz(dayStr, workStartHour, 0, timeZone);
256413
+ const slotDayEnd = makeDateInTz(dayStr, workEndHour, 0, timeZone);
256414
+ for (let t2 = slotDayStart.getTime(); t2 + durationMs <= slotDayEnd.getTime(); t2 += SLOT_STEP_MS) {
256415
+ const slotS = t2;
256416
+ const slotE = t2 + durationMs;
256417
+ let requiredOk = true;
256418
+ let resourcesOk = true;
256419
+ let availCount = 0;
256420
+ const missingOpt = [];
256421
+ const availableResourceEmails = [];
256422
+ for (const email of resourceEmails) {
256423
+ const busy = busyMap[email] || [];
256424
+ const isBusy = busy.some((b) => b.s < slotE && b.e > slotS);
256425
+ if (isBusy) {
256426
+ resourcesOk = false;
256427
+ } else {
256428
+ availableResourceEmails.push(email);
256429
+ availCount++;
256430
+ }
256431
+ }
256432
+ if (resourceEmails.length > 0 && !resourcesOk && availableResourceEmails.length === 0) continue;
256433
+ for (const email of required) {
256434
+ const busy = busyMap[email] || [];
256435
+ const isBusy = busy.some((b) => b.s < slotE && b.e > slotS);
256436
+ if (isBusy) {
256437
+ requiredOk = false;
256438
+ } else {
256439
+ availCount++;
256440
+ }
256441
+ }
256442
+ if (!requiredOk) continue;
256443
+ for (const email of optional) {
256444
+ const busy = busyMap[email] || [];
256445
+ const isBusy = busy.some((b) => b.s < slotE && b.e > slotS);
256446
+ if (isBusy) {
256447
+ missingOpt.push(email);
256448
+ } else {
256449
+ availCount++;
256450
+ }
256451
+ }
256452
+ const mustBeAvailable = [...resourceEmails.filter((e2) => availableResourceEmails.includes(e2)), ...required];
256453
+ let bufBefore = Infinity;
256454
+ let bufAfter = Infinity;
256455
+ for (const email of mustBeAvailable) {
256456
+ const busy = busyMap[email] || [];
256457
+ let gapBefore = slotS - slotDayStart.getTime();
256458
+ let gapAfter = slotDayEnd.getTime() - slotE;
256459
+ for (const b of busy) {
256460
+ if (b.e <= slotS) gapBefore = Math.min(gapBefore, slotS - b.e);
256461
+ if (b.s >= slotE) gapAfter = Math.min(gapAfter, b.s - slotE);
256462
+ }
256463
+ bufBefore = Math.min(bufBefore, gapBefore);
256464
+ bufAfter = Math.min(bufAfter, gapAfter);
256465
+ }
256466
+ if (!isFinite(bufBefore)) bufBefore = 12 * 60 * 60 * 1e3;
256467
+ if (!isFinite(bufAfter)) bufAfter = 12 * 60 * 60 * 1e3;
256468
+ candidates.push({
256469
+ start: slotS,
256470
+ end: slotE,
256471
+ allAvailable: availCount === allTargets.length,
256472
+ availableCount: availCount,
256473
+ totalParticipants: allTargets.length,
256474
+ missingOptional: missingOpt,
256475
+ bufferBefore: bufBefore,
256476
+ bufferAfter: bufAfter,
256477
+ resourceEmails: availableResourceEmails
256478
+ });
256479
+ }
256480
+ }
256481
+ const hasSufficientBuffer = (c) => c.bufferBefore >= minBufferMs && c.bufferAfter >= minBufferMs;
256482
+ const bufferImbalance = (c) => Math.abs(c.bufferBefore - c.bufferAfter);
256483
+ candidates.sort((a, b) => {
256484
+ if (a.allAvailable !== b.allAvailable) return a.allAvailable ? -1 : 1;
256485
+ if (a.availableCount !== b.availableCount) return b.availableCount - a.availableCount;
256486
+ if (a.missingOptional.length !== b.missingOptional.length) return a.missingOptional.length - b.missingOptional.length;
256487
+ const optIdx = (m2) => {
256488
+ let worst = -1;
256489
+ for (const e2 of m2) {
256490
+ const i2 = optional.indexOf(e2);
256491
+ if (i2 > worst) worst = i2;
256492
+ }
256493
+ return worst;
256494
+ };
256495
+ const ai = optIdx(a.missingOptional);
256496
+ const bi = optIdx(b.missingOptional);
256497
+ if (ai !== bi) return ai - bi;
256498
+ const aSuf = hasSufficientBuffer(a);
256499
+ const bSuf = hasSufficientBuffer(b);
256500
+ if (aSuf && bSuf) {
256501
+ if (a.start !== b.start) return a.start - b.start;
256502
+ return bufferImbalance(a) - bufferImbalance(b);
256503
+ }
256504
+ if (aSuf !== bSuf) return aSuf ? -1 : 1;
256505
+ const aTot = a.bufferBefore + a.bufferAfter;
256506
+ const bTot = b.bufferBefore + b.bufferAfter;
256507
+ if (aTot !== bTot) return bTot - aTot;
256508
+ return bufferImbalance(a) - bufferImbalance(b);
256509
+ });
256510
+ const nonOverlapping = [];
256511
+ const needed = (offset + 1) * TOP_SLOT_COUNT + 1;
256512
+ for (const c of candidates) {
256513
+ const overlaps = nonOverlapping.some((t2) => t2.start < c.end && t2.end > c.start);
256514
+ if (!overlaps) nonOverlapping.push(c);
256515
+ if (nonOverlapping.length >= needed) break;
256516
+ }
256517
+ const start = offset * TOP_SLOT_COUNT;
256518
+ const page = nonOverlapping.slice(start, start + TOP_SLOT_COUNT);
256519
+ return { slots: page, hasMore: nonOverlapping.length > start + TOP_SLOT_COUNT };
256520
+ }
256521
+ async function handleCalendarApiRoute(method, pathname, req, res) {
256522
+ if (!pathname.startsWith("/api/calendar/")) return false;
256523
+ if (method === "GET" && pathname === "/api/calendar/resources") {
256524
+ const token = await getToken2();
256525
+ if (!token) return notConnected2(res);
256526
+ const url = new URL(req.url || "/", "http://localhost");
256527
+ const buildingId = url.searchParams.get("buildingId") || void 0;
256528
+ const capacityRaw = url.searchParams.get("capacity");
256529
+ const capacity = capacityRaw ? Math.max(0, Number(capacityRaw) || 0) : void 0;
256530
+ try {
256531
+ const resources = await fetchResources(token, buildingId, capacity);
256532
+ return respondJson2(res, 200, { ok: true, resources }), true;
256533
+ } catch (err) {
256534
+ const msg = err instanceof Error ? err.message : String(err);
256535
+ return respondJson2(res, 200, { ok: false, error: `Admin SDK: ${msg}` }), true;
256536
+ }
256537
+ }
256538
+ if (method === "GET" && pathname === "/api/calendar/buildings") {
256539
+ const token = await getToken2();
256540
+ if (!token) return notConnected2(res);
256541
+ const apiRes = await gFetch2(token, `${ADMIN_RESOURCES_BASE}/buildings?maxResults=500`);
256542
+ if (!apiRes.ok) {
256543
+ return respondJson2(res, 200, { ok: false, error: `Admin SDK: ${apiRes.status}` }), true;
256544
+ }
256545
+ const data = await apiRes.json();
256546
+ const items = Array.isArray(data.buildings) ? data.buildings : [];
256547
+ const buildings = items.map((b) => ({
256548
+ buildingId: String(b.buildingId || ""),
256549
+ buildingName: String(b.buildingName || ""),
256550
+ description: String(b.description || ""),
256551
+ floorNames: Array.isArray(b.floorNames) ? b.floorNames : []
256552
+ }));
256553
+ return respondJson2(res, 200, { ok: true, buildings }), true;
256554
+ }
256555
+ if (method === "POST" && pathname === "/api/calendar/buildings") {
256556
+ const token = await getToken2();
256557
+ if (!token) return notConnected2(res);
256558
+ const reqBody = await parseJsonBody2(req);
256559
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256560
+ const buildingName = String(reqBody.buildingName || "").trim();
256561
+ if (!buildingName) return respondJson2(res, 400, { ok: false, error: "buildingName is required" }), true;
256562
+ const buildingId = String(reqBody.buildingId || "").trim() || buildingName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || `bld-${Date.now()}`;
256563
+ const body = { buildingId, buildingName };
256564
+ if (reqBody.description) body.description = String(reqBody.description).trim();
256565
+ if (reqBody.floorNames) {
256566
+ if (Array.isArray(reqBody.floorNames)) {
256567
+ body.floorNames = reqBody.floorNames.map(String);
256568
+ } else {
256569
+ const raw = String(reqBody.floorNames).trim();
256570
+ const n = Number(raw);
256571
+ if (!isNaN(n) && n > 0 && Number.isInteger(n)) {
256572
+ body.floorNames = Array.from({ length: n }, (_, i2) => String(i2 + 1));
256573
+ } else {
256574
+ body.floorNames = raw.split(",").map((f3) => f3.trim()).filter(Boolean);
256575
+ }
256576
+ }
256577
+ }
256578
+ const apiRes = await gFetch2(token, `${ADMIN_RESOURCES_BASE}/buildings`, {
256579
+ method: "POST",
256580
+ headers: { "Content-Type": "application/json" },
256581
+ body: JSON.stringify(body)
256582
+ });
256583
+ if (!apiRes.ok) {
256584
+ const errText = await apiRes.text().catch(() => "");
256585
+ if (apiRes.status === 403) {
256586
+ return respondJson2(res, 403, { ok: false, error: "permission_denied", message: "Google Workspace admin privileges required" }), true;
256587
+ }
256588
+ return respondJson2(res, 200, { ok: false, error: `Admin SDK: ${apiRes.status} ${errText}` }), true;
256589
+ }
256590
+ const created = await apiRes.json();
256591
+ return respondJson2(res, 200, {
256592
+ ok: true,
256593
+ building: {
256594
+ buildingId: String(created.buildingId || ""),
256595
+ buildingName: String(created.buildingName || ""),
256596
+ description: String(created.description || ""),
256597
+ floorNames: Array.isArray(created.floorNames) ? created.floorNames : []
256598
+ }
256599
+ }), true;
256600
+ }
256601
+ if (method === "POST" && pathname === "/api/calendar/resources") {
256602
+ const token = await getToken2();
256603
+ if (!token) return notConnected2(res);
256604
+ const reqBody = await parseJsonBody2(req);
256605
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256606
+ const resourceName = String(reqBody.resourceName || "").trim();
256607
+ if (!resourceName) return respondJson2(res, 400, { ok: false, error: "resourceName is required" }), true;
256608
+ const resourceId = String(reqBody.resourceId || "").trim() || resourceName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || `res-${Date.now()}`;
256609
+ const body = {
256610
+ resourceId,
256611
+ resourceName,
256612
+ resourceType: String(reqBody.resourceType || "CONFERENCE_ROOM").trim()
256613
+ };
256614
+ if (reqBody.buildingId) body.buildingId = String(reqBody.buildingId).trim();
256615
+ if (reqBody.capacity) body.capacity = Math.max(0, Number(reqBody.capacity) || 0);
256616
+ if (reqBody.floorName) body.floorName = String(reqBody.floorName).trim();
256617
+ if (reqBody.resourceDescription) body.resourceDescription = String(reqBody.resourceDescription).trim();
256618
+ const apiRes = await gFetch2(token, `${ADMIN_RESOURCES_BASE}/calendars`, {
256619
+ method: "POST",
256620
+ headers: { "Content-Type": "application/json" },
256621
+ body: JSON.stringify(body)
256622
+ });
256623
+ if (!apiRes.ok) {
256624
+ const errText = await apiRes.text().catch(() => "");
256625
+ if (apiRes.status === 403) {
256626
+ return respondJson2(res, 403, { ok: false, error: "permission_denied", message: "Google Workspace admin privileges required" }), true;
256627
+ }
256628
+ return respondJson2(res, 200, { ok: false, error: `Admin SDK: ${apiRes.status} ${errText}` }), true;
256629
+ }
256630
+ const created = await apiRes.json();
256631
+ return respondJson2(res, 200, {
256632
+ ok: true,
256633
+ resource: {
256634
+ resourceId: String(created.resourceId || ""),
256635
+ resourceEmail: String(created.resourceEmail || ""),
256636
+ resourceName: String(created.resourceName || ""),
256637
+ capacity: Number(created.capacity || 0),
256638
+ buildingId: String(created.buildingId || ""),
256639
+ resourceType: String(created.resourceType || ""),
256640
+ floorName: String(created.floorName || "")
256641
+ }
256642
+ }), true;
256643
+ }
256644
+ if (method === "PATCH" && pathname.startsWith("/api/calendar/buildings/")) {
256645
+ const buildingId = decodeURIComponent(pathname.slice("/api/calendar/buildings/".length));
256646
+ if (!buildingId) return respondJson2(res, 400, { ok: false, error: "buildingId is required" }), true;
256647
+ const token = await getToken2();
256648
+ if (!token) return notConnected2(res);
256649
+ const reqBody = await parseJsonBody2(req);
256650
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256651
+ const body = {};
256652
+ if (reqBody.buildingName) body.buildingName = String(reqBody.buildingName).trim();
256653
+ if (reqBody.description !== void 0) body.description = String(reqBody.description).trim();
256654
+ if (reqBody.floorNames) {
256655
+ if (Array.isArray(reqBody.floorNames)) {
256656
+ body.floorNames = reqBody.floorNames.map(String);
256657
+ } else {
256658
+ const raw = String(reqBody.floorNames).trim();
256659
+ const n = Number(raw);
256660
+ if (!isNaN(n) && n > 0 && Number.isInteger(n)) {
256661
+ body.floorNames = Array.from({ length: n }, (_, i2) => String(i2 + 1));
256662
+ } else {
256663
+ body.floorNames = raw.split(",").map((f3) => f3.trim()).filter(Boolean);
256664
+ }
256665
+ }
256666
+ }
256667
+ if (Object.keys(body).length === 0) {
256668
+ return respondJson2(res, 400, { ok: false, error: "No fields to update" }), true;
256669
+ }
256670
+ const apiRes = await gFetch2(token, `${ADMIN_RESOURCES_BASE}/buildings/${encodeURIComponent(buildingId)}`, {
256671
+ method: "PATCH",
256672
+ headers: { "Content-Type": "application/json" },
256673
+ body: JSON.stringify(body)
256674
+ });
256675
+ if (!apiRes.ok) {
256676
+ const errText = await apiRes.text().catch(() => "");
256677
+ if (apiRes.status === 403) {
256678
+ return respondJson2(res, 403, { ok: false, error: "permission_denied", message: "Google Workspace admin privileges required" }), true;
256679
+ }
256680
+ if (apiRes.status === 404) {
256681
+ return respondJson2(res, 404, { ok: false, error: "Building not found" }), true;
256682
+ }
256683
+ return respondJson2(res, 200, { ok: false, error: `Admin SDK: ${apiRes.status} ${errText}` }), true;
256684
+ }
256685
+ const updated = await apiRes.json();
256686
+ return respondJson2(res, 200, {
256687
+ ok: true,
256688
+ building: {
256689
+ buildingId: String(updated.buildingId || ""),
256690
+ buildingName: String(updated.buildingName || ""),
256691
+ description: String(updated.description || ""),
256692
+ floorNames: Array.isArray(updated.floorNames) ? updated.floorNames : []
256693
+ }
256694
+ }), true;
256695
+ }
256696
+ if (method === "PATCH" && pathname.startsWith("/api/calendar/resources/")) {
256697
+ const resourceId = decodeURIComponent(pathname.slice("/api/calendar/resources/".length));
256698
+ if (!resourceId) return respondJson2(res, 400, { ok: false, error: "resourceId is required" }), true;
256699
+ const token = await getToken2();
256700
+ if (!token) return notConnected2(res);
256701
+ const reqBody = await parseJsonBody2(req);
256702
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256703
+ const body = {};
256704
+ if (reqBody.resourceName) body.resourceName = String(reqBody.resourceName).trim();
256705
+ if (reqBody.buildingId !== void 0) body.buildingId = String(reqBody.buildingId).trim();
256706
+ if (reqBody.capacity !== void 0) body.capacity = Math.max(0, Number(reqBody.capacity) || 0);
256707
+ if (reqBody.resourceType) body.resourceType = String(reqBody.resourceType).trim();
256708
+ if (reqBody.floorName !== void 0) body.floorName = String(reqBody.floorName).trim();
256709
+ if (reqBody.resourceDescription !== void 0) body.resourceDescription = String(reqBody.resourceDescription).trim();
256710
+ if (Object.keys(body).length === 0) {
256711
+ return respondJson2(res, 400, { ok: false, error: "No fields to update" }), true;
256712
+ }
256713
+ const apiRes = await gFetch2(token, `${ADMIN_RESOURCES_BASE}/calendars/${encodeURIComponent(resourceId)}`, {
256714
+ method: "PATCH",
256715
+ headers: { "Content-Type": "application/json" },
256716
+ body: JSON.stringify(body)
256717
+ });
256718
+ if (!apiRes.ok) {
256719
+ const errText = await apiRes.text().catch(() => "");
256720
+ if (apiRes.status === 403) {
256721
+ return respondJson2(res, 403, { ok: false, error: "permission_denied", message: "Google Workspace admin privileges required" }), true;
256722
+ }
256723
+ if (apiRes.status === 404) {
256724
+ return respondJson2(res, 404, { ok: false, error: "Resource not found" }), true;
256725
+ }
256726
+ return respondJson2(res, 200, { ok: false, error: `Admin SDK: ${apiRes.status} ${errText}` }), true;
256727
+ }
256728
+ const updated = await apiRes.json();
256729
+ return respondJson2(res, 200, {
256730
+ ok: true,
256731
+ resource: {
256732
+ resourceId: String(updated.resourceId || ""),
256733
+ resourceEmail: String(updated.resourceEmail || ""),
256734
+ resourceName: String(updated.resourceName || ""),
256735
+ capacity: Number(updated.capacity || 0),
256736
+ buildingId: String(updated.buildingId || ""),
256737
+ resourceType: String(updated.resourceType || ""),
256738
+ floorName: String(updated.floorName || "")
256739
+ }
256740
+ }), true;
256741
+ }
256742
+ if (method === "POST" && pathname === "/api/calendar/slots") {
256743
+ const token = await getToken2();
256744
+ if (!token) return notConnected2(res);
256745
+ const reqBody = await parseJsonBody2(req);
256746
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256747
+ const resourceId = String(reqBody.resourceId || "").trim();
256748
+ const buildingId = String(reqBody.buildingId || "").trim();
256749
+ const capacity = Number(reqBody.capacity || 0) || 0;
256750
+ const dateSingle = String(reqBody.date || "").trim();
256751
+ const dateFrom = dateSingle || String(reqBody.dateFrom || "").trim();
256752
+ const dateTo = dateSingle || String(reqBody.dateTo || "").trim();
256753
+ const durationMinutes = Math.max(15, Math.min(480, Number(reqBody.durationMinutes || DEFAULT_DURATION_MINUTES) || DEFAULT_DURATION_MINUTES));
256754
+ const workStartHour = Number(reqBody.workStartHour ?? DEFAULT_WORK_START_HOUR);
256755
+ const workEndHour = Number(reqBody.workEndHour ?? DEFAULT_WORK_END_HOUR);
256756
+ const offset = Math.max(0, Number(reqBody.offset || 0) || 0);
256757
+ const bufferMinutes = Math.max(0, Math.min(60, Number(reqBody.bufferMinutes ?? DEFAULT_BUFFER_MINUTES) || 0));
256758
+ const required = Array.isArray(reqBody.required) ? reqBody.required.map(String).filter(Boolean) : [];
256759
+ const optional = Array.isArray(reqBody.optional) ? reqBody.optional.map(String).filter(Boolean) : [];
256760
+ if (!dateFrom || !dateTo) {
256761
+ return respondJson2(res, 400, { ok: false, error: "date or dateFrom/dateTo is required (YYYY-MM-DD)" }), true;
256762
+ }
256763
+ let resourceEmails = [];
256764
+ let resolvedResources = [];
256765
+ if (resourceId) {
256766
+ resourceEmails = [resourceId];
256767
+ } else if (buildingId || capacity > 0) {
256768
+ resolvedResources = await fetchResources(
256769
+ token,
256770
+ buildingId || void 0,
256771
+ capacity > 0 ? capacity : void 0
256772
+ );
256773
+ resourceEmails = resolvedResources.map((r2) => r2.resourceEmail).filter(Boolean);
256774
+ } else if (required.length === 0 && optional.length === 0) {
256775
+ resolvedResources = await fetchResources(token);
256776
+ resourceEmails = resolvedResources.map((r2) => r2.resourceEmail).filter(Boolean);
256777
+ }
256778
+ const allTargets = [...resourceEmails, ...required, ...optional];
256779
+ if (allTargets.length === 0) {
256780
+ return respondJson2(res, 400, { ok: false, error: "At least one target is required (resourceId, buildingId, required, or optional)" }), true;
256781
+ }
256782
+ const tz = String(reqBody.timeZone || "").trim() || DEFAULT_TIMEZONE;
256783
+ const timeMin = makeDateInTz(dateFrom, 0, 0, tz).toISOString();
256784
+ const timeMax = makeDateInTz(dateTo, 23, 59, tz).toISOString();
256785
+ const fbBody = {
256786
+ timeMin,
256787
+ timeMax,
256788
+ timeZone: tz,
256789
+ items: allTargets.map((e2) => ({ id: e2 }))
256790
+ };
256791
+ const fbRes = await gFetch2(token, `${CALENDAR_API_BASE}/freeBusy`, {
256792
+ method: "POST",
256793
+ headers: { "Content-Type": "application/json" },
256794
+ body: JSON.stringify(fbBody)
256795
+ });
256796
+ if (!fbRes.ok) {
256797
+ const err = await fbRes.text().catch(() => "");
256798
+ return respondJson2(res, 200, { ok: false, error: `FreeBusy API: ${fbRes.status} ${err}` }), true;
256799
+ }
256800
+ const fbData = await fbRes.json();
256801
+ const calendars = fbData.calendars || {};
256802
+ const busyMap = {};
256803
+ for (const email of allTargets) {
256804
+ const cal = calendars[email];
256805
+ busyMap[email] = Array.isArray(cal?.busy) ? cal.busy.map((b) => ({ s: new Date(b.start).getTime(), e: new Date(b.end).getTime() })) : [];
256806
+ }
256807
+ const durationMs = durationMinutes * 60 * 1e3;
256808
+ const { slots: top, hasMore } = findSlots({
256809
+ busyMap,
256810
+ required,
256811
+ optional,
256812
+ resourceEmails,
256813
+ durationMs,
256814
+ dateFrom,
256815
+ dateTo,
256816
+ workStartHour,
256817
+ workEndHour,
256818
+ offset,
256819
+ bufferMinutes,
256820
+ timeZone: tz
256821
+ });
256822
+ const slots = top.map((c, i2) => ({
256823
+ rank: i2 + 1,
256824
+ start: new Date(c.start).toISOString(),
256825
+ end: new Date(c.end).toISOString(),
256826
+ durationMinutes,
256827
+ allAvailable: c.allAvailable,
256828
+ availableCount: c.availableCount,
256829
+ totalParticipants: c.totalParticipants,
256830
+ missingOptional: c.missingOptional,
256831
+ bufferBeforeMinutes: Math.round(c.bufferBefore / 6e4),
256832
+ bufferAfterMinutes: Math.round(c.bufferAfter / 6e4),
256833
+ resourceEmails: c.resourceEmails
256834
+ }));
256835
+ const resourceInfo = resolvedResources.length > 0 ? resolvedResources.map((r2) => ({
256836
+ resourceEmail: r2.resourceEmail,
256837
+ resourceName: r2.resourceName,
256838
+ capacity: r2.capacity
256839
+ })) : void 0;
256840
+ return respondJson2(res, 200, {
256841
+ ok: true,
256842
+ slots,
256843
+ hasMore,
256844
+ ...resourceInfo ? { resources: resourceInfo } : {},
256845
+ durationMinutes
256846
+ }), true;
256847
+ }
256848
+ if (method === "POST" && pathname === "/api/calendar/check") {
256849
+ const token = await getToken2();
256850
+ if (!token) return notConnected2(res);
256851
+ const reqBody = await parseJsonBody2(req);
256852
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256853
+ const targets = Array.isArray(reqBody.targets) ? reqBody.targets.map(String).filter(Boolean) : [];
256854
+ const datetime = String(reqBody.datetime || "").trim();
256855
+ const durationMinutes = Math.max(15, Math.min(480, Number(reqBody.durationMinutes || DEFAULT_DURATION_MINUTES) || DEFAULT_DURATION_MINUTES));
256856
+ if (!targets.length) return respondJson2(res, 400, { ok: false, error: "targets array is required" }), true;
256857
+ if (!datetime) return respondJson2(res, 400, { ok: false, error: "datetime is required (ISO 8601)" }), true;
256858
+ const startDate = new Date(datetime);
256859
+ const endDate = new Date(startDate.getTime() + durationMinutes * 60 * 1e3);
256860
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
256861
+ const fbBody = {
256862
+ timeMin: startDate.toISOString(),
256863
+ timeMax: endDate.toISOString(),
256864
+ timeZone: tz,
256865
+ items: targets.map((e2) => ({ id: e2 }))
256866
+ };
256867
+ const fbRes = await gFetch2(token, `${CALENDAR_API_BASE}/freeBusy`, {
256868
+ method: "POST",
256869
+ headers: { "Content-Type": "application/json" },
256870
+ body: JSON.stringify(fbBody)
256871
+ });
256872
+ if (!fbRes.ok) {
256873
+ const err = await fbRes.text().catch(() => "");
256874
+ return respondJson2(res, 200, { ok: false, error: `FreeBusy API: ${fbRes.status} ${err}` }), true;
256875
+ }
256876
+ const fbData = await fbRes.json();
256877
+ const calendars = fbData.calendars || {};
256878
+ let allAvailable = true;
256879
+ const statuses = targets.map((id) => {
256880
+ const cal = calendars[id];
256881
+ const conflicts = Array.isArray(cal?.busy) ? cal.busy.map((b) => ({ start: b.start, end: b.end })) : [];
256882
+ const available = conflicts.length === 0;
256883
+ if (!available) allAvailable = false;
256884
+ return { id, available, conflicts };
256885
+ });
256886
+ return respondJson2(res, 200, {
256887
+ ok: true,
256888
+ allAvailable,
256889
+ datetime: startDate.toISOString(),
256890
+ durationMinutes,
256891
+ statuses
256892
+ }), true;
256893
+ }
256894
+ if (method === "POST" && pathname === "/api/calendar/reserve") {
256895
+ const token = await getToken2();
256896
+ if (!token) return notConnected2(res);
256897
+ const db = await getFirestore2();
256898
+ if (!db) return firestoreUnavailable(res);
256899
+ const reqBody = await parseJsonBody2(req);
256900
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
256901
+ const resourceEmail = String(reqBody.resourceEmail || "").trim();
256902
+ const start = String(reqBody.start || "").trim();
256903
+ const end = String(reqBody.end || "").trim();
256904
+ const title = String(reqBody.title || "").trim();
256905
+ const description = String(reqBody.description || "").trim();
256906
+ const bookerName = String(reqBody.bookerName || "").trim();
256907
+ const bookerPhone = String(reqBody.bookerPhone || "").trim();
256908
+ const bookerEmail = String(reqBody.bookerEmail || "").trim();
256909
+ const callSid = String(reqBody.callSid || "").trim();
256910
+ const bookedVia = String(reqBody.bookedVia || "phone").trim();
256911
+ const isAllDay = reqBody.isAllDay === true;
256912
+ const recurrenceTypeRaw = String(reqBody.recurrenceType || "").trim();
256913
+ const recurrenceType = VALID_RECURRENCE_TYPES.has(recurrenceTypeRaw) ? recurrenceTypeRaw : null;
256914
+ const recurrenceCount = recurrenceType && reqBody.recurrenceCount ? Math.max(1, Math.min(365, Number(reqBody.recurrenceCount) || 0)) : void 0;
256915
+ const recurrenceUntil = recurrenceType && typeof reqBody.recurrenceUntil === "string" ? reqBody.recurrenceUntil.trim() : void 0;
256916
+ if (!start) return respondJson2(res, 400, { ok: false, error: "start is required (ISO 8601)" }), true;
256917
+ if (!end) return respondJson2(res, 400, { ok: false, error: "end is required (ISO 8601)" }), true;
256918
+ if (!title) return respondJson2(res, 400, { ok: false, error: "title is required" }), true;
256919
+ const rrule = recurrenceType ? buildRrule(recurrenceType, recurrenceCount, recurrenceUntil) : null;
256920
+ const calendarId = resourceEmail || "primary";
256921
+ if (resourceEmail) {
256922
+ try {
256923
+ const fbBody = {
256924
+ timeMin: start,
256925
+ timeMax: end,
256926
+ timeZone: DEFAULT_TIMEZONE,
256927
+ items: [{ id: calendarId }]
256928
+ };
256929
+ const fbRes = await gFetch2(token, `${CALENDAR_API_BASE}/freeBusy`, {
256930
+ method: "POST",
256931
+ headers: { "Content-Type": "application/json" },
256932
+ body: JSON.stringify(fbBody)
256933
+ });
256934
+ if (fbRes.ok) {
256935
+ const fbData = await fbRes.json();
256936
+ const fbCalendars = fbData.calendars || {};
256937
+ const busyPeriods = fbCalendars[calendarId]?.busy || [];
256938
+ if (busyPeriods.length > 0) {
256939
+ return respondJson2(res, 409, {
256940
+ ok: false,
256941
+ error: "slot_conflict",
256942
+ message: "The requested time slot is already booked. Please choose a different time.",
256943
+ conflictingPeriods: busyPeriods
256944
+ }), true;
256945
+ }
256946
+ } else {
256947
+ const fbErr = await fbRes.text().catch(() => "");
256948
+ console.warn(
256949
+ `FreeBusy pre-check failed (${fbRes.status}), proceeding without check: ${fbErr}`
256950
+ );
256951
+ }
256952
+ } catch (fbError) {
256953
+ console.warn(
256954
+ "FreeBusy pre-check threw an error, proceeding without check:",
256955
+ fbError instanceof Error ? fbError.message : String(fbError)
256956
+ );
256957
+ }
256958
+ }
256959
+ const eventBody = {
256960
+ summary: title,
256961
+ start: isAllDay ? { date: start } : { dateTime: start },
256962
+ end: isAllDay ? { date: end } : { dateTime: end }
256963
+ };
256964
+ if (description) eventBody.description = description;
256965
+ if (resourceEmail) {
256966
+ eventBody.attendees = [{ email: resourceEmail, resource: true }];
256967
+ }
256968
+ if (rrule) {
256969
+ eventBody.recurrence = [rrule];
256970
+ }
256971
+ const createRes = await gFetch2(
256972
+ token,
256973
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events`,
256974
+ {
256975
+ method: "POST",
256976
+ headers: { "Content-Type": "application/json" },
256977
+ body: JSON.stringify(eventBody)
256978
+ }
256979
+ );
256980
+ if (!createRes.ok) {
256981
+ const err = await createRes.text().catch(() => "");
256982
+ return respondJson2(res, 200, { ok: false, error: `Calendar create failed: ${createRes.status} ${err}` }), true;
256983
+ }
256984
+ const calEvent = await createRes.json();
256985
+ const calendarEventId = String(calEvent.id || "");
256986
+ let resourceName = "";
256987
+ if (resourceEmail) {
256988
+ try {
256989
+ const resources = await fetchResources(token);
256990
+ const match = resources.find((r2) => r2.resourceEmail === resourceEmail);
256991
+ if (match) resourceName = match.resourceName;
256992
+ } catch {
256993
+ }
256994
+ }
256995
+ const reservationId = generateReservationId();
256996
+ const nowIso40 = (/* @__PURE__ */ new Date()).toISOString();
256997
+ const reservation = {
256998
+ reservationId,
256999
+ calendarEventId,
257000
+ resourceCalendarId: resourceEmail,
257001
+ resourceName,
257002
+ startTime: start,
257003
+ endTime: end,
257004
+ title,
257005
+ isAllDay,
257006
+ status: "confirmed",
257007
+ bookerName,
257008
+ bookerPhone,
257009
+ bookerEmail,
257010
+ callSid,
257011
+ bookedVia,
257012
+ createdAt: nowIso40,
257013
+ updatedAt: nowIso40
257014
+ };
257015
+ if (rrule && recurrenceType) {
257016
+ reservation.isRecurring = true;
257017
+ reservation.recurrenceRule = rrule;
257018
+ reservation.recurrenceType = recurrenceType;
257019
+ reservation.recurrenceCount = recurrenceCount || null;
257020
+ reservation.recurrenceUntil = recurrenceUntil || null;
257021
+ }
257022
+ try {
257023
+ if (resourceEmail) {
257024
+ await db.runTransaction(async (transaction) => {
257025
+ const conflictQuery = db.collection("reservations").where("resourceCalendarId", "==", resourceEmail).where("status", "==", "confirmed").where("startTime", "<", end);
257026
+ const conflictSnap = await transaction.get(conflictQuery);
257027
+ const hasConflict = conflictSnap.docs.some((doc) => {
257028
+ const data = doc.data();
257029
+ return data.endTime > start;
257030
+ });
257031
+ if (hasConflict) {
257032
+ throw new Error("RESERVATION_CONFLICT");
257033
+ }
257034
+ transaction.set(
257035
+ db.collection("reservations").doc(reservationId),
257036
+ reservation
257037
+ );
257038
+ });
257039
+ } else {
257040
+ await db.collection("reservations").doc(reservationId).set(reservation);
257041
+ }
257042
+ } catch (err) {
257043
+ const msg = err instanceof Error ? err.message : String(err);
257044
+ if (calendarEventId) {
257045
+ try {
257046
+ await gFetch2(
257047
+ token,
257048
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(calendarEventId)}`,
257049
+ { method: "DELETE" }
257050
+ );
257051
+ } catch (rollbackErr) {
257052
+ console.warn(
257053
+ "Failed to rollback Calendar event after Firestore error:",
257054
+ rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)
257055
+ );
257056
+ }
257057
+ }
257058
+ if (msg === "RESERVATION_CONFLICT") {
257059
+ return respondJson2(res, 409, {
257060
+ ok: false,
257061
+ error: "slot_conflict",
257062
+ message: "The requested time slot is already booked. Please choose a different time."
257063
+ }), true;
257064
+ }
257065
+ return respondJson2(res, 200, {
257066
+ ok: false,
257067
+ error: `Firestore write failed: ${msg}`
257068
+ }), true;
257069
+ }
257070
+ return respondJson2(res, 200, {
257071
+ ok: true,
257072
+ reservationId,
257073
+ calendarEventId: calEvent.id,
257074
+ resourceName,
257075
+ start,
257076
+ end,
257077
+ title,
257078
+ status: "confirmed",
257079
+ ...rrule ? {
257080
+ isRecurring: true,
257081
+ recurrenceType,
257082
+ recurrenceRule: rrule
257083
+ } : {}
257084
+ }), true;
257085
+ }
257086
+ if (method === "GET" && /^\/api\/calendar\/reserve\/[^/]+\/instances$/.test(pathname)) {
257087
+ const parts = pathname.split("/");
257088
+ const reservationId = decodeURIComponent(parts[4]);
257089
+ const db = await getFirestore2();
257090
+ if (!db) return firestoreUnavailable(res);
257091
+ const docSnap = await db.collection("reservations").doc(reservationId).get();
257092
+ if (!docSnap.exists) {
257093
+ return respondJson2(res, 404, { ok: false, error: "Reservation not found" }), true;
257094
+ }
257095
+ const data = docSnap.data();
257096
+ if (!data.isRecurring || !data.calendarEventId) {
257097
+ return respondJson2(res, 400, { ok: false, error: "Not a recurring reservation" }), true;
257098
+ }
257099
+ const token = await getToken2();
257100
+ if (!token) return notConnected2(res);
257101
+ const calendarId = String(data.resourceCalendarId || "") || "primary";
257102
+ const eventId = String(data.calendarEventId);
257103
+ const instancesRes = await gFetch2(
257104
+ token,
257105
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}/instances?maxResults=50`
257106
+ );
257107
+ if (!instancesRes.ok) {
257108
+ const errText = await instancesRes.text().catch(() => "");
257109
+ return respondJson2(res, 502, {
257110
+ ok: false,
257111
+ error: `Failed to fetch instances: ${instancesRes.status}`,
257112
+ detail: errText
257113
+ }), true;
257114
+ }
257115
+ const instancesData = await instancesRes.json();
257116
+ const items = Array.isArray(instancesData.items) ? instancesData.items : [];
257117
+ const instances = items.map((item) => {
257118
+ const startObj = item.start;
257119
+ const endObj = item.end;
257120
+ return {
257121
+ eventId: String(item.id || ""),
257122
+ start: startObj?.dateTime || startObj?.date || "",
257123
+ end: endObj?.dateTime || endObj?.date || "",
257124
+ status: String(item.status || "confirmed")
257125
+ };
257126
+ });
257127
+ return respondJson2(res, 200, { ok: true, reservationId, instances }), true;
257128
+ }
257129
+ if (method === "PATCH" && pathname.startsWith("/api/calendar/reserve/")) {
257130
+ const reservationId = decodeURIComponent(pathname.slice("/api/calendar/reserve/".length));
257131
+ if (!reservationId) return respondJson2(res, 400, { ok: false, error: "reservationId is required" }), true;
257132
+ const reqBody = await parseJsonBody2(req);
257133
+ if (!reqBody) return respondJson2(res, 400, { ok: false, error: "Request body is required" }), true;
257134
+ const start = String(reqBody.start || "").trim();
257135
+ const end = String(reqBody.end || "").trim();
257136
+ const resourceEmail = reqBody.resourceEmail ? String(reqBody.resourceEmail).trim() : "";
257137
+ const isAllDay = reqBody.isAllDay !== void 0 ? reqBody.isAllDay === true : void 0;
257138
+ if (!start || !end) {
257139
+ return respondJson2(res, 400, { ok: false, error: "start and end are required" }), true;
257140
+ }
257141
+ const db = await getFirestore2();
257142
+ if (!db) return firestoreUnavailable(res);
257143
+ const docRef = db.collection("reservations").doc(reservationId);
257144
+ let docSnap = await docRef.get();
257145
+ if (!docSnap.exists && reservationId.startsWith("gcal-")) {
257146
+ const gcalEventId = reservationId.slice(5);
257147
+ const calendarIdForUpsert = resourceEmail || String(reqBody.calendarId || "").trim();
257148
+ if (!calendarIdForUpsert) {
257149
+ return respondJson2(res, 400, { ok: false, error: "calendarId is required for direct calendar bookings" }), true;
257150
+ }
257151
+ const upsertToken = await getToken2();
257152
+ if (!upsertToken) return notConnected2(res);
257153
+ const eventRes = await gFetch2(
257154
+ upsertToken,
257155
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarIdForUpsert)}/events/${encodeURIComponent(gcalEventId)}`
257156
+ );
257157
+ if (!eventRes.ok) {
257158
+ return respondJson2(res, 404, { ok: false, error: "Calendar event not found" }), true;
257159
+ }
257160
+ const gcalEvent = await eventRes.json();
257161
+ const gcalStart = gcalEvent.start;
257162
+ const gcalEnd = gcalEvent.end;
257163
+ const creator = gcalEvent.creator;
257164
+ const isAllDay2 = Boolean(gcalStart?.date && !gcalStart?.dateTime);
257165
+ const upsertDoc = {
257166
+ reservationId,
257167
+ calendarEventId: gcalEventId,
257168
+ resourceCalendarId: calendarIdForUpsert,
257169
+ resourceName: String(gcalEvent.location || ""),
257170
+ startTime: gcalStart?.dateTime || gcalStart?.date || "",
257171
+ endTime: gcalEnd?.dateTime || gcalEnd?.date || "",
257172
+ title: String(gcalEvent.summary || "Direct Calendar Booking"),
257173
+ isAllDay: isAllDay2,
257174
+ status: "confirmed",
257175
+ bookerName: creator?.displayName || creator?.email || "",
257176
+ bookerPhone: "",
257177
+ bookerEmail: creator?.email || "",
257178
+ bookedVia: "direct_calendar",
257179
+ callSid: "",
257180
+ createdAt: String(gcalEvent.created || (/* @__PURE__ */ new Date()).toISOString()),
257181
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
257182
+ };
257183
+ await docRef.set(upsertDoc);
257184
+ docSnap = await docRef.get();
257185
+ }
257186
+ if (!docSnap.exists) {
257187
+ return respondJson2(res, 404, { ok: false, error: "Reservation not found" }), true;
257188
+ }
257189
+ const data = docSnap.data();
257190
+ if (data.status !== "confirmed") {
257191
+ return respondJson2(res, 400, { ok: false, error: "Only confirmed reservations can be modified" }), true;
257192
+ }
257193
+ const token = await getToken2();
257194
+ if (!token) return notConnected2(res);
257195
+ const effectiveResource = resourceEmail || String(data.resourceCalendarId || "");
257196
+ const calendarId = effectiveResource || "primary";
257197
+ const calendarEventId = String(data.calendarEventId || "");
257198
+ if (effectiveResource) {
257199
+ try {
257200
+ const fbBody = {
257201
+ timeMin: start,
257202
+ timeMax: end,
257203
+ timeZone: DEFAULT_TIMEZONE,
257204
+ items: [{ id: calendarId }]
257205
+ };
257206
+ const fbRes = await gFetch2(token, `${CALENDAR_API_BASE}/freeBusy`, {
257207
+ method: "POST",
257208
+ headers: { "Content-Type": "application/json" },
257209
+ body: JSON.stringify(fbBody)
257210
+ });
257211
+ if (fbRes.ok) {
257212
+ const fbData = await fbRes.json();
257213
+ const fbCalendars = fbData.calendars || {};
257214
+ const busyPeriods = fbCalendars[calendarId]?.busy || [];
257215
+ const currentStart = String(data.startTime || "");
257216
+ const currentEnd = String(data.endTime || "");
257217
+ const externalConflicts = busyPeriods.filter((b) => {
257218
+ const bStart = new Date(b.start).getTime();
257219
+ const bEnd = new Date(b.end).getTime();
257220
+ const curStart = currentStart ? new Date(currentStart).getTime() : 0;
257221
+ const curEnd = currentEnd ? new Date(currentEnd).getTime() : 0;
257222
+ return !(bStart === curStart && bEnd === curEnd);
257223
+ });
257224
+ if (externalConflicts.length > 0) {
257225
+ return respondJson2(res, 409, {
257226
+ ok: false,
257227
+ error: "slot_conflict",
257228
+ message: "The requested time slot is already booked by another reservation. Please choose a different time.",
257229
+ conflictingPeriods: externalConflicts
257230
+ }), true;
257231
+ }
257232
+ } else {
257233
+ const fbErr = await fbRes.text().catch(() => "");
257234
+ console.warn(
257235
+ `FreeBusy pre-check for modify failed (${fbRes.status}), proceeding: ${fbErr}`
257236
+ );
257237
+ }
257238
+ } catch (fbError) {
257239
+ console.warn(
257240
+ "FreeBusy pre-check for modify threw an error, proceeding:",
257241
+ fbError instanceof Error ? fbError.message : String(fbError)
257242
+ );
257243
+ }
257244
+ }
257245
+ if (calendarEventId) {
257246
+ const effectiveIsAllDay = isAllDay !== void 0 ? isAllDay : Boolean(data.isAllDay);
257247
+ const patchBody = {};
257248
+ if (start) patchBody.start = effectiveIsAllDay ? { date: start, dateTime: null } : { dateTime: start, date: null };
257249
+ if (end) patchBody.end = effectiveIsAllDay ? { date: end, dateTime: null } : { dateTime: end, date: null };
257250
+ if (resourceEmail && resourceEmail !== String(data.resourceCalendarId || "")) {
257251
+ patchBody.attendees = [{ email: resourceEmail, resource: true }];
257252
+ }
257253
+ const patchRes = await gFetch2(
257254
+ token,
257255
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(calendarEventId)}`,
257256
+ { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(patchBody) }
257257
+ );
257258
+ if (!patchRes.ok) {
257259
+ const errText = await patchRes.text().catch(() => "");
257260
+ return respondJson2(res, 502, { ok: false, error: `Calendar update failed: ${patchRes.status}`, detail: errText }), true;
257261
+ }
257262
+ }
257263
+ const updateFields = {
257264
+ startTime: start,
257265
+ endTime: end,
257266
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
257267
+ };
257268
+ if (isAllDay !== void 0) {
257269
+ updateFields.isAllDay = isAllDay;
257270
+ }
257271
+ if (resourceEmail && resourceEmail !== String(data.resourceCalendarId || "")) {
257272
+ updateFields.resourceCalendarId = resourceEmail;
257273
+ try {
257274
+ const resources = await fetchResources(token);
257275
+ const matched = resources.find((r2) => r2.resourceEmail === resourceEmail);
257276
+ if (matched) updateFields.resourceName = matched.resourceName || resourceEmail;
257277
+ } catch {
257278
+ }
257279
+ }
257280
+ await docRef.update(updateFields);
257281
+ return respondJson2(res, 200, {
257282
+ ok: true,
257283
+ reservationId,
257284
+ start,
257285
+ end,
257286
+ resourceName: String(updateFields.resourceName || data.resourceName || ""),
257287
+ status: "confirmed"
257288
+ }), true;
257289
+ }
257290
+ if (method === "DELETE" && pathname.startsWith("/api/calendar/reserve/")) {
257291
+ const reservationId = decodeURIComponent(pathname.slice("/api/calendar/reserve/".length));
257292
+ if (!reservationId) return respondJson2(res, 400, { ok: false, error: "reservationId is required" }), true;
257293
+ const db = await getFirestore2();
257294
+ if (!db) return firestoreUnavailable(res);
257295
+ const url = new URL(req.url || "/", "http://localhost");
257296
+ const docRef = db.collection("reservations").doc(reservationId);
257297
+ let docSnap = await docRef.get();
257298
+ if (!docSnap.exists && reservationId.startsWith("gcal-")) {
257299
+ const gcalEventId = reservationId.slice(5);
257300
+ const gcalCalendarId = url.searchParams.get("calendarId") || "";
257301
+ if (!gcalCalendarId) {
257302
+ return respondJson2(res, 400, { ok: false, error: "calendarId query param is required for direct calendar bookings" }), true;
257303
+ }
257304
+ const upsertToken = await getToken2();
257305
+ if (!upsertToken) return notConnected2(res);
257306
+ const eventRes = await gFetch2(
257307
+ upsertToken,
257308
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(gcalCalendarId)}/events/${encodeURIComponent(gcalEventId)}`
257309
+ );
257310
+ if (!eventRes.ok) {
257311
+ return respondJson2(res, 404, { ok: false, error: "Calendar event not found" }), true;
257312
+ }
257313
+ const gcalEvent = await eventRes.json();
257314
+ const gcalStart = gcalEvent.start;
257315
+ const gcalEnd = gcalEvent.end;
257316
+ const creator = gcalEvent.creator;
257317
+ const isAllDay = Boolean(gcalStart?.date && !gcalStart?.dateTime);
257318
+ const upsertDoc = {
257319
+ reservationId,
257320
+ calendarEventId: gcalEventId,
257321
+ resourceCalendarId: gcalCalendarId,
257322
+ resourceName: String(gcalEvent.location || ""),
257323
+ startTime: gcalStart?.dateTime || gcalStart?.date || "",
257324
+ endTime: gcalEnd?.dateTime || gcalEnd?.date || "",
257325
+ title: String(gcalEvent.summary || "Direct Calendar Booking"),
257326
+ isAllDay,
257327
+ status: "confirmed",
257328
+ bookerName: creator?.displayName || creator?.email || "",
257329
+ bookerPhone: "",
257330
+ bookerEmail: creator?.email || "",
257331
+ bookedVia: "direct_calendar",
257332
+ callSid: "",
257333
+ createdAt: String(gcalEvent.created || (/* @__PURE__ */ new Date()).toISOString()),
257334
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
257335
+ };
257336
+ await docRef.set(upsertDoc);
257337
+ docSnap = await docRef.get();
257338
+ }
257339
+ if (!docSnap.exists) {
257340
+ return respondJson2(res, 404, { ok: false, error: "Reservation not found" }), true;
257341
+ }
257342
+ const data = docSnap.data();
257343
+ const scope = url.searchParams.get("scope") || "all";
257344
+ const instanceEventId = url.searchParams.get("instanceEventId") || "";
257345
+ const calendarEventId = String(data.calendarEventId || "");
257346
+ const calendarId = String(data.resourceCalendarId || "") || "primary";
257347
+ const isRecurring = Boolean(data.isRecurring);
257348
+ const token = await getToken2();
257349
+ if (isRecurring && scope === "single") {
257350
+ if (!instanceEventId) {
257351
+ return respondJson2(res, 400, {
257352
+ ok: false,
257353
+ error: "instanceEventId is required when scope=single"
257354
+ }), true;
257355
+ }
257356
+ if (token) {
257357
+ try {
257358
+ await gFetch2(
257359
+ token,
257360
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(instanceEventId)}`,
257361
+ { method: "DELETE" }
257362
+ );
257363
+ } catch {
257364
+ }
257365
+ }
257366
+ return respondJson2(res, 200, {
257367
+ ok: true,
257368
+ reservationId,
257369
+ cancelledInstanceEventId: instanceEventId,
257370
+ scope: "single",
257371
+ status: "instance_cancelled"
257372
+ }), true;
257373
+ } else if (isRecurring && scope === "this_and_following") {
257374
+ if (!instanceEventId) {
257375
+ return respondJson2(res, 400, {
257376
+ ok: false,
257377
+ error: "instanceEventId is required when scope=this_and_following to determine the cut-off date"
257378
+ }), true;
257379
+ }
257380
+ if (token && calendarEventId) {
257381
+ try {
257382
+ const instanceRes = await gFetch2(
257383
+ token,
257384
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(instanceEventId)}`
257385
+ );
257386
+ if (instanceRes.ok) {
257387
+ const instanceData = await instanceRes.json();
257388
+ const instanceStart = instanceData.start?.dateTime || "";
257389
+ if (instanceStart) {
257390
+ const cutoffDate = new Date(instanceStart);
257391
+ cutoffDate.setDate(cutoffDate.getDate() - 1);
257392
+ const untilStr = cutoffDate.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
257393
+ const parentRes = await gFetch2(
257394
+ token,
257395
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(calendarEventId)}`
257396
+ );
257397
+ if (parentRes.ok) {
257398
+ const parentData = await parentRes.json();
257399
+ const existingRecurrence = Array.isArray(parentData.recurrence) ? parentData.recurrence : [];
257400
+ const updatedRecurrence = existingRecurrence.map((rule) => {
257401
+ if (!rule.startsWith("RRULE:")) return rule;
257402
+ const parts = rule.split(";").filter(
257403
+ (p) => !p.startsWith("COUNT=") && !p.startsWith("UNTIL=")
257404
+ );
257405
+ return [...parts, `UNTIL=${untilStr}`].join(";");
257406
+ });
257407
+ await gFetch2(
257408
+ token,
257409
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(calendarEventId)}`,
257410
+ {
257411
+ method: "PATCH",
257412
+ headers: { "Content-Type": "application/json" },
257413
+ body: JSON.stringify({ recurrence: updatedRecurrence })
257414
+ }
257415
+ );
257416
+ }
257417
+ }
257418
+ }
257419
+ } catch (err) {
257420
+ console.warn(
257421
+ "Failed to update RRULE for this_and_following cancel:",
257422
+ err instanceof Error ? err.message : String(err)
257423
+ );
257424
+ }
257425
+ }
257426
+ await docRef.update({
257427
+ recurrenceUntil: instanceEventId,
257428
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
257429
+ });
257430
+ return respondJson2(res, 200, {
257431
+ ok: true,
257432
+ reservationId,
257433
+ scope: "this_and_following",
257434
+ status: "truncated"
257435
+ }), true;
257436
+ } else {
257437
+ if (calendarEventId && token) {
257438
+ try {
257439
+ await gFetch2(
257440
+ token,
257441
+ `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(calendarEventId)}`,
257442
+ { method: "DELETE" }
257443
+ );
257444
+ } catch {
257445
+ }
257446
+ }
257447
+ await docRef.update({
257448
+ status: "cancelled",
257449
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
257450
+ });
257451
+ return respondJson2(res, 200, {
257452
+ ok: true,
257453
+ reservationId,
257454
+ scope: "all",
257455
+ status: "cancelled"
257456
+ }), true;
257457
+ }
257458
+ }
257459
+ if (method === "GET" && pathname.startsWith("/api/calendar/reserve/")) {
257460
+ const reservationId = decodeURIComponent(pathname.slice("/api/calendar/reserve/".length));
257461
+ if (!reservationId) return respondJson2(res, 400, { ok: false, error: "reservationId is required" }), true;
257462
+ const db = await getFirestore2();
257463
+ if (!db) return firestoreUnavailable(res);
257464
+ const docSnap = await db.collection("reservations").doc(reservationId).get();
257465
+ if (!docSnap.exists) {
257466
+ return respondJson2(res, 404, { ok: false, error: "Reservation not found" }), true;
257467
+ }
257468
+ return respondJson2(res, 200, { ok: true, reservation: docSnap.data() }), true;
257469
+ }
257470
+ if (method === "GET" && pathname === "/api/calendar/reservations") {
257471
+ const db = await getFirestore2();
257472
+ if (!db) return firestoreUnavailable(res);
257473
+ const url = new URL(req.url || "/", "http://localhost");
257474
+ const status = url.searchParams.get("status") || "";
257475
+ const date = url.searchParams.get("date") || "";
257476
+ const resourceEmail = url.searchParams.get("resourceEmail") || "";
257477
+ const buildingId = url.searchParams.get("buildingId") || "";
257478
+ const limit = Math.max(1, Math.min(200, Number(url.searchParams.get("limit") || DEFAULT_RESERVATION_LIMIT) || DEFAULT_RESERVATION_LIMIT));
257479
+ let query = db.collection("reservations");
257480
+ if (status) {
257481
+ query = query.where("status", "==", status);
257482
+ }
257483
+ if (resourceEmail) {
257484
+ query = query.where("resourceCalendarId", "==", resourceEmail);
257485
+ }
257486
+ if (date) {
257487
+ const dayStart = `${date}T00:00:00`;
257488
+ const dayEnd = `${date}T23:59:59`;
257489
+ query = query.where("startTime", ">=", dayStart).where("startTime", "<=", dayEnd + "Z");
257490
+ }
257491
+ query = query.orderBy("createdAt", "desc").limit(limit);
257492
+ let firestoreDocs;
257493
+ try {
257494
+ const snap = await query.get();
257495
+ firestoreDocs = snap.docs;
257496
+ } catch (err) {
257497
+ const msg = err instanceof Error ? err.message : String(err);
257498
+ return respondJson2(res, 200, { ok: false, error: `Firestore query failed: ${msg}` }), true;
257499
+ }
257500
+ const token = await getToken2();
257501
+ if (!token) {
257502
+ if (firestoreDocs.length === 0) {
257503
+ return respondJson2(res, 200, { ok: true, reservations: [], count: 0, dataSource: "firestore_fallback" }), true;
257504
+ }
257505
+ const reservations = firestoreDocs.map((doc) => doc.data());
257506
+ return respondJson2(res, 200, {
257507
+ ok: true,
257508
+ reservations,
257509
+ count: reservations.length,
257510
+ dataSource: "firestore_fallback"
257511
+ }), true;
257512
+ }
257513
+ const docsWithEventId = [];
257514
+ const docsWithoutEventId = [];
257515
+ for (const doc of firestoreDocs) {
257516
+ const data = doc.data();
257517
+ if (data.calendarEventId) {
257518
+ docsWithEventId.push(data);
257519
+ } else {
257520
+ docsWithoutEventId.push(data);
257521
+ }
257522
+ }
257523
+ const byCalendar = /* @__PURE__ */ new Map();
257524
+ for (const doc of docsWithEventId) {
257525
+ const calId = doc.resourceCalendarId || "primary";
257526
+ const group = byCalendar.get(calId);
257527
+ if (group) {
257528
+ group.push(doc);
257529
+ } else {
257530
+ byCalendar.set(calId, [doc]);
257531
+ }
257532
+ }
257533
+ const additionalCalendarIds = [];
257534
+ if (resourceEmail && !byCalendar.has(resourceEmail)) {
257535
+ additionalCalendarIds.push(resourceEmail);
257536
+ }
257537
+ if (!resourceEmail) {
257538
+ try {
257539
+ const resources = await fetchResources(token, buildingId || void 0);
257540
+ for (const r2 of resources) {
257541
+ if (r2.resourceEmail && !byCalendar.has(r2.resourceEmail)) {
257542
+ additionalCalendarIds.push(r2.resourceEmail);
257543
+ }
257544
+ }
257545
+ } catch (err) {
257546
+ console.warn(
257547
+ "Failed to fetch resource calendars from Admin SDK:",
257548
+ err instanceof Error ? err.message : String(err)
257549
+ );
257550
+ }
257551
+ }
257552
+ for (const calId of additionalCalendarIds) {
257553
+ if (!byCalendar.has(calId)) {
257554
+ byCalendar.set(calId, []);
257555
+ }
257556
+ }
257557
+ let timeMin;
257558
+ let timeMax;
257559
+ if (date) {
257560
+ timeMin = `${date}T00:00:00+09:00`;
257561
+ timeMax = `${date}T23:59:59+09:00`;
257562
+ } else {
257563
+ const startTimes = docsWithEventId.map((d) => d.startTime).filter(Boolean).sort();
257564
+ if (startTimes.length > 0) {
257565
+ const earliest = new Date(startTimes[0]);
257566
+ earliest.setDate(earliest.getDate() - 1);
257567
+ const latest = new Date(startTimes[startTimes.length - 1]);
257568
+ latest.setDate(latest.getDate() + 1);
257569
+ timeMin = earliest.toISOString();
257570
+ timeMax = latest.toISOString();
257571
+ } else {
257572
+ const now = /* @__PURE__ */ new Date();
257573
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
257574
+ const thirtyDaysAhead = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1e3);
257575
+ timeMin = thirtyDaysAgo.toISOString();
257576
+ timeMax = thirtyDaysAhead.toISOString();
257577
+ }
257578
+ }
257579
+ const gcalEventMap = /* @__PURE__ */ new Map();
257580
+ let gcalFetchFailed = false;
257581
+ try {
257582
+ const calendarFetches = Array.from(byCalendar.keys()).map(async (calendarId) => {
257583
+ const eventsUrl = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?timeMin=${encodeURIComponent(timeMin)}&timeMax=${encodeURIComponent(timeMax)}&singleEvents=true&orderBy=startTime&maxResults=250`;
257584
+ const eventsRes = await gFetch2(token, eventsUrl);
257585
+ if (!eventsRes.ok) {
257586
+ console.warn(
257587
+ `GCal events.list failed for calendar ${calendarId}: ${eventsRes.status}`
257588
+ );
257589
+ return;
257590
+ }
257591
+ const eventsData = await eventsRes.json();
257592
+ const items = Array.isArray(eventsData.items) ? eventsData.items : [];
257593
+ for (const item of items) {
257594
+ const eventId = String(item.id || "");
257595
+ if (!eventId) continue;
257596
+ const startObj = item.start;
257597
+ const endObj = item.end;
257598
+ const isAllDay = Boolean(startObj?.date && !startObj?.dateTime);
257599
+ const eventStart = startObj?.dateTime || startObj?.date || "";
257600
+ const eventEnd = endObj?.dateTime || endObj?.date || "";
257601
+ const creatorObj = item.creator;
257602
+ const organizerObj = item.organizer;
257603
+ gcalEventMap.set(eventId, {
257604
+ startTime: eventStart,
257605
+ endTime: eventEnd,
257606
+ title: String(item.summary || ""),
257607
+ isAllDay,
257608
+ gcalStatus: String(item.status || "confirmed"),
257609
+ creatorEmail: creatorObj?.email || "",
257610
+ creatorDisplayName: creatorObj?.displayName || "",
257611
+ organizerEmail: organizerObj?.email || "",
257612
+ organizerDisplayName: organizerObj?.displayName || "",
257613
+ location: String(item.location || ""),
257614
+ created: String(item.created || ""),
257615
+ updated: String(item.updated || ""),
257616
+ calendarId
257617
+ });
257618
+ }
257619
+ });
257620
+ await Promise.all(calendarFetches);
257621
+ } catch (err) {
257622
+ console.warn(
257623
+ "GCal fetch failed, falling back to Firestore:",
257624
+ err instanceof Error ? err.message : String(err)
257625
+ );
257626
+ gcalFetchFailed = true;
257627
+ }
257628
+ if (gcalFetchFailed) {
257629
+ const reservations = firestoreDocs.map((doc) => doc.data());
257630
+ return respondJson2(res, 200, {
257631
+ ok: true,
257632
+ reservations,
257633
+ count: reservations.length,
257634
+ dataSource: "firestore_fallback"
257635
+ }), true;
257636
+ }
257637
+ const mergedReservations = [];
257638
+ for (const fsDoc of docsWithEventId) {
257639
+ const gcalEvent = gcalEventMap.get(fsDoc.calendarEventId);
257640
+ if (gcalEvent) {
257641
+ const effectiveStatus = gcalEvent.gcalStatus === "cancelled" ? "cancelled" : fsDoc.status;
257642
+ mergedReservations.push({
257643
+ reservationId: fsDoc.reservationId,
257644
+ calendarEventId: fsDoc.calendarEventId,
257645
+ resourceCalendarId: fsDoc.resourceCalendarId,
257646
+ resourceName: fsDoc.resourceName,
257647
+ startTime: gcalEvent.startTime,
257648
+ // from GCal
257649
+ endTime: gcalEvent.endTime,
257650
+ // from GCal
257651
+ title: gcalEvent.title,
257652
+ // from GCal
257653
+ isAllDay: gcalEvent.isAllDay,
257654
+ status: effectiveStatus,
257655
+ bookerName: fsDoc.bookerName,
257656
+ // from Firestore
257657
+ bookerPhone: fsDoc.bookerPhone,
257658
+ // from Firestore
257659
+ bookerEmail: fsDoc.bookerEmail,
257660
+ // from Firestore
257661
+ callSid: fsDoc.callSid,
257662
+ // from Firestore
257663
+ bookedVia: fsDoc.bookedVia,
257664
+ // from Firestore
257665
+ createdAt: fsDoc.createdAt,
257666
+ ...fsDoc.updatedAt ? { updatedAt: fsDoc.updatedAt } : {},
257667
+ dataSource: "google_calendar"
257668
+ });
257669
+ } else {
257670
+ mergedReservations.push({
257671
+ reservationId: fsDoc.reservationId,
257672
+ calendarEventId: fsDoc.calendarEventId,
257673
+ resourceCalendarId: fsDoc.resourceCalendarId,
257674
+ resourceName: fsDoc.resourceName,
257675
+ startTime: fsDoc.startTime,
257676
+ // from Firestore (stale)
257677
+ endTime: fsDoc.endTime,
257678
+ // from Firestore (stale)
257679
+ title: fsDoc.title,
257680
+ // from Firestore (stale)
257681
+ isAllDay: false,
257682
+ status: fsDoc.status === "confirmed" ? "cancelled" : fsDoc.status,
257683
+ bookerName: fsDoc.bookerName,
257684
+ bookerPhone: fsDoc.bookerPhone,
257685
+ bookerEmail: fsDoc.bookerEmail,
257686
+ callSid: fsDoc.callSid,
257687
+ bookedVia: fsDoc.bookedVia,
257688
+ createdAt: fsDoc.createdAt,
257689
+ ...fsDoc.updatedAt ? { updatedAt: fsDoc.updatedAt } : {},
257690
+ dataSource: "stale"
257691
+ });
257692
+ }
257693
+ }
257694
+ for (const fsDoc of docsWithoutEventId) {
257695
+ mergedReservations.push({
257696
+ reservationId: fsDoc.reservationId,
257697
+ calendarEventId: "",
257698
+ resourceCalendarId: fsDoc.resourceCalendarId,
257699
+ resourceName: fsDoc.resourceName,
257700
+ startTime: fsDoc.startTime,
257701
+ endTime: fsDoc.endTime,
257702
+ title: fsDoc.title,
257703
+ isAllDay: false,
257704
+ status: fsDoc.status,
257705
+ bookerName: fsDoc.bookerName,
257706
+ bookerPhone: fsDoc.bookerPhone,
257707
+ bookerEmail: fsDoc.bookerEmail,
257708
+ callSid: fsDoc.callSid,
257709
+ bookedVia: fsDoc.bookedVia,
257710
+ createdAt: fsDoc.createdAt,
257711
+ ...fsDoc.updatedAt ? { updatedAt: fsDoc.updatedAt } : {},
257712
+ dataSource: "firestore_fallback"
257713
+ });
257714
+ }
257715
+ const matchedEventIds = new Set(docsWithEventId.map((d) => d.calendarEventId));
257716
+ for (const [eventId, gcalEvent] of gcalEventMap) {
257717
+ if (matchedEventIds.has(eventId)) continue;
257718
+ if (status) {
257719
+ const gcalEffectiveStatus = gcalEvent.gcalStatus === "cancelled" ? "cancelled" : "confirmed";
257720
+ if (gcalEffectiveStatus !== status) continue;
257721
+ }
257722
+ mergedReservations.push({
257723
+ reservationId: `gcal-${eventId}`,
257724
+ calendarEventId: eventId,
257725
+ resourceCalendarId: gcalEvent.calendarId,
257726
+ resourceName: gcalEvent.location || gcalEvent.organizerDisplayName || "",
257727
+ startTime: gcalEvent.startTime,
257728
+ endTime: gcalEvent.endTime,
257729
+ title: gcalEvent.title || "Direct Calendar Booking",
257730
+ isAllDay: gcalEvent.isAllDay,
257731
+ status: gcalEvent.gcalStatus === "cancelled" ? "cancelled" : "confirmed",
257732
+ bookerName: gcalEvent.creatorDisplayName || gcalEvent.creatorEmail || "",
257733
+ bookerPhone: "",
257734
+ bookerEmail: gcalEvent.creatorEmail,
257735
+ callSid: "",
257736
+ bookedVia: "direct_calendar",
257737
+ createdAt: gcalEvent.created,
257738
+ updatedAt: gcalEvent.updated,
257739
+ dataSource: "google_calendar_only"
257740
+ });
257741
+ }
257742
+ return respondJson2(res, 200, {
257743
+ ok: true,
257744
+ reservations: mergedReservations,
257745
+ count: mergedReservations.length,
257746
+ dataSource: "google_calendar"
257747
+ }), true;
257748
+ }
257749
+ return false;
257750
+ }
257751
+ var ADMIN_RESOURCES_BASE, CALENDAR_API_BASE, DEFAULT_WORK_START_HOUR, DEFAULT_WORK_END_HOUR, DEFAULT_DURATION_MINUTES, DEFAULT_RESERVATION_LIMIT, SLOT_STEP_MS, DEFAULT_BUFFER_MINUTES, TOP_SLOT_COUNT, DEFAULT_TIMEZONE, _firestoreInstance2, _firestoreInitPromise2, VALID_RECURRENCE_TYPES;
257752
+ var init_calendar_api_routes = __esm({
257753
+ "services/desktop/routes/calendar-api-routes.ts"() {
257754
+ init_desktop_server_helpers();
257755
+ init_google_oauth();
257756
+ init_date_tz();
257757
+ ADMIN_RESOURCES_BASE = "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/resources";
257758
+ CALENDAR_API_BASE = "https://www.googleapis.com/calendar/v3";
257759
+ DEFAULT_WORK_START_HOUR = 9;
257760
+ DEFAULT_WORK_END_HOUR = 18;
257761
+ DEFAULT_DURATION_MINUTES = 60;
257762
+ DEFAULT_RESERVATION_LIMIT = 50;
257763
+ SLOT_STEP_MS = 5 * 60 * 1e3;
257764
+ DEFAULT_BUFFER_MINUTES = 10;
257765
+ TOP_SLOT_COUNT = 3;
257766
+ DEFAULT_TIMEZONE = "Asia/Tokyo";
257767
+ _firestoreInstance2 = null;
257768
+ _firestoreInitPromise2 = null;
257769
+ VALID_RECURRENCE_TYPES = /* @__PURE__ */ new Set(["daily", "weekly", "biweekly", "monthly"]);
257770
+ }
257771
+ });
256285
257772
  function resolveMaxConcurrency() {
256286
257773
  const envRaw = String(process.env.MARIA_DESKTOP_MAX_CONCURRENCY || "").trim();
256287
257774
  if (envRaw) {
@@ -257435,6 +258922,10 @@ function createDesktopRequestHandler(s2) {
257435
258922
  const handled = await handlePhoneRoute(method, pathname, req, res);
257436
258923
  if (handled) return;
257437
258924
  }
258925
+ if (pathname.startsWith("/api/calendar/")) {
258926
+ const handled = await handleCalendarApiRoute(method, pathname, req, res);
258927
+ if (handled) return;
258928
+ }
257438
258929
  {
257439
258930
  const handled = await handleGuiCompatRoute(method, pathname, url, req, res, cwd, s2);
257440
258931
  if (handled) return;
@@ -257649,6 +259140,7 @@ var init_desktop_server = __esm({
257649
259140
  init_google_oauth_routes();
257650
259141
  init_google_api_routes();
257651
259142
  init_phone_routes();
259143
+ init_calendar_api_routes();
257652
259144
  init_desktop_server_helpers();
257653
259145
  init_desktop_data_readers();
257654
259146
  init_desktop_job_queue();
@@ -257991,7 +259483,7 @@ function createSpreadsheetField() {
257991
259483
  const wantsJson = hasLiteFlag(ctx.parsed, "json");
257992
259484
  const titleOpt = typeof ctx.parsed.options.title === "string" ? String(ctx.parsed.options.title).trim() : "";
257993
259485
  const outBase = typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "";
257994
- const outBaseRel = (outBase || "spreadsheet").replace(/\\/g, "/");
259486
+ const outBaseRel = (outBase || ".maria/desktop/spreadsheet").replace(/\\/g, "/");
257995
259487
  const sheetsN = parseSheetsCount(ctx.parsed.options.sheets, 1);
257996
259488
  const stamp = nowStamp3();
257997
259489
  const outDirRel = path87__namespace.posix.join(
@@ -259658,7 +261150,7 @@ function createPaperField() {
259658
261150
  const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : 6;
259659
261151
  return Number.isFinite(n) ? Math.max(3, Math.min(20, Math.floor(n))) : 6;
259660
261152
  })();
259661
- const outBaseRel = (typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "paper").replace(/\\/g, "/") || "paper";
261153
+ const outBaseRel = (typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : ".maria/desktop/paper").replace(/\\/g, "/") || ".maria/desktop/paper";
259662
261154
  const wantsPdf = hasLiteFlag(ctx.parsed, "pdf") || !hasLiteFlag(ctx.parsed, "pdf") && !hasLiteFlag(ctx.parsed, "docx");
259663
261155
  const wantsDocx = hasLiteFlag(ctx.parsed, "docx") || !hasLiteFlag(ctx.parsed, "pdf") && !hasLiteFlag(ctx.parsed, "docx");
259664
261156
  const planOnly = hasLiteFlag(ctx.parsed, "plan-only") || hasLiteFlag(ctx.parsed, "dry-run");
@@ -260163,7 +261655,7 @@ function createExamField() {
260163
261655
  const inlineArgs = ctx.parsed.args.join(" ").trim();
260164
261656
  const langRaw = typeof ctx.parsed.options.lang === "string" ? String(ctx.parsed.options.lang).trim().toLowerCase() : "";
260165
261657
  const langExplicit = langRaw === "ja" || langRaw === "jp" ? "ja" : langRaw === "en" ? "en" : "";
260166
- const outBaseRel = (typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "exam").replace(/\\/g, "/") || "exam";
261658
+ const outBaseRel = (typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : ".maria/desktop/exam").replace(/\\/g, "/") || ".maria/desktop/exam";
260167
261659
  const wantsPdf = hasLiteFlag(ctx.parsed, "pdf") || !hasLiteFlag(ctx.parsed, "pdf") && !hasLiteFlag(ctx.parsed, "docx");
260168
261660
  const wantsDocx = hasLiteFlag(ctx.parsed, "docx") || !hasLiteFlag(ctx.parsed, "pdf") && !hasLiteFlag(ctx.parsed, "docx");
260169
261661
  const planOnly = hasLiteFlag(ctx.parsed, "plan-only") || hasLiteFlag(ctx.parsed, "dry-run");
@@ -262207,8 +263699,27 @@ async function queryFreeBusy(token, emails, from, to, signal) {
262207
263699
  }
262208
263700
  async function findMeetingSlots(token, required, optional, durationMin, dateFrom, dateTo, workStart, workEnd, signal, extra) {
262209
263701
  let resourceCalendarIds = [];
263702
+ const resourceNameMap = {};
262210
263703
  if (extra?.resourceId) {
262211
263704
  resourceCalendarIds = [extra.resourceId];
263705
+ try {
263706
+ const adminBaseUrl = "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/resources/calendars";
263707
+ const qParams = new URLSearchParams({ maxResults: "5", query: `resourceEmail="${extra.resourceId}"` });
263708
+ const rRes = await fetch(`${adminBaseUrl}?${qParams}`, {
263709
+ headers: { Authorization: `Bearer ${token}` },
263710
+ ...signal ? { signal } : {}
263711
+ });
263712
+ if (rRes.ok) {
263713
+ const rData = await rRes.json();
263714
+ const items = Array.isArray(rData.items) ? rData.items : [];
263715
+ for (const r2 of items) {
263716
+ const email = String(r2.resourceEmail || "");
263717
+ const name = String(r2.resourceName || r2.generatedResourceName || "");
263718
+ if (email && name) resourceNameMap[email] = name;
263719
+ }
263720
+ }
263721
+ } catch {
263722
+ }
262212
263723
  } else if (extra?.buildingId || extra?.capacity) {
262213
263724
  try {
262214
263725
  const adminBaseUrl = "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/resources/calendars";
@@ -262226,6 +263737,11 @@ async function findMeetingSlots(token, required, optional, durationMin, dateFrom
262226
263737
  return true;
262227
263738
  });
262228
263739
  resourceCalendarIds = filtered.map((r2) => String(r2.resourceEmail || "")).filter(Boolean);
263740
+ for (const r2 of items) {
263741
+ const email = String(r2.resourceEmail || "");
263742
+ const name = String(r2.resourceName || r2.generatedResourceName || "");
263743
+ if (email && name) resourceNameMap[email] = name;
263744
+ }
262229
263745
  }
262230
263746
  } catch {
262231
263747
  }
@@ -262277,6 +263793,7 @@ async function findMeetingSlots(token, required, optional, durationMin, dateFrom
262277
263793
  let requiredOk = true;
262278
263794
  let availCount = 0;
262279
263795
  const missingOpt = [];
263796
+ const slotAvailResources = [];
262280
263797
  for (const email of allEmails) {
262281
263798
  const busy = busyMap[email] || [];
262282
263799
  const isBusy = busy.some((b) => b.s < slotE && b.e > slotS);
@@ -262288,6 +263805,9 @@ async function findMeetingSlots(token, required, optional, durationMin, dateFrom
262288
263805
  }
262289
263806
  } else {
262290
263807
  availCount++;
263808
+ if (resourceEmails.includes(email)) {
263809
+ slotAvailResources.push(email);
263810
+ }
262291
263811
  }
262292
263812
  }
262293
263813
  if (!requiredOk) continue;
@@ -262306,7 +263826,7 @@ async function findMeetingSlots(token, required, optional, durationMin, dateFrom
262306
263826
  }
262307
263827
  if (!isFinite(bufBefore)) bufBefore = 12 * 60 * 60 * 1e3;
262308
263828
  if (!isFinite(bufAfter)) bufAfter = 12 * 60 * 60 * 1e3;
262309
- candidates.push({ start: slotS, end: slotE, allAvailable: availCount === allEmails.length, availableCount: availCount, missingOptional: missingOpt, bufferBefore: bufBefore, bufferAfter: bufAfter });
263829
+ candidates.push({ start: slotS, end: slotE, allAvailable: availCount === allEmails.length, availableCount: availCount, missingOptional: missingOpt, bufferBefore: bufBefore, bufferAfter: bufAfter, availableResources: slotAvailResources });
262310
263830
  }
262311
263831
  }
262312
263832
  const hasSuf = (c) => c.bufferBefore >= minBufferMs && c.bufferAfter >= minBufferMs;
@@ -262347,6 +263867,10 @@ async function findMeetingSlots(token, required, optional, durationMin, dateFrom
262347
263867
  const label = c.allAvailable ? `All ${allEmails.length} available` : `${c.availableCount}/${allEmails.length} available`;
262348
263868
  lines.push(` #${i2 + 1}: ${s2.toLocaleString()} \u2192 ${e2.toLocaleTimeString()} (${label})`);
262349
263869
  if (c.missingOptional.length) lines.push(` Unavailable (optional): ${c.missingOptional.join(", ")}`);
263870
+ if (c.availableResources.length > 0) {
263871
+ const names = c.availableResources.map((e3) => resourceNameMap[e3] || e3);
263872
+ lines.push(` Available resources: ${names.join(", ")}`);
263873
+ }
262350
263874
  lines.push(` Buffer: ${Math.round(c.bufferBefore / 6e4)} min before, ${Math.round(c.bufferAfter / 6e4)} min after`);
262351
263875
  lines.push("");
262352
263876
  }
@@ -262364,7 +263888,9 @@ async function findMeetingSlots(token, required, optional, durationMin, dateFrom
262364
263888
  missingOptional: c.missingOptional,
262365
263889
  bufferBeforeMinutes: Math.round(c.bufferBefore / 6e4),
262366
263890
  bufferAfterMinutes: Math.round(c.bufferAfter / 6e4),
262367
- bufferMinutes: Math.round((c.bufferBefore + c.bufferAfter) / 6e4)
263891
+ bufferMinutes: Math.round((c.bufferBefore + c.bufferAfter) / 6e4),
263892
+ resourceEmails: c.availableResources,
263893
+ resourceNames: c.availableResources.map((e2) => resourceNameMap[e2] || e2)
262368
263894
  }))
262369
263895
  }
262370
263896
  };
@@ -262599,7 +264125,7 @@ function formatImportSummary(results, totalRecords, isDryRun) {
262599
264125
  async function fetchExistingBuildingIds(token, signal) {
262600
264126
  const ids = /* @__PURE__ */ new Set();
262601
264127
  try {
262602
- const res = await fetch(`${ADMIN_RESOURCES_BASE}/buildings?maxResults=200`, {
264128
+ const res = await fetch(`${ADMIN_RESOURCES_BASE2}/buildings?maxResults=200`, {
262603
264129
  headers: { Authorization: `Bearer ${token}` },
262604
264130
  ...signal ? { signal } : {}
262605
264131
  });
@@ -262619,7 +264145,7 @@ async function fetchExistingBuildingIds(token, signal) {
262619
264145
  async function listResources(token, buildingId, signal) {
262620
264146
  const params = new URLSearchParams({ maxResults: "200" });
262621
264147
  if (buildingId) params.set("query", `buildingId="${buildingId}"`);
262622
- const res = await fetch(`${ADMIN_RESOURCES_BASE}/calendars?${params}`, {
264148
+ const res = await fetch(`${ADMIN_RESOURCES_BASE2}/calendars?${params}`, {
262623
264149
  headers: { Authorization: `Bearer ${token}` },
262624
264150
  ...signal ? { signal } : {}
262625
264151
  });
@@ -262647,7 +264173,7 @@ async function listResources(token, buildingId, signal) {
262647
264173
  return { text: lines.join("\n"), json: { ok: true, resources: items } };
262648
264174
  }
262649
264175
  async function getResource(token, resourceId, signal) {
262650
- const res = await fetch(`${ADMIN_RESOURCES_BASE}/calendars/${encodeURIComponent(resourceId)}`, {
264176
+ const res = await fetch(`${ADMIN_RESOURCES_BASE2}/calendars/${encodeURIComponent(resourceId)}`, {
262651
264177
  headers: { Authorization: `Bearer ${token}` },
262652
264178
  ...signal ? { signal } : {}
262653
264179
  });
@@ -262678,7 +264204,7 @@ async function createResource(token, params, signal) {
262678
264204
  if (params.capacity) body.capacity = params.capacity;
262679
264205
  if (params.floorName) body.floorName = params.floorName;
262680
264206
  if (params.description) body.resourceDescription = params.description;
262681
- const res = await fetch(`${ADMIN_RESOURCES_BASE}/calendars`, {
264207
+ const res = await fetch(`${ADMIN_RESOURCES_BASE2}/calendars`, {
262682
264208
  method: "POST",
262683
264209
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
262684
264210
  body: JSON.stringify(body),
@@ -262769,7 +264295,7 @@ async function bulkImportResources(token, filePath4, signal, dryRun = false) {
262769
264295
  if (rec.floor || rec.floorName) body.floorName = rec.floor || rec.floorName;
262770
264296
  if (rec.description || rec.resourceDescription) body.resourceDescription = rec.description || rec.resourceDescription;
262771
264297
  try {
262772
- const createRes = await fetch(`${ADMIN_RESOURCES_BASE}/calendars`, {
264298
+ const createRes = await fetch(`${ADMIN_RESOURCES_BASE2}/calendars`, {
262773
264299
  method: "POST",
262774
264300
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
262775
264301
  body: JSON.stringify(body),
@@ -262800,7 +264326,7 @@ async function bulkImportResources(token, filePath4, signal, dryRun = false) {
262800
264326
  };
262801
264327
  }
262802
264328
  async function listBuildings(token, signal) {
262803
- const res = await fetch(`${ADMIN_RESOURCES_BASE}/buildings?maxResults=200`, {
264329
+ const res = await fetch(`${ADMIN_RESOURCES_BASE2}/buildings?maxResults=200`, {
262804
264330
  headers: { Authorization: `Bearer ${token}` },
262805
264331
  ...signal ? { signal } : {}
262806
264332
  });
@@ -262836,7 +264362,7 @@ async function createBuilding(token, params, signal) {
262836
264362
  body.floorNames = params.floors.split(",").map((f3) => f3.trim()).filter(Boolean);
262837
264363
  }
262838
264364
  }
262839
- const res = await fetch(`${ADMIN_RESOURCES_BASE}/buildings`, {
264365
+ const res = await fetch(`${ADMIN_RESOURCES_BASE2}/buildings`, {
262840
264366
  method: "POST",
262841
264367
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
262842
264368
  body: JSON.stringify(body),
@@ -262924,7 +264450,7 @@ async function bulkImportBuildings(token, filePath4, signal, dryRun = false) {
262924
264450
  body.floorNames = n > 0 ? Array.from({ length: n }, (_, i22) => String(i22 + 1)) : f3.split(",").map((s2) => s2.trim()).filter(Boolean);
262925
264451
  }
262926
264452
  try {
262927
- const createRes = await fetch(`${ADMIN_RESOURCES_BASE}/buildings`, {
264453
+ const createRes = await fetch(`${ADMIN_RESOURCES_BASE2}/buildings`, {
262928
264454
  method: "POST",
262929
264455
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
262930
264456
  body: JSON.stringify(body),
@@ -263003,7 +264529,7 @@ async function checkAvailability(token, targets, datetime, durationMinutes, sign
263003
264529
  function createGcalField() {
263004
264530
  return { commandId: "gcal", worker: new GcalWorker(), checker: new AlwaysPassChecker11() };
263005
264531
  }
263006
- var GcalWorker, ADMIN_RESOURCES_BASE, EMAIL_REGEX, AlwaysPassChecker11;
264532
+ var GcalWorker, ADMIN_RESOURCES_BASE2, EMAIL_REGEX, AlwaysPassChecker11;
263007
264533
  var init_gcal_field = __esm({
263008
264534
  "commands/google/gcal.field.ts"() {
263009
264535
  init_base2();
@@ -263180,7 +264706,7 @@ var init_gcal_field = __esm({
263180
264706
  }
263181
264707
  ];
263182
264708
  };
263183
- ADMIN_RESOURCES_BASE = "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/resources";
264709
+ ADMIN_RESOURCES_BASE2 = "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/resources";
263184
264710
  EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
263185
264711
  AlwaysPassChecker11 = class extends LiteCheckerAgent {
263186
264712
  commandId = "gcal";
@@ -286736,7 +288262,7 @@ var require_firestore = __commonJS({
286736
288262
  "../node_modules/.pnpm/firebase-admin@13.6.0_encoding@0.1.13/node_modules/firebase-admin/lib/firestore/index.js"(exports2) {
286737
288263
  Object.defineProperty(exports2, "__esModule", { value: true });
286738
288264
  exports2.FirebaseFirestoreError = exports2.setLogFunction = exports2.v1 = exports2.WriteResult = exports2.WriteBatch = exports2.Transaction = exports2.Timestamp = exports2.QuerySnapshot = exports2.QueryPartition = exports2.QueryDocumentSnapshot = exports2.Query = exports2.GrpcStatus = exports2.GeoPoint = exports2.Firestore = exports2.Filter = exports2.FieldValue = exports2.FieldPath = exports2.DocumentSnapshot = exports2.DocumentReference = exports2.CollectionReference = exports2.CollectionGroup = exports2.BundleBuilder = exports2.BulkWriter = exports2.AggregateQuerySnapshot = exports2.AggregateQuery = exports2.AggregateField = void 0;
286739
- exports2.getFirestore = getFirestore3;
288265
+ exports2.getFirestore = getFirestore4;
286740
288266
  exports2.initializeFirestore = initializeFirestore2;
286741
288267
  var app_1 = require_app();
286742
288268
  var firestore_internal_1 = require_firestore_internal();
@@ -286817,7 +288343,7 @@ var require_firestore = __commonJS({
286817
288343
  Object.defineProperty(exports2, "setLogFunction", { enumerable: true, get: function() {
286818
288344
  return firestore_1.setLogFunction;
286819
288345
  } });
286820
- function getFirestore3(appOrDatabaseId, optionalDatabaseId) {
288346
+ function getFirestore4(appOrDatabaseId, optionalDatabaseId) {
286821
288347
  const app = typeof appOrDatabaseId === "object" ? appOrDatabaseId : (0, app_1.getApp)();
286822
288348
  const databaseId = (typeof appOrDatabaseId === "string" ? appOrDatabaseId : optionalDatabaseId) || path_1.DEFAULT_DATABASE_ID;
286823
288349
  const firebaseApp = app;
@@ -286839,7 +288365,7 @@ var require_firestore = __commonJS({
286839
288365
  });
286840
288366
 
286841
288367
  // ../node_modules/.pnpm/firebase-admin@13.6.0_encoding@0.1.13/node_modules/firebase-admin/lib/esm/firestore/index.js
286842
- var import_firestore2, getFirestore2;
288368
+ var import_firestore2, getFirestore3;
286843
288369
  var init_firestore = __esm({
286844
288370
  "../node_modules/.pnpm/firebase-admin@13.6.0_encoding@0.1.13/node_modules/firebase-admin/lib/esm/firestore/index.js"() {
286845
288371
  import_firestore2 = __toESM(require_firestore(), 1);
@@ -286867,7 +288393,7 @@ var init_firestore = __esm({
286867
288393
  import_firestore2.default.Transaction;
286868
288394
  import_firestore2.default.WriteBatch;
286869
288395
  import_firestore2.default.WriteResult;
286870
- getFirestore2 = import_firestore2.default.getFirestore;
288396
+ getFirestore3 = import_firestore2.default.getFirestore;
286871
288397
  import_firestore2.default.initializeFirestore;
286872
288398
  import_firestore2.default.setLogFunction;
286873
288399
  import_firestore2.default.v1;
@@ -287173,7 +288699,7 @@ var init_billing_pl_field = __esm({
287173
288699
  json: { error: "access_denied", reason: accessCheck.reason }
287174
288700
  };
287175
288701
  }
287176
- const firestore = getFirestore2();
288702
+ const firestore = getFirestore3();
287177
288703
  const tenantPaths = new TenantPaths(tenantId);
287178
288704
  let targetProjectId = projectId;
287179
288705
  if (isNaturalLanguage && projectNameFromNL && !projectId) {
@@ -287495,7 +289021,7 @@ function createCompetitorsField() {
287495
289021
  const maxRoundsRaw = typeof ctx.parsed.options["max-rounds"] === "string" ? Number(ctx.parsed.options["max-rounds"]) : 10;
287496
289022
  const maxRounds = Number.isFinite(maxRoundsRaw) ? Math.max(1, Math.min(20, Math.floor(maxRoundsRaw))) : 10;
287497
289023
  const outBase = typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "";
287498
- const outBaseRel = (outBase || "competitors").replace(/\\/g, "/");
289024
+ const outBaseRel = (outBase || ".maria/desktop/competitors").replace(/\\/g, "/");
287499
289025
  const inOpt = typeof ctx.parsed.options.in === "string" ? String(ctx.parsed.options.in).trim() : "";
287500
289026
  let productDesc = ctx.parsed.args.join(" ").trim();
287501
289027
  if (inOpt) {
@@ -288179,8 +289705,9 @@ function computeWeekWindows(untilDateStr, totalDays) {
288179
289705
  }
288180
289706
  function sparkline(scores, min = 1, max = 5) {
288181
289707
  return scores.map((s2) => {
288182
- const idx = Math.round((s2 - min) / (max - min) * (SPARK_CHARS.length - 1));
288183
- return SPARK_CHARS[Math.max(0, Math.min(SPARK_CHARS.length - 1, idx))];
289708
+ const safe = Number.isFinite(s2) ? s2 : 3;
289709
+ const idx = Math.round((safe - min) / (max - min) * (SPARK_CHARS.length - 1));
289710
+ return SPARK_CHARS[Math.max(0, Math.min(SPARK_CHARS.length - 1, idx))] ?? SPARK_CHARS[4];
288184
289711
  }).join("");
288185
289712
  }
288186
289713
  function computeTrend(scores) {
@@ -288191,7 +289718,7 @@ function computeTrend(scores) {
288191
289718
  let num = 0;
288192
289719
  let den = 0;
288193
289720
  for (let i2 = 0; i2 < n; i2++) {
288194
- num += (i2 - xMean) * ((scores[i2] ?? 3) - yMean);
289721
+ num += (i2 - xMean) * (safeScore(scores[i2] ?? 3) - yMean);
288195
289722
  den += (i2 - xMean) ** 2;
288196
289723
  }
288197
289724
  const slope = den === 0 ? 0 : num / den;
@@ -288209,6 +289736,28 @@ function calcMean(values) {
288209
289736
  if (values.length === 0) return 0;
288210
289737
  return values.reduce((a, b) => a + b, 0) / values.length;
288211
289738
  }
289739
+ function clamp(v, lo, hi) {
289740
+ return Math.min(hi, Math.max(lo, v));
289741
+ }
289742
+ function safeScore(v) {
289743
+ const n = typeof v === "string" ? parseFloat(v) : Number(v);
289744
+ if (!Number.isFinite(n)) return 3;
289745
+ return clamp(n, 1, 5);
289746
+ }
289747
+ function sanitizeSubDimensions(raw, expected) {
289748
+ if (!Array.isArray(raw) || raw.length === 0) {
289749
+ return expected.map((s2) => ({ id: s2.id, score: 3, evidence: ["Parse incomplete"] }));
289750
+ }
289751
+ const lookup = /* @__PURE__ */ new Map();
289752
+ for (const r2 of raw) {
289753
+ if (r2 && typeof r2.id === "string") lookup.set(r2.id.trim().toLowerCase(), r2);
289754
+ }
289755
+ return expected.map((s2) => {
289756
+ const found = lookup.get(s2.id.toLowerCase());
289757
+ if (found) return { id: s2.id, score: safeScore(found.score), evidence: Array.isArray(found.evidence) ? found.evidence : ["(no evidence)"] };
289758
+ return { id: s2.id, score: 3, evidence: ["Sub-dimension not returned by evaluator"] };
289759
+ });
289760
+ }
288212
289761
  function trendEmoji(t2) {
288213
289762
  return t2 === "improving" ? "\u2191" : t2 === "declining" ? "\u2193" : "\u2192";
288214
289763
  }
@@ -288226,7 +289775,7 @@ function computeAllTrends(weeks) {
288226
289775
  const weeklyScores = weeks.map((w) => {
288227
289776
  const de = w.dimensionEvals.find((e2) => e2.dimensionId === dim.id);
288228
289777
  const sd = de?.subDimensions.find((s2) => s2.id === sub.id);
288229
- return sd?.score ?? 3;
289778
+ return safeScore(sd?.score ?? 3);
288230
289779
  });
288231
289780
  trends.push({
288232
289781
  id: sub.id,
@@ -288241,7 +289790,7 @@ function computeAllTrends(weeks) {
288241
289790
  }
288242
289791
  const weeklyOverall = weeks.map((w) => {
288243
289792
  const de = w.dimensionEvals.find((e2) => e2.dimensionId === dim.id);
288244
- return de?.overallScore ?? 3;
289793
+ return safeScore(de?.overallScore ?? 3);
288245
289794
  });
288246
289795
  trends.push({
288247
289796
  id: dim.id,
@@ -288277,7 +289826,7 @@ function computeQuantity(ghData) {
288277
289826
  };
288278
289827
  }
288279
289828
  async function loadPreviousWeekDominants(cwd, username, currentOutDir) {
288280
- const baseDir = path87__namespace.resolve(cwd, "dev-adviser");
289829
+ const baseDir = path87__namespace.resolve(cwd, ".maria/desktop/dev-adviser");
288281
289830
  let entries;
288282
289831
  try {
288283
289832
  entries = await fsp10__namespace.readdir(baseDir);
@@ -288338,6 +289887,75 @@ function neutralWorkClassification() {
288338
289887
  dominantCategory: "new-feature"
288339
289888
  };
288340
289889
  }
289890
+ function validateEvalRaw(raw, numWeeks) {
289891
+ const issues = [];
289892
+ if (!raw || !Array.isArray(raw.weeks)) {
289893
+ issues.push("Top-level 'weeks' array is missing or not an array.");
289894
+ return { ok: false, issues };
289895
+ }
289896
+ if (raw.weeks.length < numWeeks) {
289897
+ issues.push(`Expected ${numWeeks} weeks but got ${raw.weeks.length}.`);
289898
+ }
289899
+ for (let wi = 0; wi < numWeeks; wi++) {
289900
+ const w = raw.weeks[wi];
289901
+ if (!w) {
289902
+ issues.push(`Week ${wi} is missing entirely.`);
289903
+ continue;
289904
+ }
289905
+ if (!w.architectQuality) {
289906
+ issues.push(`Week ${wi}: architectQuality is missing.`);
289907
+ } else {
289908
+ if (!Array.isArray(w.architectQuality.subDimensions) || w.architectQuality.subDimensions.length === 0) {
289909
+ issues.push(`Week ${wi}: architectQuality.subDimensions is empty or missing.`);
289910
+ } else {
289911
+ const returnedIds = new Set(w.architectQuality.subDimensions.map((s2) => String(s2?.id || "")));
289912
+ for (const eid of EXPECTED_AQ_IDS) {
289913
+ if (!returnedIds.has(eid)) issues.push(`Week ${wi}: architectQuality sub-dimension "${eid}" is missing.`);
289914
+ }
289915
+ }
289916
+ if (typeof w.architectQuality.overallScore !== "number") {
289917
+ issues.push(`Week ${wi}: architectQuality.overallScore is not a number.`);
289918
+ }
289919
+ }
289920
+ if (!w.semanticQuality) {
289921
+ issues.push(`Week ${wi}: semanticQuality is missing.`);
289922
+ } else {
289923
+ if (!Array.isArray(w.semanticQuality.subDimensions) || w.semanticQuality.subDimensions.length === 0) {
289924
+ issues.push(`Week ${wi}: semanticQuality.subDimensions is empty or missing.`);
289925
+ } else {
289926
+ const returnedIds = new Set(w.semanticQuality.subDimensions.map((s2) => String(s2?.id || "")));
289927
+ for (const eid of EXPECTED_SQ_IDS) {
289928
+ if (!returnedIds.has(eid)) issues.push(`Week ${wi}: semanticQuality sub-dimension "${eid}" is missing.`);
289929
+ }
289930
+ }
289931
+ if (typeof w.semanticQuality.overallScore !== "number") {
289932
+ issues.push(`Week ${wi}: semanticQuality.overallScore is not a number.`);
289933
+ }
289934
+ }
289935
+ }
289936
+ return { ok: issues.length === 0, issues };
289937
+ }
289938
+ function buildRepairPrompt(brokenText, issues, numWeeks) {
289939
+ const aqIds = ARCHITECT_QUALITY.subDimensions.map((s2) => `"${s2.id}"`).join(", ");
289940
+ const sqIds = SEMANTIC_QUALITY.subDimensions.map((s2) => `"${s2.id}"`).join(", ");
289941
+ return [
289942
+ "MODEL_FIX: Your previous evaluation output is malformed. Repair it and return valid JSON only.",
289943
+ "",
289944
+ "Issues found:",
289945
+ ...issues.map((i2) => ` - ${i2}`),
289946
+ "",
289947
+ `Required: ${numWeeks} weeks, each with architectQuality and semanticQuality.`,
289948
+ `architectQuality sub-dimension IDs: [${aqIds}]`,
289949
+ `semanticQuality sub-dimension IDs: [${sqIds}]`,
289950
+ "Each sub-dimension needs: { id, score (1.0-5.0), evidence (string[]) }",
289951
+ "Each dimension needs: { subDimensions, overallScore (1.0-5.0), summary (string) }",
289952
+ "",
289953
+ "Your broken output (fix this and return correct JSON only):",
289954
+ "```",
289955
+ brokenText.slice(0, 12e3),
289956
+ "```"
289957
+ ].join("\n");
289958
+ }
288341
289959
  async function loadDevAdviserIgnore(cwd) {
288342
289960
  const filePath4 = path87__namespace.resolve(cwd, ".devadviserignore");
288343
289961
  try {
@@ -289080,7 +290698,7 @@ function buildMarkdownReport(result, username, scope) {
289080
290698
  function createDevAdviserField() {
289081
290699
  return { commandId: "dev-adviser", worker: new DevAdviserWorker(), checker: new AlwaysPassChecker20() };
289082
290700
  }
289083
- var BOT_AUTHOR_FILTER, TRAIT_DEFS, TRAIT_THRESHOLD, ARCHITECT_QUALITY, SEMANTIC_QUALITY, ALL_DIMENSIONS, WORK_CATEGORY_LABELS, SPARK_CHARS, FIX_CHASE_PATTERN, MAX_TRIAGE_PER_WEEK, MAX_TRIAGE_PATCH_LINES, MAX_LINES_PER_FILE, SKIP_PATCH_PATTERN, DevAdviserWorker, AlwaysPassChecker20;
290701
+ var BOT_AUTHOR_FILTER, TRAIT_DEFS, TRAIT_THRESHOLD, ARCHITECT_QUALITY, SEMANTIC_QUALITY, ALL_DIMENSIONS, WORK_CATEGORY_LABELS, SPARK_CHARS, MAX_EVAL_REPAIR_ATTEMPTS, EXPECTED_AQ_IDS, EXPECTED_SQ_IDS, FIX_CHASE_PATTERN, MAX_TRIAGE_PER_WEEK, MAX_TRIAGE_PATCH_LINES, MAX_LINES_PER_FILE, SKIP_PATCH_PATTERN, DevAdviserWorker, AlwaysPassChecker20;
289084
290702
  var init_dev_adviser_field = __esm({
289085
290703
  "commands/dev-adviser.field.ts"() {
289086
290704
  init_base2();
@@ -289138,6 +290756,9 @@ var init_dev_adviser_field = __esm({
289138
290756
  "dependency-updates": "Deps"
289139
290757
  };
289140
290758
  SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
290759
+ MAX_EVAL_REPAIR_ATTEMPTS = 2;
290760
+ EXPECTED_AQ_IDS = new Set(ARCHITECT_QUALITY.subDimensions.map((s2) => s2.id));
290761
+ EXPECTED_SQ_IDS = new Set(SEMANTIC_QUALITY.subDimensions.map((s2) => s2.id));
289141
290762
  FIX_CHASE_PATTERN = /\b(fix|bug|patch|hotfix|typo|修正|バグ)\b/i;
289142
290763
  MAX_TRIAGE_PER_WEEK = 30;
289143
290764
  MAX_TRIAGE_PATCH_LINES = 6e3;
@@ -289305,6 +290926,42 @@ Check the username and date range.`
289305
290926
  "",
289306
290927
  patchContext
289307
290928
  ].join("\n");
290929
+ const tryParseEval = (text) => {
290930
+ const rawText = String(text || "");
290931
+ const result2 = parseJsonFromModelText({ text: rawText, label: "dev-adviser.eval" });
290932
+ if (!result2.ok) return { parsed: null, rawText, issues: [`JSON parse failed: ${result2.error.slice(0, 200)}`] };
290933
+ const v = validateEvalRaw(result2.value, numWeeks);
290934
+ if (!v.ok) return { parsed: result2.value, rawText, issues: v.issues };
290935
+ return { parsed: result2.value, rawText, issues: [] };
290936
+ };
290937
+ const evalRawToWeekDimEvals = (raw) => {
290938
+ const result2 = [];
290939
+ for (let wi = 0; wi < numWeeks; wi++) {
290940
+ const wData = raw.weeks[wi];
290941
+ if (wData) {
290942
+ result2.push([
290943
+ {
290944
+ dimensionId: "architect-quality",
290945
+ subDimensions: sanitizeSubDimensions(wData.architectQuality?.subDimensions, ARCHITECT_QUALITY.subDimensions),
290946
+ overallScore: safeScore(wData.architectQuality?.overallScore ?? 3),
290947
+ summary: wData.architectQuality?.summary ?? ""
290948
+ },
290949
+ {
290950
+ dimensionId: "semantic-quality",
290951
+ subDimensions: sanitizeSubDimensions(wData.semanticQuality?.subDimensions, SEMANTIC_QUALITY.subDimensions),
290952
+ overallScore: safeScore(wData.semanticQuality?.overallScore ?? 3),
290953
+ summary: wData.semanticQuality?.summary ?? ""
290954
+ }
290955
+ ]);
290956
+ } else {
290957
+ result2.push([
290958
+ neutralDimensionEval(ARCHITECT_QUALITY, "No data for this week"),
290959
+ neutralDimensionEval(SEMANTIC_QUALITY, "No data for this week")
290960
+ ]);
290961
+ }
290962
+ }
290963
+ return result2;
290964
+ };
289308
290965
  const evalResult = await withLiteSpinner(
289309
290966
  `Evaluating Architect Quality & Semantic Quality (${selectedShas.size} commits examined)...`,
289310
290967
  () => this.aiPromptStructured(ctx, {
@@ -289314,36 +290971,44 @@ Check the username and date range.`
289314
290971
  signal: ctx.abortSignal
289315
290972
  })
289316
290973
  );
289317
- const weekDimensionEvals = [];
290974
+ let weekDimensionEvals = [];
290975
+ let evalAccepted = false;
289318
290976
  if (evalResult.status === "ok") {
289319
- const parsed = parseJsonFromModelText({ text: String(evalResult.text || ""), label: "dev-adviser.eval" });
289320
- if (parsed.ok && Array.isArray(parsed.value.weeks)) {
289321
- for (let wi = 0; wi < numWeeks; wi++) {
289322
- const wData = parsed.value.weeks[wi];
289323
- if (wData) {
289324
- const evals = [];
289325
- evals.push({
289326
- dimensionId: "architect-quality",
289327
- subDimensions: wData.architectQuality?.subDimensions ?? ARCHITECT_QUALITY.subDimensions.map((s2) => ({ id: s2.id, score: 3, evidence: ["Parse incomplete"] })),
289328
- overallScore: wData.architectQuality?.overallScore ?? 3,
289329
- summary: wData.architectQuality?.summary ?? ""
289330
- });
289331
- evals.push({
289332
- dimensionId: "semantic-quality",
289333
- subDimensions: wData.semanticQuality?.subDimensions ?? SEMANTIC_QUALITY.subDimensions.map((s2) => ({ id: s2.id, score: 3, evidence: ["Parse incomplete"] })),
289334
- overallScore: wData.semanticQuality?.overallScore ?? 3,
289335
- summary: wData.semanticQuality?.summary ?? ""
289336
- });
289337
- weekDimensionEvals.push(evals);
289338
- } else {
289339
- weekDimensionEvals.push([
289340
- neutralDimensionEval(ARCHITECT_QUALITY, "No data for this week"),
289341
- neutralDimensionEval(SEMANTIC_QUALITY, "No data for this week")
289342
- ]);
290977
+ const attempt0 = tryParseEval(evalResult.text || "");
290978
+ if (attempt0.issues.length === 0 && attempt0.parsed) {
290979
+ weekDimensionEvals = evalRawToWeekDimEvals(attempt0.parsed);
290980
+ evalAccepted = true;
290981
+ } else {
290982
+ let lastRawText = attempt0.rawText;
290983
+ let lastIssues = attempt0.issues;
290984
+ for (let ri = 1; ri <= MAX_EVAL_REPAIR_ATTEMPTS; ri++) {
290985
+ await emitLog("lite.dev-adviser.eval_repair", { attempt: ri, issues: lastIssues.slice(0, 5) });
290986
+ const repairPrompt = buildRepairPrompt(lastRawText, lastIssues, numWeeks);
290987
+ const repairResult = await withLiteSpinner(
290988
+ `Repairing evaluation format (attempt ${ri}/${MAX_EVAL_REPAIR_ATTEMPTS})...`,
290989
+ () => this.aiPromptStructured(ctx, {
290990
+ taskType: "dev-adviser.eval",
290991
+ systemPrompt: "You are a JSON repair assistant. Fix the broken JSON to match the required schema. Return valid JSON only.",
290992
+ prompt: repairPrompt,
290993
+ signal: ctx.abortSignal
290994
+ })
290995
+ );
290996
+ if (repairResult.status !== "ok") continue;
290997
+ const attemptN = tryParseEval(repairResult.text || "");
290998
+ if (attemptN.issues.length === 0 && attemptN.parsed) {
290999
+ weekDimensionEvals = evalRawToWeekDimEvals(attemptN.parsed);
291000
+ evalAccepted = true;
291001
+ await emitLog("lite.dev-adviser.eval_repair_ok", { attempt: ri });
291002
+ break;
289343
291003
  }
291004
+ lastRawText = attemptN.rawText;
291005
+ lastIssues = attemptN.issues;
289344
291006
  }
289345
291007
  }
289346
291008
  }
291009
+ if (!evalAccepted) {
291010
+ await emitLog("lite.dev-adviser.eval_fallback", { reason: "All eval attempts failed" });
291011
+ }
289347
291012
  while (weekDimensionEvals.length < numWeeks) {
289348
291013
  weekDimensionEvals.push([
289349
291014
  neutralDimensionEval(ARCHITECT_QUALITY, "LLM evaluation unavailable"),
@@ -289382,7 +291047,7 @@ Check the username and date range.`
289382
291047
  }
289383
291048
  const stamp = nowStamp3();
289384
291049
  const titleBase = sanitizeBasename(`dev-assessment-${username}`);
289385
- const outDirRel = outputPath || `dev-adviser/${stamp}_${sanitizeBasename(username)}`;
291050
+ const outDirRel = outputPath || `.maria/desktop/dev-adviser/${stamp}_${sanitizeBasename(username)}`;
289386
291051
  const outDirAbs = path87__namespace.resolve(ctx.cwd, outDirRel);
289387
291052
  const prevDominants = await loadPreviousWeekDominants(ctx.cwd, username, outDirAbs);
289388
291053
  const classificationChangelog = computeClassificationChangelog(weeks, prevDominants);
@@ -289534,6 +291199,69 @@ Check the username and date range.`
289534
291199
  };
289535
291200
  }
289536
291201
  });
291202
+ async function fetchRepoTree(repo, cwd, signal) {
291203
+ const r2 = await runGhCapture({ cwd, signal, args: ["api", `repos/${repo}/git/trees/HEAD?recursive=1`, "--jq", ".tree[] | [.path, .type, (.size // 0)] | @tsv"] });
291204
+ if (r2.exitCode !== 0) return { ok: false, error: r2.stderr.trim() || `gh exit ${r2.exitCode}` };
291205
+ const entries = r2.stdout.trim().split("\n").filter(Boolean).map((line) => {
291206
+ const [p, t2, s2] = line.split(" ");
291207
+ return { path: p ?? "", type: t2 ?? "blob", size: Number(s2 || 0) };
291208
+ });
291209
+ return { ok: true, entries };
291210
+ }
291211
+ function buildCompactTree(entries) {
291212
+ const SKIP = /^(node_modules|\.git|dist|build|\.next|__pycache__|\.cache|vendor)\//;
291213
+ const SKIP_FILES = /\.(lock|min\.js|min\.css|map|snap|svg|png|jpg|jpeg|gif|ico|woff2?|ttf|eot)$/;
291214
+ const filtered = entries.filter((e2) => !SKIP.test(e2.path) && !SKIP_FILES.test(e2.path));
291215
+ return filtered.map((e2) => `${e2.type === "tree" ? "D" : "F"} ${e2.path}`).join("\n");
291216
+ }
291217
+ async function fetchFileContents(repo, paths, cwd, signal) {
291218
+ const MAX_FILES = 20;
291219
+ const MAX_BYTES_PER_FILE = 15e3;
291220
+ const result = /* @__PURE__ */ new Map();
291221
+ const selected = paths.slice(0, MAX_FILES);
291222
+ for (const p of selected) {
291223
+ if (signal.aborted) break;
291224
+ const r2 = await runGhCapture({ cwd, signal, args: ["api", `repos/${repo}/contents/${p}`, "--jq", ".content"] });
291225
+ if (r2.exitCode !== 0) continue;
291226
+ try {
291227
+ const decoded = Buffer.from(r2.stdout.trim(), "base64").toString("utf8");
291228
+ result.set(p, decoded.slice(0, MAX_BYTES_PER_FILE));
291229
+ } catch {
291230
+ }
291231
+ }
291232
+ return result;
291233
+ }
291234
+ async function publishTaskAsIssue(repo, task, projectTitle, cwd, signal) {
291235
+ const body = [
291236
+ `## ${task.id}: ${task.title}`,
291237
+ "",
291238
+ task.description,
291239
+ "",
291240
+ `**Assignee:** ${task.assignee}`,
291241
+ `**Domain:** ${task.domain}`,
291242
+ `**Effort:** Optimistic ${task.effort.optimistic.hours}h / Realistic ${task.effort.realistic.hours}h / Pessimistic ${task.effort.pessimistic.hours}h`,
291243
+ `**Deadline:** ${task.deadline}`,
291244
+ `**Slack:** ${task.slackDays >= 0 ? `${task.slackDays} days` : "unknown"}`,
291245
+ task.onCriticalPath ? "**On Critical Path:** YES" : "",
291246
+ task.dependencies.length > 0 ? `**Dependencies:** ${task.dependencies.join(", ")}` : "",
291247
+ task.relatedFiles.length > 0 ? `
291248
+ **Related Files:**
291249
+ ${task.relatedFiles.map((f3) => `- \`${f3}\``).join("\n")}` : "",
291250
+ "",
291251
+ `**Rationale:** ${task.rationale}`,
291252
+ "",
291253
+ `---`,
291254
+ `*Generated by MARIA OS /task-distribution \u2014 ${projectTitle}*`
291255
+ ].filter(Boolean).join("\n");
291256
+ const r2 = await runGhCapture({
291257
+ cwd,
291258
+ signal,
291259
+ args: ["issue", "create", "--repo", repo, "--title", `[${task.id}] ${task.title}`, "--body", body]
291260
+ });
291261
+ if (r2.exitCode !== 0) return { ok: false, error: r2.stderr.trim() || `gh exit ${r2.exitCode}` };
291262
+ const url = r2.stdout.trim();
291263
+ return { ok: true, url };
291264
+ }
289537
291265
  function renderTaskDistMarkdown(result) {
289538
291266
  const lines = [];
289539
291267
  lines.push(`# ${result.projectTitle}`);
@@ -289627,6 +291355,9 @@ function renderTaskDistMarkdown(result) {
289627
291355
  lines.push(`- **Slack:** ${typeof t2.slackDays === "number" ? `${t2.slackDays} days` : "unknown"}`);
289628
291356
  lines.push(`- **Deadline:** ${t2.deadline}`);
289629
291357
  if (t2.dependencies.length > 0) lines.push(`- **Dependencies:** ${t2.dependencies.join(", ")}`);
291358
+ if (Array.isArray(t2.relatedFiles) && t2.relatedFiles.length > 0) {
291359
+ lines.push(`- **Related Files:** ${t2.relatedFiles.map((f3) => `\`${f3}\``).join(", ")}`);
291360
+ }
289630
291361
  lines.push(`- **Rationale:** ${t2.rationale}`);
289631
291362
  lines.push("");
289632
291363
  }
@@ -289769,13 +291500,17 @@ function createTaskDistributionField() {
289769
291500
  "Examples:",
289770
291501
  ' /task-distribution "Build MVP for customer portal" --members "Alice,Bob,Charlie" --apply',
289771
291502
  ' /task-distribution --in @project-brief.md --members "Dev1,Dev2,Designer,PM" --apply',
291503
+ ' /task-distribution "Add auth module" --members "Dev1,Dev2" --repo "owner/repo" --apply',
291504
+ ' /task-distribution "Refactor payments" --members "A,B" --repo "org/app" --publish-issues --apply',
289772
291505
  "",
289773
291506
  "Options:",
289774
291507
  " --members <list>",
291508
+ " --repo <owner/repo>",
289775
291509
  " --in <file>",
289776
291510
  " --out <dir>",
289777
291511
  " --docx",
289778
291512
  " --pdf",
291513
+ " --publish-issues",
289779
291514
  " --apply",
289780
291515
  " --json"
289781
291516
  ].join("\n")
@@ -289789,8 +291524,10 @@ function createTaskDistributionField() {
289789
291524
  const wantsDocx = hasLiteFlag(ctx.parsed, "docx") || !hasLiteFlag(ctx.parsed, "docx") && !hasLiteFlag(ctx.parsed, "pdf");
289790
291525
  const wantsPdf = hasLiteFlag(ctx.parsed, "pdf") || !hasLiteFlag(ctx.parsed, "docx") && !hasLiteFlag(ctx.parsed, "pdf");
289791
291526
  const wantsJson = hasLiteFlag(ctx.parsed, "json");
291527
+ const wantsPublishIssues = hasLiteFlag(ctx.parsed, "publish-issues");
291528
+ const repoSlug = typeof ctx.parsed.options.repo === "string" ? String(ctx.parsed.options.repo).trim() : "";
289792
291529
  const outBase = typeof ctx.parsed.options.out === "string" ? String(ctx.parsed.options.out).trim() : "";
289793
- const outBaseRel = (outBase || "task-distribution").replace(/\\/g, "/");
291530
+ const outBaseRel = (outBase || ".maria/desktop/task-distribution").replace(/\\/g, "/");
289794
291531
  const membersRaw = typeof ctx.parsed.options.members === "string" ? String(ctx.parsed.options.members).trim() : "";
289795
291532
  const members = membersRaw.split(",").map((m2) => m2.trim()).filter(Boolean);
289796
291533
  if (members.length === 0) return { text: `Error: --members is required.
@@ -289812,17 +291549,77 @@ ${this.help.usage}` };
289812
291549
  }
289813
291550
  if (!goal) return { text: `Usage:
289814
291551
  ${this.help.usage}` };
291552
+ let repoContext = "";
291553
+ let repoTreeEntries = [];
291554
+ if (repoSlug) {
291555
+ const ghCheck = await ensureGhInstalled2();
291556
+ if (!ghCheck.ok) return { text: `Error: ${ghCheck.message}
291557
+ --repo requires GitHub CLI.` };
291558
+ const tree = await fetchRepoTree(repoSlug, ctx.cwd, ctx.abortSignal);
291559
+ if (!tree.ok) return { text: `Error fetching repo tree: ${tree.error}` };
291560
+ repoTreeEntries = tree.entries;
291561
+ const compactTree = buildCompactTree(repoTreeEntries);
291562
+ const triagePrompt = [
291563
+ "You are a code-aware task planner. Given a project goal and a repository file tree, select the files most relevant to understanding the codebase for task distribution.",
291564
+ "",
291565
+ `Goal: ${goal}`,
291566
+ additionalContext ? `
291567
+ Additional context:
291568
+ ${additionalContext.slice(0, 1e4)}` : "",
291569
+ "",
291570
+ "## Repository File Tree",
291571
+ compactTree.slice(0, 8e4),
291572
+ "",
291573
+ "Select up to 20 files that are MOST relevant for understanding the codebase structure, architecture, and the areas this goal touches.",
291574
+ "Prefer: entry points, config files, core modules, type definitions, route definitions, schema files.",
291575
+ "Skip: tests, generated files, assets, lock files.",
291576
+ "",
291577
+ "Respond with JSON only:",
291578
+ "```json",
291579
+ '{ "selectedFiles": ["path/to/file1.ts", "path/to/file2.ts", ...],',
291580
+ ' "rationale": "Brief explanation" }',
291581
+ "```"
291582
+ ].join("\n");
291583
+ const triageRes = await this.aiPromptStructured(ctx, {
291584
+ taskType: "chat",
291585
+ prompt: triagePrompt,
291586
+ signal: ctx.abortSignal,
291587
+ spinnerTextOverride: "Analyzing repo structure"
291588
+ });
291589
+ const triageParsed = parseJsonFromModelText({
291590
+ text: String(triageRes.text || ""),
291591
+ label: "repo-triage"
291592
+ });
291593
+ const selectedPaths = triageParsed.ok ? triageParsed.value.selectedFiles || [] : [];
291594
+ const fileContents = selectedPaths.length > 0 ? await fetchFileContents(repoSlug, selectedPaths, ctx.cwd, ctx.abortSignal) : /* @__PURE__ */ new Map();
291595
+ const contextParts = [
291596
+ `
291597
+ ## Repository: ${repoSlug}`,
291598
+ "",
291599
+ "### File Tree (filtered)",
291600
+ compactTree.slice(0, 4e4)
291601
+ ];
291602
+ if (fileContents.size > 0) {
291603
+ contextParts.push("", "### Key File Contents");
291604
+ for (const [fp, content] of fileContents) {
291605
+ contextParts.push("", `#### ${fp}`, "```", content.slice(0, 1e4), "```");
291606
+ }
291607
+ }
291608
+ repoContext = contextParts.join("\n");
291609
+ }
289815
291610
  if (!apply) {
289816
291611
  return {
289817
291612
  text: [
289818
291613
  "Plan: /task-distribution",
289819
291614
  `goal=${goal.slice(0, 120)}${goal.length > 120 ? "..." : ""}`,
289820
291615
  `members=${members.join(", ")}`,
291616
+ repoSlug ? `repo=${repoSlug}` : "",
289821
291617
  `outDir=${outBaseRel}`,
289822
291618
  `docx=${wantsDocx ? "yes" : "no"} pdf=${wantsPdf ? "yes" : "no"}`,
291619
+ wantsPublishIssues ? "publish-issues=yes" : "",
289823
291620
  "",
289824
291621
  "Run with: --apply"
289825
- ].join("\n")
291622
+ ].filter(Boolean).join("\n")
289826
291623
  };
289827
291624
  }
289828
291625
  const stamp = nowStamp3();
@@ -289833,7 +291630,7 @@ ${this.help.usage}` };
289833
291630
  schemaVersion: "maria_lite_task_dist_run_v1",
289834
291631
  runId: ctx.runId,
289835
291632
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
289836
- inputs: { goal: goal.slice(0, 500), members, docx: wantsDocx, pdf: wantsPdf }
291633
+ inputs: { goal: goal.slice(0, 500), members, repo: repoSlug || void 0, docx: wantsDocx, pdf: wantsPdf, publishIssues: wantsPublishIssues }
289837
291634
  });
289838
291635
  const logger = new LiteLogger({ cwd: ctx.cwd });
289839
291636
  const emitLog = async (kind, payload) => {
@@ -289860,7 +291657,7 @@ ${this.help.usage}` };
289860
291657
  `{"schemaVersion":"maria_lite_task_dist_v1","projectTitle":"...","goal":"...","members":["..."],`,
289861
291658
  `"tasks":[{"id":"T-001","title":"...","description":"...","assignee":"member_name","domain":"...","dependencies":["T-xxx"],`,
289862
291659
  `"effort":{"optimistic":{"hours":N,"days":N},"realistic":{"hours":N,"days":N},"pessimistic":{"hours":N,"days":N}},`,
289863
- `"deadline":"YYYY-MM-DD","rationale":"...","slackDays":N,"onCriticalPath":bool}],`,
291660
+ `"deadline":"YYYY-MM-DD","rationale":"...","slackDays":N,"onCriticalPath":bool,"relatedFiles":["path/to/file.ts"]}],`,
289864
291661
  `"interfaces":[{"id":"IF-001","between":["member_A","member_B"],"contract":"...","dataFormat":"...","timing":"...","owner":"manager",`,
289865
291662
  `"boundaryScenarios":["scenario that falls in the gap between both owners' responsibilities"]}],`,
289866
291663
  `"criticalPath":{"path":["T-001","T-003","T-006"],"totalRealisticDays":N,"totalPessimisticDays":N,"bottleneckTaskId":"T-xxx","bottleneckReason":"..."},`,
@@ -289969,6 +291766,16 @@ ${this.help.usage}` };
289969
291766
  " - vulnerabilities: at least 3 structural weaknesses with mitigations.",
289970
291767
  " - idleGaps: per-member idle periods with gap-filling activity recommendations.",
289971
291768
  "",
291769
+ repoSlug ? [
291770
+ "",
291771
+ "11. **Repository-Aware Task Distribution (when repo context is provided):**",
291772
+ " - Use the repository file tree and key file contents to understand the codebase architecture.",
291773
+ " - For each task, populate `relatedFiles` with specific file/directory paths from the repo that the task will touch or depend on.",
291774
+ " - Use actual file paths from the tree \u2014 do NOT invent paths that don't exist in the repo.",
291775
+ " - Task descriptions should reference specific modules, services, and components observed in the codebase.",
291776
+ " - Domain assignments should reflect the actual code organization (e.g., if the repo has `src/auth/`, `src/payments/`, use those as domains)."
291777
+ ].join("\n") : "",
291778
+ "",
289972
291779
  `Today's date: ${todayStr}`,
289973
291780
  `Team members: ${members.join(", ")}`,
289974
291781
  "",
@@ -289977,6 +291784,8 @@ ${this.help.usage}` };
289977
291784
  additionalContext ? `
289978
291785
  Additional context:
289979
291786
  ${additionalContext.slice(0, 6e4)}` : "",
291787
+ repoContext ? `
291788
+ ${repoContext.slice(0, 8e4)}` : "",
289980
291789
  fix ? `
289981
291790
 
289982
291791
  ${fix}
@@ -290007,6 +291816,7 @@ ${fix}
290007
291816
  for (const t2 of v.tasks) {
290008
291817
  if (typeof t2.slackDays !== "number" || !Number.isFinite(t2.slackDays)) t2.slackDays = -1;
290009
291818
  if (typeof t2.onCriticalPath !== "boolean") t2.onCriticalPath = false;
291819
+ if (!Array.isArray(t2.relatedFiles)) t2.relatedFiles = [];
290010
291820
  }
290011
291821
  if (!Array.isArray(v.interfaces)) v.interfaces = [];
290012
291822
  for (const iface of v.interfaces) {
@@ -290081,16 +291891,28 @@ outDir=${outDirRel}` };
290081
291891
  failures.push(`PDF_EXPORT_FAILED: ${msg || "unknown"}`);
290082
291892
  }
290083
291893
  }
291894
+ const issueUrls = [];
291895
+ const issueFailures = [];
291896
+ if (wantsPublishIssues && repoSlug) {
291897
+ for (const task of result.tasks) {
291898
+ const ir = await publishTaskAsIssue(repoSlug, task, result.projectTitle, ctx.cwd, ctx.abortSignal);
291899
+ if (ir.ok && ir.url) issueUrls.push(`${task.id}: ${ir.url}`);
291900
+ else issueFailures.push(`${task.id}: ${ir.error || "unknown"}`);
291901
+ }
291902
+ await emitLog("lite.task_dist.issues", { published: issueUrls.length, failed: issueFailures.length });
291903
+ }
290084
291904
  const ok = failures.length === 0 && ((wantsDocx ? Boolean(exports2.docxRel) : true) && (wantsPdf ? Boolean(exports2.pdfRel) : true));
290085
291905
  await writeJson9(path87__namespace.join(outDirAbs, "summary.json"), {
290086
291906
  schemaVersion: "maria_lite_task_dist_summary_v1",
290087
291907
  ok,
290088
291908
  outDir: outDirRel,
291909
+ repo: repoSlug || void 0,
290089
291910
  taskCount: result.tasks.length,
290090
291911
  interfaceCount: result.interfaces.length,
290091
291912
  memberCount: result.members.length,
290092
291913
  exports: exports2,
290093
- failures
291914
+ failures,
291915
+ issues: issueUrls.length > 0 ? { published: issueUrls, failures: issueFailures } : void 0
290094
291916
  });
290095
291917
  const primaryArtifacts = [];
290096
291918
  if (exports2.docxRel) primaryArtifacts.push({ path: exports2.docxRel, kind: "primary" });
@@ -290108,18 +291930,25 @@ outDir=${outDirRel}` };
290108
291930
  idleGaps: result.idleGaps.length,
290109
291931
  docx: Boolean(exports2.docxRel),
290110
291932
  pdf: Boolean(exports2.pdfRel),
290111
- failures: failures.length
291933
+ failures: failures.length,
291934
+ issuesPublished: issueUrls.length
290112
291935
  });
290113
291936
  const lines = [];
290114
291937
  lines.push(ok ? "OK: /task-distribution" : "WARN: /task-distribution (partial)");
290115
291938
  lines.push(`outDir=${outDirRel}`);
290116
291939
  lines.push(`tasks=${result.tasks.length} members=${result.members.length} interfaces=${result.interfaces.length}`);
291940
+ if (repoSlug) lines.push(`repo=${repoSlug}`);
290117
291941
  if (exports2.docxRel) lines.push(`docx=${exports2.docxRel}`);
290118
291942
  if (exports2.pdfRel) lines.push(`pdf=${exports2.pdfRel}`);
291943
+ if (issueUrls.length > 0) {
291944
+ lines.push(`issues_published=${issueUrls.length}`);
291945
+ for (const u of issueUrls) lines.push(` ${u}`);
291946
+ }
291947
+ if (issueFailures.length > 0) lines.push(`issue_failures=${issueFailures.length}`);
290119
291948
  if (!ok) lines.push(`failures=${failures.length}`);
290120
291949
  return {
290121
291950
  text: lines.join("\n"),
290122
- json: wantsJson ? { ok, outDir: outDirRel, taskCount: result.tasks.length, interfaces: result.interfaces.length, exports: exports2, failures, data: result } : { text: lines.join("\n") },
291951
+ json: wantsJson ? { ok, outDir: outDirRel, repo: repoSlug || void 0, taskCount: result.tasks.length, interfaces: result.interfaces.length, exports: exports2, failures, issues: { published: issueUrls, failures: issueFailures }, data: result } : { text: lines.join("\n") },
290123
291952
  artifacts: [...primaryArtifacts, ...intermediateArtifacts]
290124
291953
  };
290125
291954
  }
@@ -290147,6 +291976,7 @@ var init_task_distribution_field = __esm({
290147
291976
  init_soffice();
290148
291977
  init_slides_helpers();
290149
291978
  init_logger();
291979
+ init_gh_cli();
290150
291980
  }
290151
291981
  });
290152
291982
 
@@ -291460,6 +293290,254 @@ var init_phone_init_field = __esm({
291460
293290
  }
291461
293291
  });
291462
293292
 
293293
+ // commands/resource-manager.field.ts
293294
+ async function gFetch3(token, url, opts) {
293295
+ return fetch(url, {
293296
+ ...opts,
293297
+ headers: { Authorization: `Bearer ${token}`, ...opts?.headers || {} }
293298
+ });
293299
+ }
293300
+ async function listBuildings2(token, _signal) {
293301
+ const res = await gFetch3(token, `${ADMIN_RESOURCES_BASE3}/buildings?maxResults=500`);
293302
+ if (!res.ok) {
293303
+ return { text: `Resource Manager: Admin SDK error ${res.status}`, json: { ok: false, error: `Admin SDK: ${res.status}` } };
293304
+ }
293305
+ const data = await res.json();
293306
+ const items = Array.isArray(data.buildings) ? data.buildings : [];
293307
+ if (items.length === 0) {
293308
+ return { text: "No buildings found.", json: { ok: true, buildings: [] } };
293309
+ }
293310
+ const lines = ["## Buildings", ""];
293311
+ lines.push("| ID | Name | Floors | Description |");
293312
+ lines.push("|---|---|---|---|");
293313
+ for (const b of items) {
293314
+ const floors = Array.isArray(b.floorNames) ? b.floorNames.join(", ") : "";
293315
+ lines.push(`| ${b.buildingId || ""} | ${b.buildingName || ""} | ${floors} | ${b.description || ""} |`);
293316
+ }
293317
+ lines.push("", `Total: ${items.length} building(s)`);
293318
+ return { text: lines.join("\n"), json: { ok: true, buildings: items } };
293319
+ }
293320
+ async function listResources2(token, buildingId, capacity, _signal) {
293321
+ let url = `${ADMIN_RESOURCES_BASE3}/calendars?maxResults=500`;
293322
+ const res = await gFetch3(token, url);
293323
+ if (!res.ok) {
293324
+ return { text: `Resource Manager: Admin SDK error ${res.status}`, json: { ok: false, error: `Admin SDK: ${res.status}` } };
293325
+ }
293326
+ const data = await res.json();
293327
+ let items = Array.isArray(data.items) ? data.items : [];
293328
+ if (buildingId) items = items.filter((r2) => String(r2.buildingId || "") === buildingId);
293329
+ if (capacity) items = items.filter((r2) => Number(r2.capacity || 0) >= capacity);
293330
+ if (items.length === 0) {
293331
+ return { text: "No resources found.", json: { ok: true, resources: [] } };
293332
+ }
293333
+ const lines = ["## Calendar Resources", ""];
293334
+ lines.push("| Name | Building | Capacity | Type | Floor | Email |");
293335
+ lines.push("|---|---|---|---|---|---|");
293336
+ for (const r2 of items) {
293337
+ lines.push(`| ${r2.resourceName || ""} | ${r2.buildingId || ""} | ${r2.capacity || ""} | ${r2.resourceType || ""} | ${r2.floorName || ""} | ${r2.resourceEmail || ""} |`);
293338
+ }
293339
+ lines.push("", `Total: ${items.length} resource(s)`);
293340
+ return { text: lines.join("\n"), json: { ok: true, resources: items } };
293341
+ }
293342
+ async function createBuilding2(token, name, floorsRaw, description, _signal) {
293343
+ const buildingId = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || `bld-${Date.now()}`;
293344
+ const body = { buildingId, buildingName: name };
293345
+ if (description) body.description = description;
293346
+ if (floorsRaw) {
293347
+ const n = Number(floorsRaw);
293348
+ if (!isNaN(n) && n > 0 && Number.isInteger(n)) {
293349
+ body.floorNames = Array.from({ length: n }, (_, i2) => String(i2 + 1));
293350
+ } else {
293351
+ body.floorNames = floorsRaw.split(",").map((f3) => f3.trim()).filter(Boolean);
293352
+ }
293353
+ }
293354
+ const res = await gFetch3(token, `${ADMIN_RESOURCES_BASE3}/buildings`, {
293355
+ method: "POST",
293356
+ headers: { "Content-Type": "application/json" },
293357
+ body: JSON.stringify(body)
293358
+ });
293359
+ if (!res.ok) {
293360
+ const errText = await res.text().catch(() => "");
293361
+ if (res.status === 403) return { text: "Permission denied: Google Workspace admin privileges required.", json: { ok: false, error: "permission_denied" } };
293362
+ return { text: `Failed to create building: ${res.status} ${errText}`, json: { ok: false, error: `Admin SDK: ${res.status}` } };
293363
+ }
293364
+ const created = await res.json();
293365
+ return {
293366
+ text: `Building created: ${created.buildingName} (${created.buildingId})`,
293367
+ json: { ok: true, building: created }
293368
+ };
293369
+ }
293370
+ async function createResource2(token, opts, _signal) {
293371
+ const resourceId = opts.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || `res-${Date.now()}`;
293372
+ const body = { resourceId, resourceName: opts.name, resourceType: opts.resourceType };
293373
+ if (opts.buildingId) body.buildingId = opts.buildingId;
293374
+ if (opts.capacity) body.capacity = opts.capacity;
293375
+ if (opts.floorName) body.floorName = opts.floorName;
293376
+ if (opts.description) body.resourceDescription = opts.description;
293377
+ const res = await gFetch3(token, `${ADMIN_RESOURCES_BASE3}/calendars`, {
293378
+ method: "POST",
293379
+ headers: { "Content-Type": "application/json" },
293380
+ body: JSON.stringify(body)
293381
+ });
293382
+ if (!res.ok) {
293383
+ const errText = await res.text().catch(() => "");
293384
+ if (res.status === 403) return { text: "Permission denied: Google Workspace admin privileges required.", json: { ok: false, error: "permission_denied" } };
293385
+ return { text: `Failed to create resource: ${res.status} ${errText}`, json: { ok: false, error: `Admin SDK: ${res.status}` } };
293386
+ }
293387
+ const created = await res.json();
293388
+ return {
293389
+ text: `Resource created: ${created.resourceName} (${created.resourceEmail})`,
293390
+ json: { ok: true, resource: created }
293391
+ };
293392
+ }
293393
+ async function updateBuilding(token, buildingId, body, _signal) {
293394
+ const res = await gFetch3(token, `${ADMIN_RESOURCES_BASE3}/buildings/${encodeURIComponent(buildingId)}`, {
293395
+ method: "PATCH",
293396
+ headers: { "Content-Type": "application/json" },
293397
+ body: JSON.stringify(body)
293398
+ });
293399
+ if (!res.ok) {
293400
+ const errText = await res.text().catch(() => "");
293401
+ if (res.status === 403) return { text: "Permission denied: Google Workspace admin privileges required.", json: { ok: false, error: "permission_denied" } };
293402
+ if (res.status === 404) return { text: `Building "${buildingId}" not found.`, json: { ok: false, error: "not_found" } };
293403
+ return { text: `Failed to update building: ${res.status} ${errText}`, json: { ok: false, error: `Admin SDK: ${res.status}` } };
293404
+ }
293405
+ const updated = await res.json();
293406
+ return {
293407
+ text: `Building updated: ${updated.buildingName} (${updated.buildingId})`,
293408
+ json: { ok: true, building: updated }
293409
+ };
293410
+ }
293411
+ async function updateResource(token, resourceId, body, _signal) {
293412
+ const res = await gFetch3(token, `${ADMIN_RESOURCES_BASE3}/calendars/${encodeURIComponent(resourceId)}`, {
293413
+ method: "PATCH",
293414
+ headers: { "Content-Type": "application/json" },
293415
+ body: JSON.stringify(body)
293416
+ });
293417
+ if (!res.ok) {
293418
+ const errText = await res.text().catch(() => "");
293419
+ if (res.status === 403) return { text: "Permission denied: Google Workspace admin privileges required.", json: { ok: false, error: "permission_denied" } };
293420
+ if (res.status === 404) return { text: `Resource "${resourceId}" not found.`, json: { ok: false, error: "not_found" } };
293421
+ return { text: `Failed to update resource: ${res.status} ${errText}`, json: { ok: false, error: `Admin SDK: ${res.status}` } };
293422
+ }
293423
+ const updated = await res.json();
293424
+ return {
293425
+ text: `Resource updated: ${updated.resourceName} (${updated.resourceEmail})`,
293426
+ json: { ok: true, resource: updated }
293427
+ };
293428
+ }
293429
+ function createResourceManagerField() {
293430
+ const worker = new ResourceManagerWorker();
293431
+ const checker = new ResourceManagerChecker();
293432
+ return { worker, checker };
293433
+ }
293434
+ var ADMIN_RESOURCES_BASE3, ResourceManagerWorker, ResourceManagerChecker;
293435
+ var init_resource_manager_field = __esm({
293436
+ "commands/resource-manager.field.ts"() {
293437
+ init_base2();
293438
+ init_google_oauth();
293439
+ ADMIN_RESOURCES_BASE3 = "https://admin.googleapis.com/admin/directory/v1/customer/my_customer/resources";
293440
+ ResourceManagerWorker = class extends LiteWorkerAgent {
293441
+ commandId = "resource-manager";
293442
+ help = {
293443
+ command: "/resource-manager",
293444
+ description: "Manage Google Workspace buildings and calendar resources.",
293445
+ usage: '/resource-manager buildings\n/resource-manager resources [--building <buildingId>] [--capacity <n>]\n/resource-manager create-building --name "Main Office" [--floors <n>] [--description <text>]\n/resource-manager create-resource --name "Room A" --building <buildingId> [--capacity <n>] [--type CONFERENCE_ROOM] [--floor <floor>] [--description <text>]\n/resource-manager update-building --id <buildingId> [--name <name>] [--floors <n>] [--description <text>]\n/resource-manager update-resource --id <resourceId> [--name <name>] [--building <buildingId>] [--capacity <n>] [--type <type>] [--floor <floor>] [--description <text>]',
293446
+ runOnEmpty: true
293447
+ };
293448
+ steps = [
293449
+ {
293450
+ id: "resource-manager.exec",
293451
+ title: "Resource Manager",
293452
+ async run(ctx) {
293453
+ const mgr = new GoogleOAuthManager();
293454
+ const token = await mgr.getValidToken(ctx.abortSignal);
293455
+ if (!token) {
293456
+ return {
293457
+ text: "Resource Manager: Google account not connected. Run /google-connect first.",
293458
+ json: { ok: false, error: "not_connected" }
293459
+ };
293460
+ }
293461
+ const sub = (ctx.parsed.subcommand || ctx.parsed.args?.[0] || "buildings").toLowerCase();
293462
+ if (sub === "buildings" || sub === "list-buildings") {
293463
+ return await listBuildings2(token, ctx.abortSignal);
293464
+ }
293465
+ if (sub === "resources" || sub === "list-resources") {
293466
+ const buildingId = String(ctx.parsed.options.building || ctx.parsed.options.buildingId || "").trim() || void 0;
293467
+ const capacityRaw = String(ctx.parsed.options.capacity || "").trim();
293468
+ const capacity = capacityRaw ? Math.max(0, Number(capacityRaw) || 0) : void 0;
293469
+ return await listResources2(token, buildingId, capacity, ctx.abortSignal);
293470
+ }
293471
+ if (sub === "create-building") {
293472
+ const name = String(ctx.parsed.options.name || ctx.parsed.prompt || "").trim();
293473
+ if (!name) return { text: "resource-manager create-building: --name is required" };
293474
+ const floorsRaw = String(ctx.parsed.options.floors || "").trim();
293475
+ const description = String(ctx.parsed.options.description || "").trim() || void 0;
293476
+ return await createBuilding2(token, name, floorsRaw, description, ctx.abortSignal);
293477
+ }
293478
+ if (sub === "create-resource") {
293479
+ const name = String(ctx.parsed.options.name || ctx.parsed.prompt || "").trim();
293480
+ if (!name) return { text: "resource-manager create-resource: --name is required" };
293481
+ const buildingId = String(ctx.parsed.options.building || ctx.parsed.options.buildingId || "").trim() || void 0;
293482
+ const capacity = Number(ctx.parsed.options.capacity || 0) || void 0;
293483
+ const resourceType = String(ctx.parsed.options.type || "CONFERENCE_ROOM").trim();
293484
+ const floorName = String(ctx.parsed.options.floor || "").trim() || void 0;
293485
+ const description = String(ctx.parsed.options.description || "").trim() || void 0;
293486
+ return await createResource2(token, { name, buildingId, capacity, resourceType, floorName, description }, ctx.abortSignal);
293487
+ }
293488
+ if (sub === "update-building") {
293489
+ const id = String(ctx.parsed.options.id || "").trim();
293490
+ if (!id) return { text: "resource-manager update-building: --id is required" };
293491
+ const body = {};
293492
+ const name = String(ctx.parsed.options.name || "").trim();
293493
+ if (name) body.buildingName = name;
293494
+ const floorsRaw = String(ctx.parsed.options.floors || "").trim();
293495
+ if (floorsRaw) {
293496
+ const n = Number(floorsRaw);
293497
+ if (!isNaN(n) && n > 0 && Number.isInteger(n)) {
293498
+ body.floorNames = Array.from({ length: n }, (_, i2) => String(i2 + 1));
293499
+ } else {
293500
+ body.floorNames = floorsRaw.split(",").map((f3) => f3.trim()).filter(Boolean);
293501
+ }
293502
+ }
293503
+ const description = String(ctx.parsed.options.description || "").trim();
293504
+ if (description) body.description = description;
293505
+ if (Object.keys(body).length === 0) return { text: "resource-manager update-building: at least one field to update is required (--name, --floors, --description)" };
293506
+ return await updateBuilding(token, id, body, ctx.abortSignal);
293507
+ }
293508
+ if (sub === "update-resource") {
293509
+ const id = String(ctx.parsed.options.id || "").trim();
293510
+ if (!id) return { text: "resource-manager update-resource: --id is required" };
293511
+ const body = {};
293512
+ const name = String(ctx.parsed.options.name || "").trim();
293513
+ if (name) body.resourceName = name;
293514
+ const buildingId = String(ctx.parsed.options.building || ctx.parsed.options.buildingId || "").trim();
293515
+ if (buildingId) body.buildingId = buildingId;
293516
+ const capacity = Number(ctx.parsed.options.capacity || 0);
293517
+ if (capacity) body.capacity = capacity;
293518
+ const resourceType = String(ctx.parsed.options.type || "").trim();
293519
+ if (resourceType) body.resourceType = resourceType;
293520
+ const floorName = String(ctx.parsed.options.floor || "").trim();
293521
+ if (floorName) body.floorName = floorName;
293522
+ const description = String(ctx.parsed.options.description || "").trim();
293523
+ if (description) body.resourceDescription = description;
293524
+ if (Object.keys(body).length === 0) return { text: "resource-manager update-resource: at least one field to update is required" };
293525
+ return await updateResource(token, id, body, ctx.abortSignal);
293526
+ }
293527
+ return { text: `resource-manager: unknown subcommand "${sub}". Use buildings, resources, create-building, create-resource, update-building, or update-resource.` };
293528
+ }
293529
+ }
293530
+ ];
293531
+ };
293532
+ ResourceManagerChecker = class extends LiteCheckerAgent {
293533
+ commandId = "resource-manager";
293534
+ async check(_ctx, _input) {
293535
+ return { outcome: "PASS", reasons: ["resource_manager_ok"] };
293536
+ }
293537
+ };
293538
+ }
293539
+ });
293540
+
291463
293541
  // runtime/ext/ext-loader.ts
291464
293542
  var ext_loader_exports = {};
291465
293543
  __export(ext_loader_exports, {
@@ -291987,6 +294065,7 @@ function registerCoreLiteCommands(registry) {
291987
294065
  safeRegister(registry, "gcal", createGcalField);
291988
294066
  safeRegister(registry, "gdrive", createGdriveField);
291989
294067
  safeRegister(registry, "gmeet", createGmeetField);
294068
+ safeRegister(registry, "resource-manager", createResourceManagerField);
291990
294069
  }
291991
294070
  function createLiteCommandProviders() {
291992
294071
  const core2 = {
@@ -292116,6 +294195,7 @@ var init_command_providers = __esm({
292116
294195
  init_phone_hp_import_field();
292117
294196
  init_phone_deploy_field();
292118
294197
  init_phone_init_field();
294198
+ init_resource_manager_field();
292119
294199
  init_ext_loader();
292120
294200
  init_beta_loader();
292121
294201
  }