@commandgarden/cli 2.1.0 → 2.2.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 +13 -8
- package/node_modules/@commandgarden/app/dist/client/assets/{index-JB_Cdl_F.css → index-Cglxej9D.css} +1 -1
- package/node_modules/@commandgarden/app/dist/client/assets/index-Cju0_G1Y.js +397 -0
- package/node_modules/@commandgarden/app/dist/client/index.html +19 -19
- package/node_modules/@commandgarden/app/dist/server/routes/connectors.js +1 -0
- package/node_modules/@commandgarden/app/dist/server/store.js +31 -31
- package/node_modules/@commandgarden/app/package.json +42 -42
- package/node_modules/@commandgarden/chrome/dist/content-script.js.map +1 -1
- package/node_modules/@commandgarden/chrome/dist/manifest.json +16 -16
- package/node_modules/@commandgarden/chrome/dist/popup.html +218 -218
- package/node_modules/@commandgarden/chrome/dist/popup.js.map +1 -1
- package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +1 -1
- package/node_modules/@commandgarden/chrome/package.json +25 -25
- package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +143 -143
- package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +70 -70
- package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +58 -58
- package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +47 -47
- package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -115
- package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -54
- package/node_modules/@commandgarden/daemon/connectors/lib/msal-token.js +20 -20
- package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +186 -186
- package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +41 -41
- package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.eval.js +94 -0
- package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.yaml +34 -0
- package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -63
- package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.eval.js +341 -341
- package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.yaml +46 -46
- package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.eval.js +52 -52
- package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.yaml +42 -42
- package/node_modules/@commandgarden/daemon/connectors/timetracking-report.eval.js +81 -81
- package/node_modules/@commandgarden/daemon/connectors/timetracking-report.yaml +52 -52
- package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.eval.js +27 -27
- package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.yaml +38 -38
- package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -55
- package/node_modules/@commandgarden/daemon/dist/audit-store.js +8 -8
- package/node_modules/@commandgarden/daemon/package.json +32 -32
- package/node_modules/@commandgarden/shared/package.json +32 -32
- package/package.json +58 -57
- package/node_modules/@commandgarden/app/dist/client/assets/index-GpK1-Tyq.js +0 -255
- package/node_modules/@commandgarden/app/dist/server/daemon-client.test.js +0 -47
- package/node_modules/@commandgarden/app/dist/server/routes/routes.test.js +0 -137
- package/node_modules/@commandgarden/app/dist/server/store.test.js +0 -59
- package/node_modules/@commandgarden/chrome/dist/fetch-interceptor.js +0 -44
- package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts +0 -24
- package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts.map +0 -1
- package/node_modules/@commandgarden/daemon/dist/cdp-client.js +0 -150
- package/node_modules/@commandgarden/daemon/dist/cdp-client.js.map +0 -1
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
// Runs in page context via js_evaluate step.
|
|
2
|
-
// Captures the user's calendar events by intercepting OWA's own data fetches.
|
|
3
|
-
//
|
|
4
|
-
// Strategy: patch fetch() and XMLHttpRequest to capture responses containing
|
|
5
|
-
// calendar event data. OWA loads calendar events automatically when the
|
|
6
|
-
// calendar page opens — we just wait and read what it fetched.
|
|
7
|
-
//
|
|
8
|
-
// This avoids all auth issues (no tokens, no CANARY, no cross-origin) because
|
|
9
|
-
// we're reading data OWA already fetched for itself.
|
|
10
|
-
//
|
|
11
|
-
// Template variables interpolated before execution:
|
|
12
|
-
// ${{ args.date }}
|
|
13
|
-
|
|
14
|
-
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
15
|
-
function pad2(n) { return String(n).padStart(2, '0'); }
|
|
16
|
-
|
|
17
|
-
const DATE_RE = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
18
|
-
const date = '${{ args.date }}'.trim();
|
|
19
|
-
if (!DATE_RE.test(date)) throw new Error(`Invalid date "${date}" — use YYYY-MM-DD`);
|
|
20
|
-
|
|
21
|
-
// ── Wait for OWA to finish SSO/redirect (up to 30s) ──────────────────
|
|
22
|
-
const __deadline = Date.now() + 30000;
|
|
23
|
-
while (Date.now() < __deadline) {
|
|
24
|
-
if (location.hostname.includes('outlook') && !(/login\.|\/oauth2\/|signin|sso/i.test(location.href))) break;
|
|
25
|
-
await sleep(1000);
|
|
26
|
-
}
|
|
27
|
-
if (/login\.|\/oauth2\/|signin|sso/i.test(location.href)) {
|
|
28
|
-
throw new Error('Not signed in to Outlook — log in and retry');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ── Install interceptors to capture calendar event data ───────────────
|
|
32
|
-
// OWA uses both fetch() and XMLHttpRequest for API calls. We patch both
|
|
33
|
-
// to capture any response containing calendar events (Items, calendarView,
|
|
34
|
-
// getSchedule, FindItem).
|
|
35
|
-
if (!window.__calEvents) {
|
|
36
|
-
window.__calEvents = [];
|
|
37
|
-
|
|
38
|
-
// Patch fetch
|
|
39
|
-
const origFetch = window.fetch;
|
|
40
|
-
window.fetch = function () {
|
|
41
|
-
const args = arguments;
|
|
42
|
-
return origFetch.apply(this, args).then(r => {
|
|
43
|
-
try {
|
|
44
|
-
r.clone().text().then(t => {
|
|
45
|
-
// Capture responses that look like calendar event data
|
|
46
|
-
if (t.includes('"Subject"') && (t.includes('"Start"') || t.includes('"StartTime"'))) {
|
|
47
|
-
try {
|
|
48
|
-
const parsed = JSON.parse(t);
|
|
49
|
-
extractEvents(parsed);
|
|
50
|
-
} catch (_) {}
|
|
51
|
-
}
|
|
52
|
-
// Also capture getSchedule GraphQL responses (fallback)
|
|
53
|
-
if (t.includes('getSchedule') && t.includes('scheduleItems')) {
|
|
54
|
-
try {
|
|
55
|
-
const parsed = JSON.parse(t);
|
|
56
|
-
extractScheduleItems(parsed);
|
|
57
|
-
} catch (_) {}
|
|
58
|
-
}
|
|
59
|
-
}).catch(() => {});
|
|
60
|
-
} catch (_) {}
|
|
61
|
-
return r;
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// Patch XMLHttpRequest (OWA uses this for some API calls)
|
|
66
|
-
const origXHROpen = XMLHttpRequest.prototype.open;
|
|
67
|
-
const origXHRSend = XMLHttpRequest.prototype.send;
|
|
68
|
-
XMLHttpRequest.prototype.open = function () {
|
|
69
|
-
this.__url = arguments[1] || '';
|
|
70
|
-
return origXHROpen.apply(this, arguments);
|
|
71
|
-
};
|
|
72
|
-
XMLHttpRequest.prototype.send = function () {
|
|
73
|
-
this.addEventListener('load', function () {
|
|
74
|
-
try {
|
|
75
|
-
const t = this.responseText || '';
|
|
76
|
-
if (t.includes('"Subject"') && (t.includes('"Start"') || t.includes('"StartTime"'))) {
|
|
77
|
-
try { extractEvents(JSON.parse(t)); } catch (_) {}
|
|
78
|
-
}
|
|
79
|
-
} catch (_) {}
|
|
80
|
-
});
|
|
81
|
-
return origXHRSend.apply(this, arguments);
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Extract calendar events from OWA API response shapes. */
|
|
86
|
-
function extractEvents(data) {
|
|
87
|
-
// OWA returns events in various shapes; try common patterns
|
|
88
|
-
const items = data.value || data.Items || data.items || [];
|
|
89
|
-
const arr = Array.isArray(data) ? data : (Array.isArray(items) ? items : []);
|
|
90
|
-
for (const item of arr) {
|
|
91
|
-
if (item && (item.Subject || item.subject) && (item.Start || item.start)) {
|
|
92
|
-
window.__calEvents.push(item);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Extract events from getSchedule GraphQL responses (fallback path). */
|
|
98
|
-
function extractScheduleItems(data) {
|
|
99
|
-
const nodes = Array.isArray(data) ? data : [data];
|
|
100
|
-
for (const node of nodes) {
|
|
101
|
-
const schedules = node?.data?.getSchedule?.schedules;
|
|
102
|
-
if (!Array.isArray(schedules)) continue;
|
|
103
|
-
for (const s of schedules) {
|
|
104
|
-
for (const it of (s.scheduleItems || [])) {
|
|
105
|
-
if (it && it.startTime && it.endTime) {
|
|
106
|
-
// Convert getSchedule format to calendarView-like format
|
|
107
|
-
window.__calEvents.push({
|
|
108
|
-
subject: it.subject || '(meeting)',
|
|
109
|
-
start: it.startTime,
|
|
110
|
-
end: it.endTime,
|
|
111
|
-
showAs: it.status || 'Busy',
|
|
112
|
-
responseStatus: { response: 'Accepted' },
|
|
113
|
-
isAllDayEvent: false,
|
|
114
|
-
isCancelled: false,
|
|
115
|
-
_fromSchedule: true,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── Wait for OWA to load calendar data (up to 20s) ───────────────────
|
|
124
|
-
for (let i = 0; i < 20; i++) {
|
|
125
|
-
await sleep(1000);
|
|
126
|
-
if (window.__calEvents.length > 0) break;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Deduplicate events by start+end time
|
|
130
|
-
const seen = new Set();
|
|
131
|
-
const uniqueEvents = [];
|
|
132
|
-
for (const ev of window.__calEvents) {
|
|
133
|
-
const startStr = ev.Start?.DateTime || ev.start?.dateTime || ev.start?.DateTime || '';
|
|
134
|
-
const endStr = ev.End?.DateTime || ev.end?.dateTime || ev.end?.DateTime || '';
|
|
135
|
-
const key = startStr + '|' + endStr;
|
|
136
|
-
if (seen.has(key)) continue;
|
|
137
|
-
seen.add(key);
|
|
138
|
-
uniqueEvents.push(ev);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Clear captured events for next run
|
|
142
|
-
window.__calEvents = [];
|
|
143
|
-
|
|
144
|
-
// ── Filter to the target date and map to output rows ──────────────────
|
|
145
|
-
const rows = [];
|
|
146
|
-
for (const ev of uniqueEvents) {
|
|
147
|
-
// Normalize field names (OWA mixes PascalCase and camelCase)
|
|
148
|
-
const subject = ev.Subject || ev.subject || '(meeting)';
|
|
149
|
-
const startRaw = ev.Start?.DateTime || ev.start?.dateTime || ev.start?.DateTime || '';
|
|
150
|
-
const endRaw = ev.End?.DateTime || ev.end?.dateTime || ev.end?.DateTime || '';
|
|
151
|
-
const showAs = (ev.ShowAs || ev.showAs || 'busy');
|
|
152
|
-
const isCancelled = ev.IsCancelled || ev.isCancelled || false;
|
|
153
|
-
const isAllDay = ev.IsAllDayEvent || ev.isAllDayEvent || false;
|
|
154
|
-
const responseType = ev.ResponseStatus?.Response || ev.responseStatus?.response || '';
|
|
155
|
-
|
|
156
|
-
if (isCancelled || isAllDay) continue;
|
|
157
|
-
|
|
158
|
-
// Filter by response status if available (accepted/organizer only)
|
|
159
|
-
// If responseStatus is missing (e.g. from getSchedule), include the event
|
|
160
|
-
if (responseType && responseType !== 'Organizer' && responseType !== 'Accepted') continue;
|
|
161
|
-
|
|
162
|
-
// Only show busy/tentative items (skip free blocks from getSchedule)
|
|
163
|
-
const showAsLower = String(showAs).toLowerCase();
|
|
164
|
-
if (showAsLower === 'free') continue;
|
|
165
|
-
|
|
166
|
-
const startDt = new Date(startRaw.endsWith('Z') ? startRaw : startRaw + 'Z');
|
|
167
|
-
const endDt = new Date(endRaw.endsWith('Z') ? endRaw : endRaw + 'Z');
|
|
168
|
-
const evDate = `${startDt.getFullYear()}-${pad2(startDt.getMonth() + 1)}-${pad2(startDt.getDate())}`;
|
|
169
|
-
|
|
170
|
-
// Filter to the target date
|
|
171
|
-
if (evDate !== date) continue;
|
|
172
|
-
|
|
173
|
-
const durationMin = Math.round((endDt.getTime() - startDt.getTime()) / 60000);
|
|
174
|
-
if (durationMin < 5 || durationMin > 480) continue;
|
|
175
|
-
|
|
176
|
-
rows.push({
|
|
177
|
-
date: evDate,
|
|
178
|
-
subject: subject,
|
|
179
|
-
start: `${pad2(startDt.getHours())}:${pad2(startDt.getMinutes())}`,
|
|
180
|
-
end: `${pad2(endDt.getHours())}:${pad2(endDt.getMinutes())}`,
|
|
181
|
-
durationMin,
|
|
182
|
-
state: showAsLower,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return rows;
|
|
1
|
+
// Runs in page context via js_evaluate step.
|
|
2
|
+
// Captures the user's calendar events by intercepting OWA's own data fetches.
|
|
3
|
+
//
|
|
4
|
+
// Strategy: patch fetch() and XMLHttpRequest to capture responses containing
|
|
5
|
+
// calendar event data. OWA loads calendar events automatically when the
|
|
6
|
+
// calendar page opens — we just wait and read what it fetched.
|
|
7
|
+
//
|
|
8
|
+
// This avoids all auth issues (no tokens, no CANARY, no cross-origin) because
|
|
9
|
+
// we're reading data OWA already fetched for itself.
|
|
10
|
+
//
|
|
11
|
+
// Template variables interpolated before execution:
|
|
12
|
+
// ${{ args.date }}
|
|
13
|
+
|
|
14
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
15
|
+
function pad2(n) { return String(n).padStart(2, '0'); }
|
|
16
|
+
|
|
17
|
+
const DATE_RE = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
18
|
+
const date = '${{ args.date }}'.trim();
|
|
19
|
+
if (!DATE_RE.test(date)) throw new Error(`Invalid date "${date}" — use YYYY-MM-DD`);
|
|
20
|
+
|
|
21
|
+
// ── Wait for OWA to finish SSO/redirect (up to 30s) ──────────────────
|
|
22
|
+
const __deadline = Date.now() + 30000;
|
|
23
|
+
while (Date.now() < __deadline) {
|
|
24
|
+
if (location.hostname.includes('outlook') && !(/login\.|\/oauth2\/|signin|sso/i.test(location.href))) break;
|
|
25
|
+
await sleep(1000);
|
|
26
|
+
}
|
|
27
|
+
if (/login\.|\/oauth2\/|signin|sso/i.test(location.href)) {
|
|
28
|
+
throw new Error('Not signed in to Outlook — log in and retry');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Install interceptors to capture calendar event data ───────────────
|
|
32
|
+
// OWA uses both fetch() and XMLHttpRequest for API calls. We patch both
|
|
33
|
+
// to capture any response containing calendar events (Items, calendarView,
|
|
34
|
+
// getSchedule, FindItem).
|
|
35
|
+
if (!window.__calEvents) {
|
|
36
|
+
window.__calEvents = [];
|
|
37
|
+
|
|
38
|
+
// Patch fetch
|
|
39
|
+
const origFetch = window.fetch;
|
|
40
|
+
window.fetch = function () {
|
|
41
|
+
const args = arguments;
|
|
42
|
+
return origFetch.apply(this, args).then(r => {
|
|
43
|
+
try {
|
|
44
|
+
r.clone().text().then(t => {
|
|
45
|
+
// Capture responses that look like calendar event data
|
|
46
|
+
if (t.includes('"Subject"') && (t.includes('"Start"') || t.includes('"StartTime"'))) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(t);
|
|
49
|
+
extractEvents(parsed);
|
|
50
|
+
} catch (_) {}
|
|
51
|
+
}
|
|
52
|
+
// Also capture getSchedule GraphQL responses (fallback)
|
|
53
|
+
if (t.includes('getSchedule') && t.includes('scheduleItems')) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(t);
|
|
56
|
+
extractScheduleItems(parsed);
|
|
57
|
+
} catch (_) {}
|
|
58
|
+
}
|
|
59
|
+
}).catch(() => {});
|
|
60
|
+
} catch (_) {}
|
|
61
|
+
return r;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Patch XMLHttpRequest (OWA uses this for some API calls)
|
|
66
|
+
const origXHROpen = XMLHttpRequest.prototype.open;
|
|
67
|
+
const origXHRSend = XMLHttpRequest.prototype.send;
|
|
68
|
+
XMLHttpRequest.prototype.open = function () {
|
|
69
|
+
this.__url = arguments[1] || '';
|
|
70
|
+
return origXHROpen.apply(this, arguments);
|
|
71
|
+
};
|
|
72
|
+
XMLHttpRequest.prototype.send = function () {
|
|
73
|
+
this.addEventListener('load', function () {
|
|
74
|
+
try {
|
|
75
|
+
const t = this.responseText || '';
|
|
76
|
+
if (t.includes('"Subject"') && (t.includes('"Start"') || t.includes('"StartTime"'))) {
|
|
77
|
+
try { extractEvents(JSON.parse(t)); } catch (_) {}
|
|
78
|
+
}
|
|
79
|
+
} catch (_) {}
|
|
80
|
+
});
|
|
81
|
+
return origXHRSend.apply(this, arguments);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Extract calendar events from OWA API response shapes. */
|
|
86
|
+
function extractEvents(data) {
|
|
87
|
+
// OWA returns events in various shapes; try common patterns
|
|
88
|
+
const items = data.value || data.Items || data.items || [];
|
|
89
|
+
const arr = Array.isArray(data) ? data : (Array.isArray(items) ? items : []);
|
|
90
|
+
for (const item of arr) {
|
|
91
|
+
if (item && (item.Subject || item.subject) && (item.Start || item.start)) {
|
|
92
|
+
window.__calEvents.push(item);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Extract events from getSchedule GraphQL responses (fallback path). */
|
|
98
|
+
function extractScheduleItems(data) {
|
|
99
|
+
const nodes = Array.isArray(data) ? data : [data];
|
|
100
|
+
for (const node of nodes) {
|
|
101
|
+
const schedules = node?.data?.getSchedule?.schedules;
|
|
102
|
+
if (!Array.isArray(schedules)) continue;
|
|
103
|
+
for (const s of schedules) {
|
|
104
|
+
for (const it of (s.scheduleItems || [])) {
|
|
105
|
+
if (it && it.startTime && it.endTime) {
|
|
106
|
+
// Convert getSchedule format to calendarView-like format
|
|
107
|
+
window.__calEvents.push({
|
|
108
|
+
subject: it.subject || '(meeting)',
|
|
109
|
+
start: it.startTime,
|
|
110
|
+
end: it.endTime,
|
|
111
|
+
showAs: it.status || 'Busy',
|
|
112
|
+
responseStatus: { response: 'Accepted' },
|
|
113
|
+
isAllDayEvent: false,
|
|
114
|
+
isCancelled: false,
|
|
115
|
+
_fromSchedule: true,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Wait for OWA to load calendar data (up to 20s) ───────────────────
|
|
124
|
+
for (let i = 0; i < 20; i++) {
|
|
125
|
+
await sleep(1000);
|
|
126
|
+
if (window.__calEvents.length > 0) break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Deduplicate events by start+end time
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
const uniqueEvents = [];
|
|
132
|
+
for (const ev of window.__calEvents) {
|
|
133
|
+
const startStr = ev.Start?.DateTime || ev.start?.dateTime || ev.start?.DateTime || '';
|
|
134
|
+
const endStr = ev.End?.DateTime || ev.end?.dateTime || ev.end?.DateTime || '';
|
|
135
|
+
const key = startStr + '|' + endStr;
|
|
136
|
+
if (seen.has(key)) continue;
|
|
137
|
+
seen.add(key);
|
|
138
|
+
uniqueEvents.push(ev);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Clear captured events for next run
|
|
142
|
+
window.__calEvents = [];
|
|
143
|
+
|
|
144
|
+
// ── Filter to the target date and map to output rows ──────────────────
|
|
145
|
+
const rows = [];
|
|
146
|
+
for (const ev of uniqueEvents) {
|
|
147
|
+
// Normalize field names (OWA mixes PascalCase and camelCase)
|
|
148
|
+
const subject = ev.Subject || ev.subject || '(meeting)';
|
|
149
|
+
const startRaw = ev.Start?.DateTime || ev.start?.dateTime || ev.start?.DateTime || '';
|
|
150
|
+
const endRaw = ev.End?.DateTime || ev.end?.dateTime || ev.end?.DateTime || '';
|
|
151
|
+
const showAs = (ev.ShowAs || ev.showAs || 'busy');
|
|
152
|
+
const isCancelled = ev.IsCancelled || ev.isCancelled || false;
|
|
153
|
+
const isAllDay = ev.IsAllDayEvent || ev.isAllDayEvent || false;
|
|
154
|
+
const responseType = ev.ResponseStatus?.Response || ev.responseStatus?.response || '';
|
|
155
|
+
|
|
156
|
+
if (isCancelled || isAllDay) continue;
|
|
157
|
+
|
|
158
|
+
// Filter by response status if available (accepted/organizer only)
|
|
159
|
+
// If responseStatus is missing (e.g. from getSchedule), include the event
|
|
160
|
+
if (responseType && responseType !== 'Organizer' && responseType !== 'Accepted') continue;
|
|
161
|
+
|
|
162
|
+
// Only show busy/tentative items (skip free blocks from getSchedule)
|
|
163
|
+
const showAsLower = String(showAs).toLowerCase();
|
|
164
|
+
if (showAsLower === 'free') continue;
|
|
165
|
+
|
|
166
|
+
const startDt = new Date(startRaw.endsWith('Z') ? startRaw : startRaw + 'Z');
|
|
167
|
+
const endDt = new Date(endRaw.endsWith('Z') ? endRaw : endRaw + 'Z');
|
|
168
|
+
const evDate = `${startDt.getFullYear()}-${pad2(startDt.getMonth() + 1)}-${pad2(startDt.getDate())}`;
|
|
169
|
+
|
|
170
|
+
// Filter to the target date
|
|
171
|
+
if (evDate !== date) continue;
|
|
172
|
+
|
|
173
|
+
const durationMin = Math.round((endDt.getTime() - startDt.getTime()) / 60000);
|
|
174
|
+
if (durationMin < 5 || durationMin > 480) continue;
|
|
175
|
+
|
|
176
|
+
rows.push({
|
|
177
|
+
date: evDate,
|
|
178
|
+
subject: subject,
|
|
179
|
+
start: `${pad2(startDt.getHours())}:${pad2(startDt.getMinutes())}`,
|
|
180
|
+
end: `${pad2(endDt.getHours())}:${pad2(endDt.getMinutes())}`,
|
|
181
|
+
durationMin,
|
|
182
|
+
state: showAsLower,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return rows;
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
site: outlook
|
|
2
|
-
name: my-meetings
|
|
3
|
-
version: "1.0"
|
|
4
|
-
description: "User's own meeting schedule from Outlook Calendar for a given day"
|
|
5
|
-
access: read
|
|
6
|
-
|
|
7
|
-
domains:
|
|
8
|
-
- "outlook.cloud.microsoft.mcas.ms"
|
|
9
|
-
capabilities:
|
|
10
|
-
- navigate
|
|
11
|
-
- js_evaluate
|
|
12
|
-
|
|
13
|
-
args:
|
|
14
|
-
- name: date
|
|
15
|
-
type: string
|
|
16
|
-
required: true
|
|
17
|
-
help: "Day to fetch in YYYY-MM-DD format"
|
|
18
|
-
pattern: "^\\d{4}-\\d{2}-\\d{2}$"
|
|
19
|
-
|
|
20
|
-
columns:
|
|
21
|
-
- name: date
|
|
22
|
-
type: string
|
|
23
|
-
- name: subject
|
|
24
|
-
type: string
|
|
25
|
-
- name: start
|
|
26
|
-
type: string
|
|
27
|
-
- name: end
|
|
28
|
-
type: string
|
|
29
|
-
- name: durationMin
|
|
30
|
-
type: number
|
|
31
|
-
- name: state
|
|
32
|
-
type: string
|
|
33
|
-
|
|
34
|
-
pipeline:
|
|
35
|
-
- step: navigate
|
|
36
|
-
url: "https://outlook.cloud.microsoft.mcas.ms/calendar/deeplink/compose"
|
|
37
|
-
- step: wait
|
|
38
|
-
selector: "body"
|
|
39
|
-
timeout: 30000
|
|
40
|
-
- step: js_evaluate
|
|
41
|
-
file: outlook-my-meetings.eval.js
|
|
1
|
+
site: outlook
|
|
2
|
+
name: my-meetings
|
|
3
|
+
version: "1.0"
|
|
4
|
+
description: "User's own meeting schedule from Outlook Calendar for a given day"
|
|
5
|
+
access: read
|
|
6
|
+
|
|
7
|
+
domains:
|
|
8
|
+
- "outlook.cloud.microsoft.mcas.ms"
|
|
9
|
+
capabilities:
|
|
10
|
+
- navigate
|
|
11
|
+
- js_evaluate
|
|
12
|
+
|
|
13
|
+
args:
|
|
14
|
+
- name: date
|
|
15
|
+
type: string
|
|
16
|
+
required: true
|
|
17
|
+
help: "Day to fetch in YYYY-MM-DD format"
|
|
18
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}$"
|
|
19
|
+
|
|
20
|
+
columns:
|
|
21
|
+
- name: date
|
|
22
|
+
type: string
|
|
23
|
+
- name: subject
|
|
24
|
+
type: string
|
|
25
|
+
- name: start
|
|
26
|
+
type: string
|
|
27
|
+
- name: end
|
|
28
|
+
type: string
|
|
29
|
+
- name: durationMin
|
|
30
|
+
type: number
|
|
31
|
+
- name: state
|
|
32
|
+
type: string
|
|
33
|
+
|
|
34
|
+
pipeline:
|
|
35
|
+
- step: navigate
|
|
36
|
+
url: "https://outlook.cloud.microsoft.mcas.ms/calendar/deeplink/compose"
|
|
37
|
+
- step: wait
|
|
38
|
+
selector: "body"
|
|
39
|
+
timeout: 30000
|
|
40
|
+
- step: js_evaluate
|
|
41
|
+
file: outlook-my-meetings.eval.js
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Runs in Saba Cloud page context via js_evaluate step.
|
|
2
|
+
// Fetches pending mandatory learning items from Saba Cloud (v64+) using two
|
|
3
|
+
// confirmed API endpoints:
|
|
4
|
+
//
|
|
5
|
+
// 1. GET /Saba/api/ui/torque/uicontext/currentuser
|
|
6
|
+
// → userInfo.userId (e.g. "emplo000000001653633")
|
|
7
|
+
//
|
|
8
|
+
// 2. GET /Saba/api/common/todocontroller/detail/{userId}?context=learning&expand=savetabpref
|
|
9
|
+
// → searchResults[1] (Saba list format: ["list", [...items]])
|
|
10
|
+
|
|
11
|
+
const ORIGIN = 'https://daimler.sabacloud.com';
|
|
12
|
+
|
|
13
|
+
// ── Wait for valid Saba session (SSO / redirects, up to 60s) ─────────────
|
|
14
|
+
|
|
15
|
+
const __deadline = Date.now() + 60000;
|
|
16
|
+
while (Date.now() < __deadline) {
|
|
17
|
+
if (location.hostname === 'daimler.sabacloud.com' && document.readyState === 'complete') break;
|
|
18
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
19
|
+
}
|
|
20
|
+
if (location.hostname !== 'daimler.sabacloud.com') {
|
|
21
|
+
throw new Error('AUTH_REQUIRED: Not signed in to Saba — log in at daimler.sabacloud.com and retry');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Step 1: Resolve current user ID ──────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const meResp = await fetch(`${ORIGIN}/Saba/api/ui/torque/uicontext/currentuser`, {
|
|
27
|
+
headers: { Accept: 'application/json' },
|
|
28
|
+
credentials: 'include',
|
|
29
|
+
});
|
|
30
|
+
if (meResp.status === 401 || meResp.status === 403) {
|
|
31
|
+
throw new Error('AUTH_REQUIRED: Saba session expired — log in and retry');
|
|
32
|
+
}
|
|
33
|
+
if (!meResp.ok) {
|
|
34
|
+
throw new Error(`currentuser API returned HTTP ${meResp.status}`);
|
|
35
|
+
}
|
|
36
|
+
const meData = await meResp.json();
|
|
37
|
+
const userId = meData?.userInfo?.userId;
|
|
38
|
+
if (!userId) {
|
|
39
|
+
throw new Error('Could not read userInfo.userId from currentuser response');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Step 2: Fetch pending learning to-do items ────────────────────────────
|
|
43
|
+
|
|
44
|
+
const todoResp = await fetch(
|
|
45
|
+
`${ORIGIN}/Saba/api/common/todocontroller/detail/${encodeURIComponent(userId)}?context=learning&expand=savetabpref`,
|
|
46
|
+
{ headers: { Accept: 'application/json' }, credentials: 'include' }
|
|
47
|
+
);
|
|
48
|
+
if (!todoResp.ok) {
|
|
49
|
+
throw new Error(`todocontroller API returned HTTP ${todoResp.status} for userId=${userId}`);
|
|
50
|
+
}
|
|
51
|
+
const todoData = await todoResp.json();
|
|
52
|
+
|
|
53
|
+
// searchResults is serialised as ["list", [item, item, ...]]
|
|
54
|
+
const rawList = todoData?.searchResults;
|
|
55
|
+
const items = Array.isArray(rawList)
|
|
56
|
+
? (rawList[0] === 'list' ? rawList[1] : rawList)
|
|
57
|
+
: [];
|
|
58
|
+
|
|
59
|
+
if (!Array.isArray(items)) {
|
|
60
|
+
throw new Error('Unexpected todocontroller response shape — searchResults is not a list');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Step 3: Process and return rows ──────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
const DONE_STATUSES = new Set([
|
|
66
|
+
'completed', 'passed', 'withdrawn', 'cancelled', 'waived', 'successful',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const today = new Date();
|
|
70
|
+
today.setHours(0, 0, 0, 0);
|
|
71
|
+
|
|
72
|
+
return items
|
|
73
|
+
.filter(item => {
|
|
74
|
+
if (!item.mandatory) return false;
|
|
75
|
+
const status = (item.itemDisplayStatus || '').toLowerCase();
|
|
76
|
+
return !DONE_STATUSES.has(status);
|
|
77
|
+
})
|
|
78
|
+
.map(item => {
|
|
79
|
+
const dueDateMs = item.dueDate?.date ?? null;
|
|
80
|
+
const dueDateObj = dueDateMs ? new Date(dueDateMs) : null;
|
|
81
|
+
const daysUntilDue = item.dueDateTs?.localDaysDifference
|
|
82
|
+
?? (dueDateObj !== null
|
|
83
|
+
? Math.ceil((dueDateObj.getTime() - today.getTime()) / 86400000)
|
|
84
|
+
: null);
|
|
85
|
+
return {
|
|
86
|
+
title: item.itemName || '(unknown)',
|
|
87
|
+
type: item.itemType || 'Training',
|
|
88
|
+
status: item.itemDisplayStatus || 'Pending',
|
|
89
|
+
dueDate: dueDateObj ? dueDateObj.toISOString().slice(0, 10) : '',
|
|
90
|
+
daysUntilDue: daysUntilDue ?? 9999,
|
|
91
|
+
isOverdue: item.overdue ?? (daysUntilDue !== null && daysUntilDue < 0),
|
|
92
|
+
};
|
|
93
|
+
})
|
|
94
|
+
.sort((a, b) => a.daysUntilDue - b.daysUntilDue);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
site: saba
|
|
2
|
+
name: pending-training
|
|
3
|
+
version: "1.0"
|
|
4
|
+
description: "Pending mandatory learning plan items from Saba Cloud (daimler.sabacloud.com)"
|
|
5
|
+
access: read
|
|
6
|
+
|
|
7
|
+
domains:
|
|
8
|
+
- "daimler.sabacloud.com"
|
|
9
|
+
capabilities:
|
|
10
|
+
- navigate
|
|
11
|
+
- js_evaluate
|
|
12
|
+
|
|
13
|
+
columns:
|
|
14
|
+
- name: title
|
|
15
|
+
type: string
|
|
16
|
+
- name: type
|
|
17
|
+
type: string
|
|
18
|
+
- name: status
|
|
19
|
+
type: string
|
|
20
|
+
- name: dueDate
|
|
21
|
+
type: string
|
|
22
|
+
- name: daysUntilDue
|
|
23
|
+
type: number
|
|
24
|
+
- name: isOverdue
|
|
25
|
+
type: boolean
|
|
26
|
+
|
|
27
|
+
pipeline:
|
|
28
|
+
- step: navigate
|
|
29
|
+
url: "https://daimler.sabacloud.com/"
|
|
30
|
+
- step: wait
|
|
31
|
+
selector: "body"
|
|
32
|
+
timeout: 60000
|
|
33
|
+
- step: js_evaluate
|
|
34
|
+
file: saba-pending-training.eval.js
|