@commandgarden/cli 2.4.0 → 2.5.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/node_modules/@commandgarden/app/dist/client/assets/{index--IDuJWmP.js → index-DCptfmmK.js} +21 -21
- package/node_modules/@commandgarden/app/dist/client/index.html +1 -1
- package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.eval.js +1 -0
- package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.eval.js +150 -42
- package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.yaml +1 -1
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
11
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
12
12
|
<title>commandGarden</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-DCptfmmK.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/index-qunCIeG9.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -191,8 +191,25 @@ function buildTimeline(items, dateIso) {
|
|
|
191
191
|
|
|
192
192
|
const rawRooms = '${{ args.rooms }}'.trim();
|
|
193
193
|
if (!rawRooms) throw new Error('Missing rooms argument — pass comma-separated room names or emails');
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
194
|
+
const roomTokens = rawRooms.split(',').map(r => r.trim()).filter(r => r.length > 0);
|
|
195
|
+
if (roomTokens.length === 0) throw new Error('No rooms provided after parsing');
|
|
196
|
+
|
|
197
|
+
const parsedRooms = roomTokens.map(r => {
|
|
198
|
+
const colonIdx = r.lastIndexOf(':');
|
|
199
|
+
if (colonIdx > 0) {
|
|
200
|
+
const name = r.substring(0, colonIdx).trim();
|
|
201
|
+
const email = r.substring(colonIdx + 1).trim();
|
|
202
|
+
if (EMAIL_RE.test(email)) {
|
|
203
|
+
return { name, email: email.toLowerCase(), mode: 'pair' };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (EMAIL_RE.test(r)) {
|
|
207
|
+
return { name: r, email: r.toLowerCase(), mode: 'email' };
|
|
208
|
+
}
|
|
209
|
+
return { name: r, email: null, mode: 'name' };
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const canBatch = parsedRooms.every(r => r.mode === 'pair');
|
|
196
213
|
|
|
197
214
|
let date = '${{ args.date | default("") }}'.trim();
|
|
198
215
|
if (!date) date = todayLocalISO();
|
|
@@ -309,6 +326,8 @@ async function addRoomByName(name) {
|
|
|
309
326
|
throw new Error('Could not open the room finder');
|
|
310
327
|
}
|
|
311
328
|
typeText(inputSel, name);
|
|
329
|
+
// Wait for OWA to process the search and refresh suggestions.
|
|
330
|
+
await sleep(1500);
|
|
312
331
|
}
|
|
313
332
|
|
|
314
333
|
let label = null;
|
|
@@ -375,65 +394,154 @@ async function captureNewSchedule(knownIds) {
|
|
|
375
394
|
return { newId, schedule, sawView, errMsg, items: [...itemsById.values()] };
|
|
376
395
|
}
|
|
377
396
|
|
|
378
|
-
|
|
397
|
+
/**
|
|
398
|
+
* Batch capture: wait for getSchedule responses covering all expected emails.
|
|
399
|
+
* Returns a Map of email (lowercased) -> { schedule, items, sawView, errMsg }.
|
|
400
|
+
*/
|
|
401
|
+
async function captureBatchSchedules(expectedEmails, knownIds) {
|
|
402
|
+
const byEmail = new Map();
|
|
403
|
+
|
|
404
|
+
for (let i = 0; i < 80; i++) {
|
|
405
|
+
await sleep(100);
|
|
406
|
+
for (const pair of readCapture()) {
|
|
407
|
+
for (const [id, s] of schedulesFromPair(pair)) {
|
|
408
|
+
if (knownIds.has(id)) continue;
|
|
409
|
+
const idLower = id.toLowerCase();
|
|
410
|
+
if (!expectedEmails.has(idLower)) continue;
|
|
411
|
+
const items = (s.scheduleItems || []).filter(Boolean);
|
|
412
|
+
byEmail.set(idLower, {
|
|
413
|
+
schedule: s,
|
|
414
|
+
items,
|
|
415
|
+
sawView: !!(s.availabilityView && s.availabilityView.length),
|
|
416
|
+
errMsg: s.error ? (s.error.message || s.error.responseCode || 'unknown') : null,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (expectedEmails.size > 0 && [...expectedEmails].every(e => byEmail.has(e))) break;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return byEmail;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── Room processing ──────────────────────────────────────────────────
|
|
379
427
|
|
|
380
428
|
const allRows = [];
|
|
381
429
|
const knownIds = new Set(seen);
|
|
382
430
|
|
|
383
|
-
|
|
384
|
-
|
|
431
|
+
if (canBatch) {
|
|
432
|
+
// ── Batch mode: add all rooms via room finder, single capture ──────
|
|
433
|
+
const emailToName = new Map();
|
|
434
|
+
const expectedEmails = new Set();
|
|
435
|
+
for (const r of parsedRooms) {
|
|
436
|
+
emailToName.set(r.email, r.name);
|
|
437
|
+
expectedEmails.add(r.email);
|
|
438
|
+
}
|
|
385
439
|
|
|
386
|
-
// Clear
|
|
440
|
+
// Clear stale captures, then add all rooms without dismissing.
|
|
387
441
|
readCapture();
|
|
388
442
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
await
|
|
392
|
-
|
|
393
|
-
|
|
443
|
+
for (const r of parsedRooms) {
|
|
444
|
+
try {
|
|
445
|
+
await addRoomByName(r.name);
|
|
446
|
+
await sleep(300);
|
|
447
|
+
} catch (addErr) {
|
|
448
|
+
allRows.push({
|
|
449
|
+
roomName: r.name, roomEmail: r.email, date,
|
|
450
|
+
state: 'error', start: addErr.message, end: '', durationMin: 0,
|
|
451
|
+
});
|
|
452
|
+
expectedEmails.delete(r.email);
|
|
394
453
|
}
|
|
395
|
-
} catch (addErr) {
|
|
396
|
-
allRows.push({
|
|
397
|
-
roomName: room, roomEmail: '', date,
|
|
398
|
-
state: 'error', start: addErr.message, end: '', durationMin: 0,
|
|
399
|
-
});
|
|
400
|
-
continue;
|
|
401
454
|
}
|
|
402
455
|
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
456
|
+
// Single batch capture for all successfully added rooms.
|
|
457
|
+
if (expectedEmails.size > 0) {
|
|
458
|
+
const results = await captureBatchSchedules(expectedEmails, knownIds);
|
|
459
|
+
|
|
460
|
+
for (const [email, data] of results) {
|
|
461
|
+
const name = emailToName.get(email);
|
|
462
|
+
knownIds.add(email);
|
|
463
|
+
if (data.errMsg && !data.sawView) {
|
|
464
|
+
allRows.push({
|
|
465
|
+
roomName: name, roomEmail: email, date,
|
|
466
|
+
state: 'error', start: `Free/busy error: ${data.errMsg}`,
|
|
467
|
+
end: '', durationMin: 0,
|
|
468
|
+
});
|
|
469
|
+
} else {
|
|
470
|
+
const timeline = buildTimeline(data.items, date);
|
|
471
|
+
for (const row of timeline) {
|
|
472
|
+
allRows.push({ roomName: name, roomEmail: email, ...row });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
408
476
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
477
|
+
// Report rooms not found in any captured response.
|
|
478
|
+
for (const email of expectedEmails) {
|
|
479
|
+
if (!results.has(email)) {
|
|
480
|
+
allRows.push({
|
|
481
|
+
roomName: emailToName.get(email), roomEmail: email, date,
|
|
482
|
+
state: 'error', start: `No free/busy returned for "${emailToName.get(email)}" on ${date}`,
|
|
483
|
+
end: '', durationMin: 0,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
416
487
|
}
|
|
488
|
+
} else {
|
|
489
|
+
// ── Sequential mode (original behavior) ────────────────────────────
|
|
490
|
+
for (const r of parsedRooms) {
|
|
491
|
+
const isEmail = r.mode === 'email';
|
|
492
|
+
|
|
493
|
+
// Clear any pending captures before adding this room.
|
|
494
|
+
readCapture();
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
if (isEmail) {
|
|
498
|
+
await addRoomByEmail(r.name);
|
|
499
|
+
} else {
|
|
500
|
+
await addRoomByName(r.name);
|
|
501
|
+
}
|
|
502
|
+
} catch (addErr) {
|
|
503
|
+
allRows.push({
|
|
504
|
+
roomName: r.name, roomEmail: r.email || '', date,
|
|
505
|
+
state: 'error', start: addErr.message, end: '', durationMin: 0,
|
|
506
|
+
});
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
417
509
|
|
|
418
|
-
|
|
510
|
+
// Capture the getSchedule response immediately after adding (before dismiss).
|
|
511
|
+
const result = await captureNewSchedule(knownIds);
|
|
419
512
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
513
|
+
// Remove the room so "Add a room" reappears for the next room.
|
|
514
|
+
await dismissRoom();
|
|
515
|
+
|
|
516
|
+
if (!result.newId) {
|
|
517
|
+
allRows.push({
|
|
518
|
+
roomName: r.name, roomEmail: r.email || '', date,
|
|
519
|
+
state: 'error', start: `No free/busy returned for "${r.name}" on ${date}`,
|
|
520
|
+
end: '', durationMin: 0,
|
|
521
|
+
});
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
428
524
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
525
|
+
knownIds.add(result.newId);
|
|
526
|
+
|
|
527
|
+
if (result.errMsg && !result.sawView) {
|
|
528
|
+
allRows.push({
|
|
529
|
+
roomName: r.name, roomEmail: result.newId, date,
|
|
530
|
+
state: 'error', start: `Free/busy error: ${result.errMsg}`,
|
|
531
|
+
end: '', durationMin: 0,
|
|
532
|
+
});
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const timeline = buildTimeline(result.items, date);
|
|
537
|
+
for (const row of timeline) {
|
|
538
|
+
allRows.push({ roomName: r.name, roomEmail: result.newId, ...row });
|
|
539
|
+
}
|
|
432
540
|
}
|
|
433
541
|
}
|
|
434
542
|
|
|
435
543
|
if (allRows.length === 0) {
|
|
436
|
-
throw new Error(`No results for any of the ${
|
|
544
|
+
throw new Error(`No results for any of the ${parsedRooms.length} room(s) on ${date}`);
|
|
437
545
|
}
|
|
438
546
|
|
|
439
547
|
return allRows;
|
|
@@ -15,7 +15,7 @@ args:
|
|
|
15
15
|
- name: rooms
|
|
16
16
|
type: string
|
|
17
17
|
required: true
|
|
18
|
-
help: "Comma-separated room names or
|
|
18
|
+
help: "Comma-separated room names, emails, or name:email pairs (e.g. 'MBTMY The Vista:RES-RERE-M6VJ7ZUW@mercedes-benz.com,MBTMY The Cliffside:res-rere-m6vjl2tw@mercedes-benz.com' for batch mode, or 'MBTMY The Vista,res-rere-m6vj7zuw@mercedes-benz.com' for sequential)"
|
|
19
19
|
- name: date
|
|
20
20
|
type: string
|
|
21
21
|
required: false
|