@zeyos/client 0.1.1 → 0.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/CHANGELOG.md +22 -0
- package/agents/README.md +16 -6
- package/agents/shared/zeyos-agent-operating-guide.md +112 -0
- package/agents/shared/zeyos-query-patterns.md +12 -1
- package/agents/zeyos/SKILL.md +95 -0
- package/agents/zeyos-account-intelligence/SKILL.md +1 -1
- package/agents/zeyos-account-intelligence/references/workflows.md +7 -0
- package/agents/zeyos-billing-insights/SKILL.md +6 -1
- package/agents/zeyos-billing-insights/references/workflows.md +32 -3
- package/agents/zeyos-campaign-and-outreach/SKILL.md +1 -1
- package/agents/zeyos-campaign-and-outreach/references/workflows.md +8 -0
- package/agents/zeyos-collaboration-and-activity/SKILL.md +1 -1
- package/agents/zeyos-collaboration-and-activity/references/workflows.md +9 -0
- package/agents/zeyos-collections-and-dunning/SKILL.md +1 -1
- package/agents/zeyos-commerce-and-inventory/SKILL.md +1 -1
- package/agents/zeyos-commerce-and-inventory/references/workflows.md +7 -0
- package/agents/zeyos-mail-operations/SKILL.md +1 -1
- package/agents/zeyos-notes-and-sops/SKILL.md +1 -1
- package/agents/zeyos-platform-and-schema/SKILL.md +1 -1
- package/agents/zeyos-platform-and-schema/references/workflows.md +8 -0
- package/agents/zeyos-work-management/SKILL.md +1 -1
- package/docs/03-cli/01-getting-started.md +7 -0
- package/docs/03-cli/02-commands.md +28 -0
- package/package.json +9 -3
- package/samples/missioncontrol/README.md +106 -0
- package/samples/missioncontrol/fetch-data.mjs +341 -0
- package/samples/missioncontrol/index.html +419 -0
- package/src/runtime/client.js +27 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Mission Control — ZeyOS</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0b0f17; --panel: #121925; --panel-2: #0f1521; --line: #1f2a3a; --line-soft: #18222f;
|
|
10
|
+
--txt: #e6edf6; --muted: #8794a8; --faint: #5b687d;
|
|
11
|
+
--amber: #f7bc60; --blue: #5b9dff; --green: #3ddc97; --red: #ff6b6b; --violet: #b08bff; --teal: #4fd1d9;
|
|
12
|
+
--r: 12px;
|
|
13
|
+
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
|
14
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
15
|
+
}
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
html, body { margin: 0; padding: 0; }
|
|
18
|
+
body {
|
|
19
|
+
background: radial-gradient(1200px 600px at 80% -10%, rgba(247,188,96,.06), transparent 60%),
|
|
20
|
+
radial-gradient(1000px 500px at 0% 0%, rgba(91,157,255,.05), transparent 55%), var(--bg);
|
|
21
|
+
color: var(--txt); font-family: var(--sans); font-size: 14px; line-height: 1.45;
|
|
22
|
+
-webkit-font-smoothing: antialiased; min-height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 24px 80px; }
|
|
25
|
+
|
|
26
|
+
header.top { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: 22px; }
|
|
27
|
+
.brand { display: flex; align-items: center; gap: 12px; }
|
|
28
|
+
.brand .logo { font-size: 26px; line-height: 1; filter: drop-shadow(0 0 10px rgba(247,188,96,.35)); }
|
|
29
|
+
.brand h1 { font-size: 21px; margin: 0; letter-spacing: .3px; font-weight: 700; }
|
|
30
|
+
.brand .sub { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
|
|
31
|
+
.meta { display: flex; gap: 18px; flex-wrap: wrap; text-align: right; }
|
|
32
|
+
.meta .m { font-size: 11px; color: var(--faint); text-transform: uppercase; letter-spacing: .6px; }
|
|
33
|
+
.meta .v { font-size: 13px; color: var(--txt); font-family: var(--mono); }
|
|
34
|
+
.meta .v.amber { color: var(--amber); }
|
|
35
|
+
|
|
36
|
+
.sec-h { display: flex; align-items: center; gap: 10px; margin: 26px 2px 12px; }
|
|
37
|
+
.sec-h h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 1.2px; color: var(--muted); margin: 0; font-weight: 600; }
|
|
38
|
+
.sec-h .rule { flex: 1; height: 1px; background: linear-gradient(90deg, var(--line), transparent); }
|
|
39
|
+
|
|
40
|
+
.kpis { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; }
|
|
41
|
+
@media (max-width: 1100px) { .kpis { grid-template-columns: repeat(3, 1fr); } }
|
|
42
|
+
@media (max-width: 620px) { .kpis { grid-template-columns: repeat(2, 1fr); } }
|
|
43
|
+
.card { background: linear-gradient(180deg, var(--panel), var(--panel-2)); border: 1px solid var(--line); border-radius: var(--r); padding: 15px 16px; position: relative; }
|
|
44
|
+
.kpi { overflow: hidden; }
|
|
45
|
+
.kpi .label { font-size: 11.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }
|
|
46
|
+
.kpi .num { font-family: var(--mono); font-size: 30px; font-weight: 650; margin-top: 6px; line-height: 1; }
|
|
47
|
+
.kpi .foot { font-size: 11.5px; color: var(--faint); margin-top: 8px; min-height: 15px; }
|
|
48
|
+
.kpi .accent { position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }
|
|
49
|
+
.num.blue { color: var(--blue); } .num.green { color: var(--green); } .num.amber { color: var(--amber); } .num.red { color: var(--red); } .num.violet { color: var(--violet); }
|
|
50
|
+
.pill { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 11px; font-family: var(--mono); }
|
|
51
|
+
.pill.up { background: rgba(255,107,107,.13); color: var(--red); } .pill.down { background: rgba(61,220,151,.13); color: var(--green); }
|
|
52
|
+
|
|
53
|
+
.grid-2 { display: grid; grid-template-columns: 2fr 1fr; gap: 14px; }
|
|
54
|
+
@media (max-width: 980px) { .grid-2 { grid-template-columns: 1fr; } }
|
|
55
|
+
.panel-h { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; gap: 12px; }
|
|
56
|
+
.panel-h .t { font-size: 14px; font-weight: 650; } .panel-h .s { font-size: 11.5px; color: var(--faint); }
|
|
57
|
+
.legend { display: flex; gap: 13px; font-size: 11.5px; color: var(--muted); flex-wrap: wrap; }
|
|
58
|
+
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 5px; vertical-align: middle; }
|
|
59
|
+
|
|
60
|
+
.cap-bar { display: flex; height: 16px; border-radius: 6px; overflow: hidden; border: 1px solid var(--line); margin: 6px 0 14px; }
|
|
61
|
+
.cap-bar span { display: block; height: 100%; }
|
|
62
|
+
.cap-legend { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 18px; }
|
|
63
|
+
.cap-legend .row { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; }
|
|
64
|
+
.cap-legend .row .k { color: var(--muted); } .cap-legend .row .k i { display:inline-block; width:9px;height:9px;border-radius:2px;margin-right:7px; }
|
|
65
|
+
.cap-legend .row .n { font-family: var(--mono); }
|
|
66
|
+
.insight { font-size: 12.5px; color: var(--muted); margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--line-soft); }
|
|
67
|
+
.insight b { color: var(--txt); } .insight .warn { color: var(--red); }
|
|
68
|
+
|
|
69
|
+
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 4px 2px 14px; }
|
|
70
|
+
.toolbar input, .toolbar select { background: var(--panel-2); border: 1px solid var(--line); color: var(--txt); border-radius: 8px; padding: 7px 10px; font-size: 13px; font-family: var(--sans); }
|
|
71
|
+
.toolbar input { min-width: 170px; } .toolbar input:focus, .toolbar select:focus { outline: none; border-color: var(--amber); }
|
|
72
|
+
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
73
|
+
.chip { background: var(--panel-2); border: 1px solid var(--line); color: var(--muted); border-radius: 999px; padding: 5px 12px; font-size: 12px; cursor: pointer; user-select: none; }
|
|
74
|
+
.chip:hover { border-color: var(--faint); color: var(--txt); } .chip.on { background: rgba(247,188,96,.14); border-color: var(--amber); color: var(--amber); }
|
|
75
|
+
.spacer { flex: 1; } .count-note { font-size: 12px; color: var(--faint); font-family: var(--mono); }
|
|
76
|
+
|
|
77
|
+
.emps { display: grid; grid-template-columns: repeat(auto-fill, minmax(262px, 1fr)); gap: 13px; }
|
|
78
|
+
.emp { cursor: pointer; transition: transform .12s ease, border-color .12s ease; overflow: hidden; }
|
|
79
|
+
.emp:hover { transform: translateY(-2px); border-color: var(--faint); }
|
|
80
|
+
.emp .head { display: flex; align-items: center; gap: 10px; }
|
|
81
|
+
.avatar { width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; font-family: var(--mono); font-size: 13px; font-weight: 600; color: #0b0f17; flex: 0 0 auto; }
|
|
82
|
+
.emp .nm { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
83
|
+
.emp .em { font-size: 11px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
84
|
+
.badge { margin-left: auto; font-size: 10.5px; padding: 2px 8px; border-radius: 999px; text-transform: uppercase; letter-spacing: .5px; font-weight: 600; flex: 0 0 auto; }
|
|
85
|
+
.b-overloaded { background: rgba(255,107,107,.15); color: var(--red); } .b-available { background: rgba(61,220,151,.15); color: var(--green); }
|
|
86
|
+
.b-balanced { background: rgba(91,157,255,.13); color: var(--blue); } .b-idle { background: rgba(135,148,168,.15); color: var(--muted); } .b-former { background: rgba(135,148,168,.08); color: var(--faint); }
|
|
87
|
+
.emp .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin: 13px 0 11px; }
|
|
88
|
+
.stat .s-n { font-family: var(--mono); font-size: 16px; font-weight: 600; } .stat .s-l { font-size: 9.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .3px; margin-top: 1px; }
|
|
89
|
+
.grp { display: flex; gap: 5px; flex-wrap: wrap; margin: 2px 0 11px; min-height: 18px; }
|
|
90
|
+
.grp .g { font-size: 10px; color: var(--muted); background: var(--panel-2); border: 1px solid var(--line-soft); border-radius: 5px; padding: 1px 6px; }
|
|
91
|
+
.emp .foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; border-top: 1px solid var(--line-soft); padding-top: 9px; }
|
|
92
|
+
.spark { height: 26px; }
|
|
93
|
+
.la { font-size: 10.5px; color: var(--faint); text-align: right; }
|
|
94
|
+
.la.stale { color: var(--red); }
|
|
95
|
+
.la .warn { font-weight: 700; }
|
|
96
|
+
.la-wrap { cursor: help; }
|
|
97
|
+
|
|
98
|
+
/* contribution graph */
|
|
99
|
+
.contrib { display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 11px); gap: 3px; }
|
|
100
|
+
.contrib i { width: 11px; height: 11px; border-radius: 2px; background: var(--line-soft); }
|
|
101
|
+
.c1 { background: rgba(61,220,151,.30) !important; } .c2 { background: rgba(61,220,151,.50) !important; }
|
|
102
|
+
.c3 { background: rgba(61,220,151,.72) !important; } .c4 { background: var(--green) !important; }
|
|
103
|
+
.contrib-months { display: flex; font-size: 10px; color: var(--faint); margin-bottom: 5px; font-family: var(--mono); }
|
|
104
|
+
|
|
105
|
+
/* Modal */
|
|
106
|
+
.overlay { position: fixed; inset: 0; background: rgba(4,7,12,.66); backdrop-filter: blur(3px); display: none; place-items: center; z-index: 50; padding: 20px; }
|
|
107
|
+
.overlay.on { display: grid; }
|
|
108
|
+
.modal { width: min(720px, 100%); max-height: 90vh; overflow: auto; background: linear-gradient(180deg, var(--panel), var(--panel-2)); border: 1px solid var(--line); border-radius: 16px; padding: 22px 24px; }
|
|
109
|
+
.modal .x { float: right; cursor: pointer; color: var(--faint); font-size: 20px; line-height: 1; padding: 2px 6px; } .modal .x:hover { color: var(--txt); }
|
|
110
|
+
.m-head { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
|
|
111
|
+
.m-head .nm { font-size: 18px; font-weight: 700; }
|
|
112
|
+
.m-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 18px 0; }
|
|
113
|
+
@media (max-width: 560px) { .m-kpis { grid-template-columns: repeat(2,1fr); } }
|
|
114
|
+
.m-kpi { background: var(--panel-2); border: 1px solid var(--line-soft); border-radius: 10px; padding: 11px 12px; }
|
|
115
|
+
.m-kpi .n { font-family: var(--mono); font-size: 20px; font-weight: 600; } .m-kpi .l { font-size: 10.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .4px; margin-top: 3px; }
|
|
116
|
+
.toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
|
|
117
|
+
.toggle button { background: var(--panel-2); border: 0; color: var(--muted); padding: 5px 13px; font-size: 12px; cursor: pointer; font-family: var(--sans); }
|
|
118
|
+
.toggle button.on { background: rgba(247,188,96,.16); color: var(--amber); }
|
|
119
|
+
.m-sec { margin-top: 22px; }
|
|
120
|
+
|
|
121
|
+
/* shared floating tooltip */
|
|
122
|
+
#tip { position: fixed; z-index: 100; display: none; max-width: 360px; background: #0a0e16; border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; box-shadow: 0 8px 30px rgba(0,0,0,.5); pointer-events: none; font-size: 12px; }
|
|
123
|
+
#tip table { border-collapse: collapse; width: 100%; } #tip td { padding: 2px 6px 2px 0; white-space: nowrap; }
|
|
124
|
+
#tip .th { color: var(--faint); font-size: 10.5px; text-transform: uppercase; letter-spacing: .4px; margin-bottom: 5px; }
|
|
125
|
+
#tip .dt { color: var(--faint); font-family: var(--mono); } #tip .ty { color: var(--amber); } #tip .cu { color: var(--txt); } #tip .tk { color: var(--blue); font-family: var(--mono); } #tip .mn { color: var(--green); font-family: var(--mono); text-align: right; }
|
|
126
|
+
|
|
127
|
+
.empty { color: var(--faint); text-align: center; padding: 40px; font-size: 13px; }
|
|
128
|
+
.setup { max-width: 560px; margin: 80px auto; }
|
|
129
|
+
.setup code, .setup pre { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; font-family: var(--mono); font-size: 12.5px; color: var(--amber); }
|
|
130
|
+
.setup pre { padding: 14px 16px; overflow:auto; }
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<div class="wrap" id="app"></div>
|
|
135
|
+
<div class="overlay" id="overlay"><div class="modal" id="modal"></div></div>
|
|
136
|
+
<div id="tip"></div>
|
|
137
|
+
|
|
138
|
+
<script src="data.js"></script>
|
|
139
|
+
<script>
|
|
140
|
+
(function () {
|
|
141
|
+
"use strict";
|
|
142
|
+
const D = window.MISSION_DATA;
|
|
143
|
+
const app = document.getElementById("app");
|
|
144
|
+
if (!D) {
|
|
145
|
+
app.innerHTML = `<div class="setup"><h1>⚡️ Mission Control</h1>
|
|
146
|
+
<p style="color:var(--muted)">No data found. Generate it with the ZeyOS client:</p>
|
|
147
|
+
<pre>zeyos login # if not already logged in
|
|
148
|
+
node fetch-data.mjs # pulls live data into data.js</pre>
|
|
149
|
+
<p style="color:var(--muted)">Then reload. The fetcher is read-only and reuses your CLI credentials.</p></div>`;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── helpers ──
|
|
154
|
+
const $ = (tag, cls, html) => { const e = document.createElement(tag); if (cls) e.className = cls; if (html != null) e.innerHTML = html; return e; };
|
|
155
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
156
|
+
const fmtDate = (ts) => ts ? new Date(ts * 1000).toISOString().slice(0, 10) : "—";
|
|
157
|
+
const DAY = 86400;
|
|
158
|
+
const host = (() => { try { return new URL(D.meta.instance).host; } catch { return D.meta.instance; } })();
|
|
159
|
+
const CAP = { overloaded: { label: "Overloaded", color: "var(--red)" }, balanced: { label: "Balanced", color: "var(--blue)" },
|
|
160
|
+
available: { label: "Available", color: "var(--green)" }, idle: { label: "Idle", color: "var(--muted)" }, former: { label: "Former", color: "var(--faint)" } };
|
|
161
|
+
const avatarColor = (s) => { let h = 0; for (const c of s) h = (h * 31 + c.charCodeAt(0)) % 360; return `hsl(${h} 55% 62%)`; };
|
|
162
|
+
const initials = (s) => s.replace(/[^a-zA-Z]/g, "").slice(0, 2).toUpperCase() || "?";
|
|
163
|
+
|
|
164
|
+
// type → colour
|
|
165
|
+
const PALETTE = ["var(--blue)", "var(--green)", "var(--amber)", "var(--violet)", "var(--red)", "var(--teal)", "var(--faint)"];
|
|
166
|
+
const TYPES = D.meta.typeOrder || [];
|
|
167
|
+
const typeColor = {}; TYPES.forEach((t, i) => typeColor[t] = PALETTE[i % PALETTE.length]);
|
|
168
|
+
|
|
169
|
+
// ── shared floating tooltip ──
|
|
170
|
+
const tip = document.getElementById("tip");
|
|
171
|
+
function showTip(html, ev) {
|
|
172
|
+
tip.innerHTML = html; tip.style.display = "block";
|
|
173
|
+
const r = tip.getBoundingClientRect();
|
|
174
|
+
let x = ev.clientX + 14, y = ev.clientY + 14;
|
|
175
|
+
if (x + r.width > innerWidth - 8) x = ev.clientX - r.width - 14;
|
|
176
|
+
if (y + r.height > innerHeight - 8) y = ev.clientY - r.height - 14;
|
|
177
|
+
tip.style.left = Math.max(8, x) + "px"; tip.style.top = Math.max(8, y) + "px";
|
|
178
|
+
}
|
|
179
|
+
const hideTip = () => { tip.style.display = "none"; };
|
|
180
|
+
function attachTip(el, htmlFn) {
|
|
181
|
+
el.addEventListener("mouseenter", (ev) => showTip(htmlFn(), ev));
|
|
182
|
+
el.addEventListener("mousemove", (ev) => { if (tip.style.display === "block") showTip(tip.innerHTML, ev); });
|
|
183
|
+
el.addEventListener("mouseleave", hideTip);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── charts ──
|
|
187
|
+
function groupedBars(series, opts = {}) { // team trend: opened vs closed
|
|
188
|
+
const W = opts.w || 720, H = opts.h || 190, padL = 26, padB = 22, padT = 8, n = series.length;
|
|
189
|
+
const max = Math.max(1, ...series.map(d => Math.max(d.opened, d.closed)));
|
|
190
|
+
const innerW = W - padL - 6, innerH = H - padB - padT, gW = innerW / n, bW = Math.min(13, gW / 2.6);
|
|
191
|
+
const y = v => padT + innerH - (v / max) * innerH;
|
|
192
|
+
let g = "", bars = "", lab = "";
|
|
193
|
+
for (let i = 0; i <= 3; i++) { const gv = Math.round(max * i / 3), gy = y(gv);
|
|
194
|
+
g += `<line x1="${padL}" y1="${gy}" x2="${W-6}" y2="${gy}" stroke="var(--line-soft)"/><text x="${padL-5}" y="${gy+3}" text-anchor="end" font-size="9" fill="var(--faint)" font-family="var(--mono)">${gv}</text>`; }
|
|
195
|
+
series.forEach((d, i) => { const cx = padL + gW * i + gW / 2;
|
|
196
|
+
bars += `<rect x="${cx-bW-1}" y="${y(d.opened)}" width="${bW}" height="${padT+innerH-y(d.opened)}" rx="2" fill="var(--blue)"><title>${d.label}: ${d.opened} opened</title></rect>`;
|
|
197
|
+
bars += `<rect x="${cx+1}" y="${y(d.closed)}" width="${bW}" height="${padT+innerH-y(d.closed)}" rx="2" fill="var(--green)"><title>${d.label}: ${d.closed} closed</title></rect>`;
|
|
198
|
+
if (n <= 14 || i % 2 === 0) lab += `<text x="${cx}" y="${H-7}" text-anchor="middle" font-size="9" fill="var(--faint)" font-family="var(--mono)">${d.label}</text>`; });
|
|
199
|
+
return `<svg viewBox="0 0 ${W} ${H}" width="100%" preserveAspectRatio="xMidYMid meet">${g}${bars}${lab}</svg>`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function stackedBars(series, opts = {}) { // per-employee: hours booked, stacked by type
|
|
203
|
+
const W = opts.w || 640, H = opts.h || 200, padL = 30, padB = 22, padT = 8, n = series.length;
|
|
204
|
+
const totals = series.map(d => TYPES.reduce((a, t) => a + (d.seg[t] || 0), 0) / 60);
|
|
205
|
+
const max = Math.max(1, ...totals);
|
|
206
|
+
const innerW = W - padL - 6, innerH = H - padB - padT, gW = innerW / n, bW = Math.min(26, gW * 0.62);
|
|
207
|
+
const y = v => padT + innerH - (v / max) * innerH;
|
|
208
|
+
let grid = "", bars = "", lab = "";
|
|
209
|
+
for (let i = 0; i <= 3; i++) { const gv = Math.round(max * i / 3), gy = y(gv);
|
|
210
|
+
grid += `<line x1="${padL}" y1="${gy}" x2="${W-6}" y2="${gy}" stroke="var(--line-soft)"/><text x="${padL-5}" y="${gy+3}" text-anchor="end" font-size="9" fill="var(--faint)" font-family="var(--mono)">${gv}h</text>`; }
|
|
211
|
+
series.forEach((d, i) => { const cx = padL + gW * i + gW / 2; let acc = 0;
|
|
212
|
+
TYPES.forEach((t) => { const hrs = (d.seg[t] || 0) / 60; if (hrs <= 0) return; const h = (hrs / max) * innerH;
|
|
213
|
+
const yTop = padT + innerH - acc - h; acc += h;
|
|
214
|
+
bars += `<rect x="${cx-bW/2}" y="${yTop}" width="${bW}" height="${h}" fill="${typeColor[t]}"><title>${d.label} · ${t}: ${hrs.toFixed(1)}h</title></rect>`; });
|
|
215
|
+
if (n <= 14 || i % 2 === 0) lab += `<text x="${cx}" y="${H-7}" text-anchor="middle" font-size="9" fill="var(--faint)" font-family="var(--mono)">${d.label}</text>`; });
|
|
216
|
+
return `<svg viewBox="0 0 ${W} ${H}" width="100%" preserveAspectRatio="xMidYMid meet">${grid}${bars}${lab}</svg>`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function spark(values, color) {
|
|
220
|
+
const W = 92, H = 26, max = Math.max(1, ...values), n = values.length, bw = W / n;
|
|
221
|
+
return `<svg class="spark" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">` +
|
|
222
|
+
values.map((v, i) => { const h = (v / max) * (H - 3); return `<rect x="${(i*bw).toFixed(1)}" y="${(H-h).toFixed(1)}" width="${(bw-1.4).toFixed(1)}" height="${h.toFixed(1)}" rx="1" fill="${color}" opacity="${v?1:.25}"/>`; }).join("") + `</svg>`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// GitHub-style contribution heatmap from sparse [dayIndex,count] pairs
|
|
226
|
+
function contribGraph(pairs) {
|
|
227
|
+
const cols = D.meta.contribCols, start = D.meta.contribStart, byDay = new Map(pairs);
|
|
228
|
+
const lvl = (c) => c >= 8 ? "c4" : c >= 5 ? "c3" : c >= 3 ? "c2" : c >= 1 ? "c1" : "";
|
|
229
|
+
let cells = "";
|
|
230
|
+
const monthCols = {};
|
|
231
|
+
for (let col = 0; col < cols; col++) {
|
|
232
|
+
for (let row = 0; row < 7; row++) {
|
|
233
|
+
const di = col * 7 + row, ts = start + di * DAY, c = byDay.get(di) || 0;
|
|
234
|
+
cells += `<i class="${lvl(c)}" data-d="${ts}" data-c="${c}"></i>`;
|
|
235
|
+
}
|
|
236
|
+
const md = new Date((start + col * 7 * DAY) * 1000);
|
|
237
|
+
const mk = md.getMonth(); if (!(mk in monthCols)) monthCols[mk] = col;
|
|
238
|
+
}
|
|
239
|
+
const MON = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
240
|
+
let months = ""; let last = -1;
|
|
241
|
+
for (let col = 0; col < cols; col++) { const m = new Date((start + col*7*DAY)*1000).getMonth();
|
|
242
|
+
if (m !== last && monthCols[m] === col) { months += `<span style="width:${14*1}px">${MON[m]}</span>`; last = m; } }
|
|
243
|
+
return { html: `<div class="contrib">${cells}</div>` };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── header ──
|
|
247
|
+
const hd = $("header", "top");
|
|
248
|
+
hd.innerHTML = `<div class="brand"><div class="logo">⚡️</div><div><h1>Mission Control</h1>
|
|
249
|
+
<div class="sub">Team performance & capacity · IT consultancy delivery</div></div></div>
|
|
250
|
+
<div class="meta">
|
|
251
|
+
<div><div class="m">Instance</div><div class="v">${host}</div></div>
|
|
252
|
+
<div><div class="m">Data as of</div><div class="v amber">${fmtDate(D.meta.asOf)}</div></div>
|
|
253
|
+
<div><div class="m">Window</div><div class="v">${D.meta.windowDays} days</div></div>
|
|
254
|
+
<div><div class="m">Booked</div><div class="v">${Math.round(D.time.bookedHours)}h</div></div>
|
|
255
|
+
<div><div class="m">Active roster</div><div class="v">${D.team.activeUsers}</div></div>
|
|
256
|
+
</div>`;
|
|
257
|
+
app.appendChild(hd);
|
|
258
|
+
|
|
259
|
+
// ── velocity ──
|
|
260
|
+
addSection("Velocity — last " + D.meta.windowDays + " days");
|
|
261
|
+
const v = D.velocity, c = v.cycle, netUp = v.net > 0;
|
|
262
|
+
const kpi = (label, num, cls, foot, accent) => `<div class="card kpi"><div class="accent" style="background:${accent}"></div>
|
|
263
|
+
<div class="label">${label}</div><div class="num ${cls}">${num}</div><div class="foot">${foot||""}</div></div>`;
|
|
264
|
+
const row = $("div", "kpis");
|
|
265
|
+
row.innerHTML =
|
|
266
|
+
kpi("Tickets opened", v.openedInWindow, "blue", `~${v.weeklyOpenedAvg}/week`, "var(--blue)") +
|
|
267
|
+
kpi("Tickets closed", v.closedInWindow, "green", `~${v.weeklyClosedAvg}/week`, "var(--green)") +
|
|
268
|
+
kpi("Net flow", (v.net>0?"+":"")+v.net, netUp?"red":"green", `<span class="pill ${netUp?"up":"down"}">${netUp?"▲ backlog +"+v.net:"▼ burned "+(-v.net)}</span>`, netUp?"var(--red)":"var(--green)") +
|
|
269
|
+
kpi("Avg cycle time", c.avgDays+"<span style='font-size:15px;color:var(--faint)'>d</span>", "amber", `median ${c.medianDays}d · p90 ${c.p90Days}d`, "var(--amber)") +
|
|
270
|
+
kpi("Open backlog", v.backlogOpen, "txt", `${v.backlogOverdue} overdue`, "var(--violet)") +
|
|
271
|
+
kpi("Hours booked", Math.round(D.time.bookedHours).toLocaleString(), "teal", `${D.time.entries.toLocaleString()} time entries`, "var(--teal)");
|
|
272
|
+
app.appendChild(row);
|
|
273
|
+
|
|
274
|
+
// ── trend + capacity ──
|
|
275
|
+
const g2 = $("div", "grid-2");
|
|
276
|
+
const trend = $("div", "card");
|
|
277
|
+
trend.innerHTML = `<div class="panel-h"><div><div class="t">Throughput trend</div><div class="s">opened vs closed · ${D.meta.trendWeeks} weeks</div></div>
|
|
278
|
+
<div class="legend"><span><i style="background:var(--blue)"></i>Opened</span><span><i style="background:var(--green)"></i>Closed</span></div></div>${groupedBars(v.trend)}`;
|
|
279
|
+
g2.appendChild(trend);
|
|
280
|
+
|
|
281
|
+
const t = D.team, segs = [["overloaded",t.overloaded],["balanced",t.balanced],["available",t.available],["idle",t.idle],["former",t.former]];
|
|
282
|
+
const total = segs.reduce((a,[,n])=>a+n,0)||1, spare = t.available + t.idle;
|
|
283
|
+
const cap = $("div", "card");
|
|
284
|
+
cap.innerHTML = `<div class="panel-h"><div class="t">Team capacity</div><div class="s">${t.engaged} engaged</div></div>
|
|
285
|
+
<div class="cap-bar">${segs.map(([k,n])=>`<span style="width:${(n/total*100).toFixed(1)}%;background:${CAP[k].color}" title="${CAP[k].label}: ${n}"></span>`).join("")}</div>
|
|
286
|
+
<div class="cap-legend">${segs.map(([k,n])=>`<div class="row"><span class="k"><i style="background:${CAP[k].color}"></i>${CAP[k].label}</span><span class="n">${n}</span></div>`).join("")}</div>
|
|
287
|
+
<div class="insight"><b>${spare}</b> active engineers have spare capacity (<b>${t.available}</b> light, <b>${t.idle}</b> no load) vs <b>${t.overloaded}</b> overloaded. <span class="warn">${D.time.staleEngineers}</span> have logged no time in ${D.meta.staleDays}+ days.</div>`;
|
|
288
|
+
g2.appendChild(cap);
|
|
289
|
+
app.appendChild(g2);
|
|
290
|
+
|
|
291
|
+
// ── employees ──
|
|
292
|
+
addSection("Employee activity");
|
|
293
|
+
const state = { sort: "activity", filter: "engaged", q: "", group: "" };
|
|
294
|
+
const SORTS = {
|
|
295
|
+
activity: (a,b)=>score(b)-score(a), throughput: (a,b)=>b.closedInWindow-a.closedInWindow,
|
|
296
|
+
hours: (a,b)=>b.bookedHours-a.bookedHours, workload: (a,b)=>b.workload-a.workload,
|
|
297
|
+
cycle: (a,b)=>(b.avgCycleDays||0)-(a.avgCycleDays||0), overdue: (a,b)=>b.overdueTickets-a.overdueTickets,
|
|
298
|
+
stale: (a,b)=>(a.lastActivity||0)-(b.lastActivity||0), available: (a,b)=>capRank(a)-capRank(b)||a.workload-b.workload,
|
|
299
|
+
};
|
|
300
|
+
const score = (e) => e.workload + e.throughput + e.openedInWindow + e.entriesInWindow;
|
|
301
|
+
const capRank = (e) => ({available:0,idle:1,balanced:2,overloaded:3,former:4})[e.capacity];
|
|
302
|
+
function matches(e) {
|
|
303
|
+
if (state.q && !(e.name+" "+e.email).toLowerCase().includes(state.q.toLowerCase())) return false;
|
|
304
|
+
if (state.group && !(e.groups||[]).includes(state.group)) return false;
|
|
305
|
+
if (state.filter === "all") return true;
|
|
306
|
+
if (state.filter === "engaged") return e.capacity !== "former";
|
|
307
|
+
if (state.filter === "spare") return e.capacity === "available" || e.capacity === "idle";
|
|
308
|
+
if (state.filter === "stale") return e.active && e.stale;
|
|
309
|
+
return e.capacity === state.filter;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const bar = $("div", "toolbar");
|
|
313
|
+
bar.innerHTML = `<input id="q" type="search" placeholder="Search engineer…" autocomplete="off">
|
|
314
|
+
<select id="group"><option value="">All teams / locations</option>${D.groups.map(g=>`<option value="${esc(g.name)}">${esc(g.name)} (${g.count})</option>`).join("")}</select>
|
|
315
|
+
<div class="chips" id="chips">${[["engaged","Engaged"],["spare","Spare capacity"],["stale","Inactive >"+D.meta.staleDays+"d"],["overloaded","Overloaded"],["all","All"]].map(([k,l])=>`<span class="chip ${state.filter===k?"on":""}" data-f="${k}">${l}</span>`).join("")}</div>
|
|
316
|
+
<div class="spacer"></div>
|
|
317
|
+
<select id="sort">${[["activity","Most active"],["hours","Most hours"],["throughput","Most closed"],["workload","Highest workload"],["cycle","Slowest cycle"],["overdue","Most overdue"],["stale","Least recent"],["available","Available first"]].map(([k,l])=>`<option value="${k}" ${state.sort===k?"selected":""}>Sort: ${l}</option>`).join("")}</select>`;
|
|
318
|
+
app.appendChild(bar);
|
|
319
|
+
const note = $("div", "count-note"); note.style.margin = "0 2px 12px"; app.appendChild(note);
|
|
320
|
+
const grid = $("div", "emps"); app.appendChild(grid);
|
|
321
|
+
|
|
322
|
+
function entriesTip(e) {
|
|
323
|
+
if (!e.recentEntries.length) return `<div class="th">${esc(e.name)} — no time entries</div>`;
|
|
324
|
+
const rows = e.recentEntries.map(r => `<tr><td class="dt">${fmtDate(r.date)}</td><td class="ty">${esc(r.type||"")}</td><td class="cu">${esc(r.customer||"—")}</td><td class="tk">${esc(r.ticket||"")}</td><td class="mn">${(r.mins/60).toFixed(2)}h</td></tr>`).join("");
|
|
325
|
+
return `<div class="th">Last ${e.recentEntries.length} time entries — ${esc(e.name)}</div><table>${rows}</table>`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function empCard(e) {
|
|
329
|
+
const card = $("div", "card emp"); card.onclick = () => openModal(e);
|
|
330
|
+
const weeklyHours = e.weeklyByType.map(w => TYPES.reduce((a,t)=>a+(w.seg[t]||0),0)/60);
|
|
331
|
+
const laTxt = e.lastActivity ? fmtDate(e.lastActivity) : "no entries";
|
|
332
|
+
const laClass = (e.stale || !e.lastActivity) ? "la stale" : "la";
|
|
333
|
+
const warn = e.stale ? `<span class="warn">⚠ </span>` : "";
|
|
334
|
+
card.innerHTML = `<div class="head"><div class="avatar" style="background:${avatarColor(e.name)}">${initials(e.name)}</div>
|
|
335
|
+
<div style="min-width:0"><div class="nm">${esc(e.name)}</div><div class="em">${esc(e.email||"—")}</div></div>
|
|
336
|
+
<div class="badge b-${e.capacity}">${CAP[e.capacity].label}</div></div>
|
|
337
|
+
<div class="grp">${(e.groups||[]).slice(0,3).map(g=>`<span class="g">${esc(g)}</span>`).join("")}</div>
|
|
338
|
+
<div class="stats">
|
|
339
|
+
<div class="stat"><div class="s-n" style="color:var(--blue)">${e.openTickets}</div><div class="s-l">Open tix</div></div>
|
|
340
|
+
<div class="stat"><div class="s-n" style="color:var(--violet)">${e.openTasks}</div><div class="s-l">Tasks</div></div>
|
|
341
|
+
<div class="stat"><div class="s-n" style="color:var(--green)">${e.closedInWindow}</div><div class="s-l">Closed</div></div>
|
|
342
|
+
<div class="stat"><div class="s-n" style="color:var(--teal)">${Math.round(e.bookedHours)}</div><div class="s-l">Hours</div></div>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="foot"><div>${spark(weeklyHours, "var(--teal)")}<div class="la" style="color:var(--faint);text-align:left">cycle ${e.avgCycleDays||"—"}d${e.overdueTickets?` · <span style="color:var(--red)">${e.overdueTickets} overdue</span>`:""}</div></div>
|
|
345
|
+
<div class="${laClass} la-wrap">${warn}last activity<br>${laTxt}</div></div>`;
|
|
346
|
+
attachTip(card.querySelector(".la-wrap"), () => entriesTip(e));
|
|
347
|
+
return card;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function paint() {
|
|
351
|
+
const list = D.employees.filter(matches).sort(SORTS[state.sort]);
|
|
352
|
+
grid.innerHTML = "";
|
|
353
|
+
if (!list.length) grid.appendChild($("div", "empty", "No engineers match this filter."));
|
|
354
|
+
else list.forEach(e => grid.appendChild(empCard(e)));
|
|
355
|
+
note.textContent = `${list.length} of ${D.employees.length} people` + (state.group ? ` · ${state.group}` : "");
|
|
356
|
+
}
|
|
357
|
+
bar.querySelector("#q").oninput = (e) => { state.q = e.target.value; paint(); };
|
|
358
|
+
bar.querySelector("#sort").onchange = (e) => { state.sort = e.target.value; paint(); };
|
|
359
|
+
bar.querySelector("#group").onchange = (e) => { state.group = e.target.value; paint(); };
|
|
360
|
+
bar.querySelectorAll(".chip").forEach(ch => ch.onclick = () => { state.filter = ch.dataset.f; bar.querySelectorAll(".chip").forEach(x=>x.classList.toggle("on",x===ch)); paint(); });
|
|
361
|
+
paint();
|
|
362
|
+
|
|
363
|
+
// ── modal: per-employee digest ──
|
|
364
|
+
const overlay = document.getElementById("overlay"), modal = document.getElementById("modal");
|
|
365
|
+
overlay.onclick = (e) => { if (e.target === overlay) closeModal(); };
|
|
366
|
+
document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeModal(); });
|
|
367
|
+
function closeModal() { overlay.classList.remove("on"); }
|
|
368
|
+
|
|
369
|
+
function rollup(weekly, mode) {
|
|
370
|
+
if (mode === "week") return weekly;
|
|
371
|
+
const out = [], size = 4;
|
|
372
|
+
for (let i = 0; i < weekly.length; i += size) { const chunk = weekly.slice(i, i+size); const seg = {};
|
|
373
|
+
for (const w of chunk) for (const k in w.seg) seg[k] = (seg[k]||0) + w.seg[k];
|
|
374
|
+
out.push({ label: chunk[chunk.length-1].label, seg }); }
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function openModal(e) {
|
|
379
|
+
let mode = "week";
|
|
380
|
+
const draw = () => {
|
|
381
|
+
const legend = TYPES.map(t => `<span><i style="background:${typeColor[t]}"></i>${esc(t)}</span>`).join("");
|
|
382
|
+
const cg = contribGraph(e.contrib);
|
|
383
|
+
const laWarn = e.stale ? `<span style="color:var(--red)"> ⚠ ${Math.round((D.meta.asOf-e.lastActivity)/DAY)}d ago</span>` : "";
|
|
384
|
+
modal.innerHTML = `<span class="x" id="x">✕</span>
|
|
385
|
+
<div class="m-head"><div class="avatar" style="background:${avatarColor(e.name)};width:42px;height:42px;border-radius:11px;font-size:16px">${initials(e.name)}</div>
|
|
386
|
+
<div><div class="nm">${esc(e.name)}</div><div class="em" style="color:var(--muted)">${esc(e.email||"—")} · <span class="badge b-${e.capacity}" style="margin:0">${CAP[e.capacity].label}</span> ${(e.groups||[]).slice(0,4).map(g=>`<span class="g" style="margin-left:4px">${esc(g)}</span>`).join("")}</div></div></div>
|
|
387
|
+
<div class="m-kpis">
|
|
388
|
+
<div class="m-kpi"><div class="n" style="color:var(--teal)">${e.bookedHours}<span style="font-size:13px;color:var(--faint)">h</span></div><div class="l">Booked (${D.meta.windowDays}d)</div></div>
|
|
389
|
+
<div class="m-kpi"><div class="n" style="color:var(--green)">${e.closedInWindow}</div><div class="l">Tickets closed</div></div>
|
|
390
|
+
<div class="m-kpi"><div class="n" style="color:var(--amber)">${e.avgCycleDays||"—"}<span style="font-size:13px;color:var(--faint)">d</span></div><div class="l">Avg cycle</div></div>
|
|
391
|
+
<div class="m-kpi"><div class="n" style="color:var(--blue)">${e.openTickets}</div><div class="l">Open tickets</div></div>
|
|
392
|
+
<div class="m-kpi"><div class="n" style="color:var(--violet)">${e.openTasks}</div><div class="l">Open tasks</div></div>
|
|
393
|
+
<div class="m-kpi"><div class="n" style="color:${e.overdueTickets?"var(--red)":"var(--txt)"}">${e.overdueTickets}</div><div class="l">Overdue</div></div>
|
|
394
|
+
<div class="m-kpi"><div class="n">${e.entriesInWindow}</div><div class="l">Time entries</div></div>
|
|
395
|
+
<div class="m-kpi"><div class="n" style="font-size:14px;padding-top:4px">${fmtDate(e.lastActivity)}${laWarn}</div><div class="l">Last activity</div></div>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="m-sec"><div class="panel-h"><div><div class="t">Booked hours by type</div><div class="s">per ${mode}</div></div>
|
|
398
|
+
<div class="toggle"><button data-m="week" class="${mode==="week"?"on":""}">Weekly</button><button data-m="month" class="${mode==="month"?"on":""}">Monthly</button></div></div>
|
|
399
|
+
${stackedBars(rollup(e.weeklyByType, mode))}
|
|
400
|
+
<div class="legend" style="margin-top:8px">${legend}</div></div>
|
|
401
|
+
<div class="m-sec"><div class="panel-h"><div><div class="t">Activity contribution</div><div class="s">time entries / day · ${D.meta.contribWeeks} weeks</div></div>
|
|
402
|
+
<div class="legend"><span>less</span><i style="width:11px;height:11px;border-radius:2px;background:var(--line-soft)"></i><i style="width:11px;height:11px;border-radius:2px;background:rgba(61,220,151,.5)"></i><i style="width:11px;height:11px;border-radius:2px;background:var(--green)"></i><span>more</span></div></div>
|
|
403
|
+
<div style="overflow-x:auto;padding-bottom:4px">${cg.html}</div></div>`;
|
|
404
|
+
modal.querySelector("#x").onclick = closeModal;
|
|
405
|
+
modal.querySelectorAll(".toggle button").forEach(b => b.onclick = () => { mode = b.dataset.m; draw(); });
|
|
406
|
+
modal.querySelectorAll(".contrib i").forEach(cell => attachTip(cell, () => {
|
|
407
|
+
const ts = +cell.dataset.d, ct = +cell.dataset.c;
|
|
408
|
+
return `<div class="th">${fmtDate(ts)}</div><div>${ct} time ${ct===1?"entry":"entries"}</div>`;
|
|
409
|
+
}));
|
|
410
|
+
};
|
|
411
|
+
draw();
|
|
412
|
+
overlay.classList.add("on");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function addSection(title) { const h = $("div", "sec-h"); h.innerHTML = `<h2>${title}</h2><div class="rule"></div>`; app.appendChild(h); }
|
|
416
|
+
})();
|
|
417
|
+
</script>
|
|
418
|
+
</body>
|
|
419
|
+
</html>
|
package/src/runtime/client.js
CHANGED
|
@@ -788,6 +788,33 @@ export function createZeyosClient(rawConfig = {}) {
|
|
|
788
788
|
}
|
|
789
789
|
|
|
790
790
|
async function executeOperation({ serviceKey, operation, prepared, requestOptions = {} }) {
|
|
791
|
+
// Dry run: resolve the route + payload exactly as they would be sent, but
|
|
792
|
+
// return that descriptor instead of performing any network request or token
|
|
793
|
+
// work. Powers `zeyos … --query` and is handy for debugging/testing.
|
|
794
|
+
if (requestOptions.dryRun || prepared.dryRun) {
|
|
795
|
+
const baseUrl = resolveBaseUrl({
|
|
796
|
+
services: SERVICES,
|
|
797
|
+
serviceKey,
|
|
798
|
+
config,
|
|
799
|
+
explicitBaseUrl: prepared.baseUrl ?? requestOptions.baseUrl
|
|
800
|
+
});
|
|
801
|
+
const url = buildUrl(baseUrl, operation.path, prepared.pathParams, prepared.query);
|
|
802
|
+
const bodyType = chooseBodyType(serviceKey, operation, prepared, requestOptions?.bodyType);
|
|
803
|
+
return {
|
|
804
|
+
dryRun: true,
|
|
805
|
+
service: serviceKey,
|
|
806
|
+
operationId: operation.operationId,
|
|
807
|
+
method: operation.method,
|
|
808
|
+
url,
|
|
809
|
+
path: operation.path,
|
|
810
|
+
pathParams: prepared.pathParams,
|
|
811
|
+
query: prepared.query,
|
|
812
|
+
headers: prepared.headers,
|
|
813
|
+
body: prepared.body,
|
|
814
|
+
bodyType
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
791
818
|
const requestAuth = normalizeRequestAuth(prepared.auth ?? requestOptions.auth);
|
|
792
819
|
const mode = normalizeAuthMode(requestAuth.mode, defaultMode);
|
|
793
820
|
const schemes = securitySchemesFromOperation(operation);
|