@commandgarden/cli 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +6 -6
- 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 +148 -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();
|
|
@@ -375,65 +392,154 @@ async function captureNewSchedule(knownIds) {
|
|
|
375
392
|
return { newId, schedule, sawView, errMsg, items: [...itemsById.values()] };
|
|
376
393
|
}
|
|
377
394
|
|
|
378
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Batch capture: wait for getSchedule responses covering all expected emails.
|
|
397
|
+
* Returns a Map of email (lowercased) -> { schedule, items, sawView, errMsg }.
|
|
398
|
+
*/
|
|
399
|
+
async function captureBatchSchedules(expectedEmails, knownIds) {
|
|
400
|
+
const byEmail = new Map();
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < 80; i++) {
|
|
403
|
+
await sleep(100);
|
|
404
|
+
for (const pair of readCapture()) {
|
|
405
|
+
for (const [id, s] of schedulesFromPair(pair)) {
|
|
406
|
+
if (knownIds.has(id)) continue;
|
|
407
|
+
const idLower = id.toLowerCase();
|
|
408
|
+
if (!expectedEmails.has(idLower)) continue;
|
|
409
|
+
const items = (s.scheduleItems || []).filter(Boolean);
|
|
410
|
+
byEmail.set(idLower, {
|
|
411
|
+
schedule: s,
|
|
412
|
+
items,
|
|
413
|
+
sawView: !!(s.availabilityView && s.availabilityView.length),
|
|
414
|
+
errMsg: s.error ? (s.error.message || s.error.responseCode || 'unknown') : null,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (expectedEmails.size > 0 && [...expectedEmails].every(e => byEmail.has(e))) break;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return byEmail;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ── Room processing ──────────────────────────────────────────────────
|
|
379
425
|
|
|
380
426
|
const allRows = [];
|
|
381
427
|
const knownIds = new Set(seen);
|
|
382
428
|
|
|
383
|
-
|
|
384
|
-
|
|
429
|
+
if (canBatch) {
|
|
430
|
+
// ── Batch mode: add all rooms via room finder, single capture ──────
|
|
431
|
+
const emailToName = new Map();
|
|
432
|
+
const expectedEmails = new Set();
|
|
433
|
+
for (const r of parsedRooms) {
|
|
434
|
+
emailToName.set(r.email, r.name);
|
|
435
|
+
expectedEmails.add(r.email);
|
|
436
|
+
}
|
|
385
437
|
|
|
386
|
-
// Clear
|
|
438
|
+
// Clear stale captures, then add all rooms without dismissing.
|
|
387
439
|
readCapture();
|
|
388
440
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
await
|
|
392
|
-
|
|
393
|
-
|
|
441
|
+
for (const r of parsedRooms) {
|
|
442
|
+
try {
|
|
443
|
+
await addRoomByName(r.name);
|
|
444
|
+
await sleep(300);
|
|
445
|
+
} catch (addErr) {
|
|
446
|
+
allRows.push({
|
|
447
|
+
roomName: r.name, roomEmail: r.email, date,
|
|
448
|
+
state: 'error', start: addErr.message, end: '', durationMin: 0,
|
|
449
|
+
});
|
|
450
|
+
expectedEmails.delete(r.email);
|
|
394
451
|
}
|
|
395
|
-
} catch (addErr) {
|
|
396
|
-
allRows.push({
|
|
397
|
-
roomName: room, roomEmail: '', date,
|
|
398
|
-
state: 'error', start: addErr.message, end: '', durationMin: 0,
|
|
399
|
-
});
|
|
400
|
-
continue;
|
|
401
452
|
}
|
|
402
453
|
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
454
|
+
// Single batch capture for all successfully added rooms.
|
|
455
|
+
if (expectedEmails.size > 0) {
|
|
456
|
+
const results = await captureBatchSchedules(expectedEmails, knownIds);
|
|
457
|
+
|
|
458
|
+
for (const [email, data] of results) {
|
|
459
|
+
const name = emailToName.get(email);
|
|
460
|
+
knownIds.add(email);
|
|
461
|
+
if (data.errMsg && !data.sawView) {
|
|
462
|
+
allRows.push({
|
|
463
|
+
roomName: name, roomEmail: email, date,
|
|
464
|
+
state: 'error', start: `Free/busy error: ${data.errMsg}`,
|
|
465
|
+
end: '', durationMin: 0,
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
const timeline = buildTimeline(data.items, date);
|
|
469
|
+
for (const row of timeline) {
|
|
470
|
+
allRows.push({ roomName: name, roomEmail: email, ...row });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
408
474
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
475
|
+
// Report rooms not found in any captured response.
|
|
476
|
+
for (const email of expectedEmails) {
|
|
477
|
+
if (!results.has(email)) {
|
|
478
|
+
allRows.push({
|
|
479
|
+
roomName: emailToName.get(email), roomEmail: email, date,
|
|
480
|
+
state: 'error', start: `No free/busy returned for "${emailToName.get(email)}" on ${date}`,
|
|
481
|
+
end: '', durationMin: 0,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
416
485
|
}
|
|
486
|
+
} else {
|
|
487
|
+
// ── Sequential mode (original behavior) ────────────────────────────
|
|
488
|
+
for (const r of parsedRooms) {
|
|
489
|
+
const isEmail = r.mode === 'email';
|
|
490
|
+
|
|
491
|
+
// Clear any pending captures before adding this room.
|
|
492
|
+
readCapture();
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
if (isEmail) {
|
|
496
|
+
await addRoomByEmail(r.name);
|
|
497
|
+
} else {
|
|
498
|
+
await addRoomByName(r.name);
|
|
499
|
+
}
|
|
500
|
+
} catch (addErr) {
|
|
501
|
+
allRows.push({
|
|
502
|
+
roomName: r.name, roomEmail: r.email || '', date,
|
|
503
|
+
state: 'error', start: addErr.message, end: '', durationMin: 0,
|
|
504
|
+
});
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
417
507
|
|
|
418
|
-
|
|
508
|
+
// Capture the getSchedule response immediately after adding (before dismiss).
|
|
509
|
+
const result = await captureNewSchedule(knownIds);
|
|
419
510
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
511
|
+
// Remove the room so "Add a room" reappears for the next room.
|
|
512
|
+
await dismissRoom();
|
|
513
|
+
|
|
514
|
+
if (!result.newId) {
|
|
515
|
+
allRows.push({
|
|
516
|
+
roomName: r.name, roomEmail: r.email || '', date,
|
|
517
|
+
state: 'error', start: `No free/busy returned for "${r.name}" on ${date}`,
|
|
518
|
+
end: '', durationMin: 0,
|
|
519
|
+
});
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
428
522
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
523
|
+
knownIds.add(result.newId);
|
|
524
|
+
|
|
525
|
+
if (result.errMsg && !result.sawView) {
|
|
526
|
+
allRows.push({
|
|
527
|
+
roomName: r.name, roomEmail: result.newId, date,
|
|
528
|
+
state: 'error', start: `Free/busy error: ${result.errMsg}`,
|
|
529
|
+
end: '', durationMin: 0,
|
|
530
|
+
});
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const timeline = buildTimeline(result.items, date);
|
|
535
|
+
for (const row of timeline) {
|
|
536
|
+
allRows.push({ roomName: r.name, roomEmail: result.newId, ...row });
|
|
537
|
+
}
|
|
432
538
|
}
|
|
433
539
|
}
|
|
434
540
|
|
|
435
541
|
if (allRows.length === 0) {
|
|
436
|
-
throw new Error(`No results for any of the ${
|
|
542
|
+
throw new Error(`No results for any of the ${parsedRooms.length} room(s) on ${date}`);
|
|
437
543
|
}
|
|
438
544
|
|
|
439
545
|
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
|