@crievo/omni 0.1.7 → 0.1.9
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/omnivideo.config.example.json +2 -0
- package/package.json +1 -1
- package/reference/cloud.md +15 -1
- package/reference/learnings.md +7 -0
- package/reference/scenes-contract.md +3 -2
- package/reference/studio.md +3 -2
- package/studio/api/app.ts +4 -0
- package/studio/api/routes/cost.ts +13 -0
- package/studio/api/routes/me.ts +8 -0
- package/studio/api/services/agent-persona.ts +1 -0
- package/studio/api/services/agent-tools.ts +2 -2
- package/studio/api/services/cost.ts +22 -0
- package/studio/api/services/me.ts +67 -0
- package/studio/api/services/sessions.ts +8 -2
- package/studio/api/services/sound.ts +17 -6
- package/studio/runner/runner.ts +57 -10
- package/studio/shared/omni-screens.ts +1 -1
- package/studio/shared/schemas/cost.ts +10 -0
- package/studio/shared/types/api.ts +77 -1
- package/studio/studio.ts +27 -2
- package/studio/web/components/AppShell.tsx +11 -3
- package/studio/web/components/FirstConnection.tsx +222 -0
- package/studio/web/components/OfflineNotice.tsx +59 -0
- package/studio/web/components/SetupSteps.tsx +63 -0
- package/studio/web/components/Sidebar.tsx +10 -25
- package/studio/web/components/StudioOffline.tsx +48 -50
- package/studio/web/components/UserCard.tsx +79 -0
- package/studio/web/components/icons.tsx +2 -0
- package/studio/web/hooks/useComputers.ts +55 -0
- package/studio/web/hooks/useCost.ts +11 -0
- package/studio/web/hooks/useMe.ts +32 -0
- package/studio/web/hooks/useSetupProgress.ts +54 -0
- package/studio/web/lib/me.ts +17 -0
- package/studio/web/lib/query-client.ts +6 -0
- package/studio/web/lib/settings-categories.ts +2 -2
- package/studio/web/lib/setup-retry.ts +29 -0
- package/studio/web/lib/sound-timeline.ts +33 -0
- package/studio/web/lib/studio-offline.ts +105 -59
- package/studio/web/pages/ConnectPreviewPage/index.tsx +215 -0
- package/studio/web/pages/SettingsPage/components/AgentCategory.tsx +18 -9
- package/studio/web/pages/SettingsPage/components/AgentCostPanel.tsx +253 -0
- package/studio/web/pages/SettingsPage/components/ComputersPanel.tsx +145 -0
- package/studio/web/pages/SettingsPage/components/PublishingCategory/OmnichatSettingsPanel.tsx +2 -2
- package/studio/web/pages/SettingsPage/components/PublishingCategory/SchedulePanel.tsx +3 -3
- package/studio/web/pages/SettingsPage/components/UsageCategory.tsx +7 -2
- package/studio/web/pages/SettingsPage/index.tsx +15 -1
- package/studio/web/pages/VideoPage/components/PublishForm.tsx +5 -2
- package/studio/web/pages/VideoPage/components/SoundTimeline/EffectChoice.tsx +10 -8
- package/studio/web/pages/VideoPage/components/SoundTimeline/SoundLibrary.tsx +31 -18
- package/studio/web/router.tsx +8 -0
- package/studio/web/styles/components/setup-steps.css +163 -0
- package/studio/web/styles/components/user-card.css +121 -0
- package/studio/web/styles/index.css +2 -0
- package/studio/web/styles/pages/settings/shell.css +79 -0
- package/studio/web/styles/pages/settings/usage.css +47 -0
- package/studio/web/styles/pages/studio-offline.css +224 -38
- package/tools/cost.mjs +114 -0
- package/tools/lib/agents.mjs +3 -2
- package/tools/lib/channel.mjs +1 -1
- package/tools/lib/cloud-user.mjs +58 -0
- package/tools/lib/cost.mjs +357 -0
- package/tools/lib/deps.mjs +27 -12
- package/tools/lib/oauth-client.mjs +28 -14
- package/tools/lib/pages-style.mjs +67 -0
- package/tools/lib/pairing.mjs +1 -1
- package/tools/lib/schedule/schedule.mjs +1 -1
- package/tools/lib/self-update.mjs +122 -0
- package/tools/lib/setup-progress.mjs +315 -0
- package/tools/lib/up.mjs +176 -61
- package/tools/metrics.mjs +6 -1
- package/tools/omni.mjs +41 -16
- package/tools/omnichat.mjs +45 -9
- package/tools/platforms.mjs +75 -14
- package/tools/runner.mjs +6 -6
- package/tools/setup.mjs +18 -5
- package/tools/sound.mjs +198 -94
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/* What the agents spent: the cost of every Claude turn, split by video, stage, fork and model.
|
|
2
|
+
|
|
3
|
+
The source is what the studio already writes: .sessions/<id>/turn-<n>.jsonl, claude's
|
|
4
|
+
stream-json. Each turn ends with a `result` that carries `total_cost_usd` and, per model, the
|
|
5
|
+
`costUSD` of everything the session ran so far, forks included. That total is the truth, and
|
|
6
|
+
it is CUMULATIVE: a resumed session keeps adding to it, so a turn costs the difference to the
|
|
7
|
+
previous result. The per-message `usage` lines are only used to SPLIT that cost (which fork
|
|
8
|
+
spent what): they come with the output tokens undercounted, so the split is scaled per model
|
|
9
|
+
to what the result says (the price table only gives the weights between token kinds, never the
|
|
10
|
+
final value). A turn without `result` (running, interrupted) is the uncalibrated estimate,
|
|
11
|
+
marked `partial`.
|
|
12
|
+
|
|
13
|
+
A fork is a message whose `parent_tool_use_id` points at a Skill or Agent tool_use. A stage
|
|
14
|
+
skill (`context: fork`) shows up as a Skill, a fan-out scene as an Agent ("compose c01").
|
|
15
|
+
Codex reports tokens, never dollars: its turns count as turns, with no cost.
|
|
16
|
+
|
|
17
|
+
Everything here is read-only. The only thing written is a cache per finished turn file
|
|
18
|
+
(.cost/turns/), which never goes stale: a turn with `result` does not change. */
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
createReadStream,
|
|
22
|
+
existsSync,
|
|
23
|
+
mkdirSync,
|
|
24
|
+
readFileSync,
|
|
25
|
+
readdirSync,
|
|
26
|
+
renameSync,
|
|
27
|
+
statSync,
|
|
28
|
+
writeFileSync,
|
|
29
|
+
} from "node:fs";
|
|
30
|
+
import { createInterface } from "node:readline";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
|
|
34
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
35
|
+
export const SESSIONS_DIR = join(ROOT, ".sessions");
|
|
36
|
+
export const CACHE_DIR = join(ROOT, ".cost", "turns");
|
|
37
|
+
const CACHE_VERSION = 1;
|
|
38
|
+
|
|
39
|
+
/* USD per million tokens: input, output, cache read, cache write. Only the proportion between
|
|
40
|
+
the four matters (calibration fixes the scale); a model missing here is priced as Opus. */
|
|
41
|
+
const PRICES = {
|
|
42
|
+
opus: [4, 20, 0.2, 5],
|
|
43
|
+
sonnet: [2, 10, 0.2, 2.5],
|
|
44
|
+
haiku: [1, 5, 0.1, 1.25],
|
|
45
|
+
};
|
|
46
|
+
const priceOf = model => PRICES[Object.keys(PRICES).find(k => String(model).includes(k)) ?? "opus"];
|
|
47
|
+
|
|
48
|
+
export const shortModel = m =>
|
|
49
|
+
String(m ?? "?")
|
|
50
|
+
.replace(/^claude-/, "")
|
|
51
|
+
.replace(/-\d{8}$/, "");
|
|
52
|
+
|
|
53
|
+
/* the skills before the English migration, under the name they have today */
|
|
54
|
+
const LEGACY_SKILLS = {
|
|
55
|
+
"av-roteiro": "av-script",
|
|
56
|
+
"av-narracao": "av-narration",
|
|
57
|
+
"av-voz": "av-voice",
|
|
58
|
+
"av-legenda": "av-captions",
|
|
59
|
+
"av-montar": "av-assemble",
|
|
60
|
+
"av-previa": "av-preview",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/* fork description → stage. The Skill forks carry their own name (av-motion…). */
|
|
64
|
+
const AGENT_STAGES = {
|
|
65
|
+
wireframe: "wireframe",
|
|
66
|
+
compose: "compose",
|
|
67
|
+
compor: "compose",
|
|
68
|
+
narration: "av-narration",
|
|
69
|
+
narrar: "av-narration",
|
|
70
|
+
};
|
|
71
|
+
export function stageOf(name, input = {}) {
|
|
72
|
+
if (name === "Skill") {
|
|
73
|
+
const skill = String(input.skill ?? "skill").replace(/^.*:/, "");
|
|
74
|
+
return LEGACY_SKILLS[skill] ?? skill;
|
|
75
|
+
}
|
|
76
|
+
const word = String(input.description ?? "")
|
|
77
|
+
.split(/\s+/)[0]
|
|
78
|
+
.toLowerCase();
|
|
79
|
+
return AGENT_STAGES[word] ?? `agent:${input.subagent_type ?? "general-purpose"}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const costOf = (u, model) => {
|
|
83
|
+
const [pi, po, pr, pw] = priceOf(model);
|
|
84
|
+
return (
|
|
85
|
+
((u.input_tokens ?? 0) * pi +
|
|
86
|
+
(u.output_tokens ?? 0) * po +
|
|
87
|
+
(u.cache_read_input_tokens ?? 0) * pr +
|
|
88
|
+
(u.cache_creation_input_tokens ?? 0) * pw) /
|
|
89
|
+
1e6
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const round = (n, d = 4) => +n.toFixed(d);
|
|
94
|
+
|
|
95
|
+
/* One turn file → what it says, still uncalibrated. Streams the file and only parses the lines
|
|
96
|
+
that matter: an image Read puts megabytes of base64 on one `user` line.
|
|
97
|
+
`cum` is the `result` (the session's running total up to the end of this turn), `groups` the
|
|
98
|
+
split of this turn's own messages in raw dollars. `settle` turns the two into the turn's cost. */
|
|
99
|
+
export async function readTurnCost(file) {
|
|
100
|
+
const messages = new Map(); // message.id → { model, parent, usage }: the last usage wins, it only grows
|
|
101
|
+
const forks = new Map(); // tool_use id → { stage, label }
|
|
102
|
+
let result = null;
|
|
103
|
+
const rl = createInterface({ input: createReadStream(file, { encoding: "utf8" }), crlfDelay: Infinity });
|
|
104
|
+
for await (const line of rl) {
|
|
105
|
+
/* `result` does not open with "type" (it starts at duration_api_ms), so it is found by its field */
|
|
106
|
+
const assistant = line.startsWith('{"type":"assistant"');
|
|
107
|
+
if (!assistant && !line.includes('"total_cost_usd"')) continue;
|
|
108
|
+
let o;
|
|
109
|
+
try {
|
|
110
|
+
o = JSON.parse(line);
|
|
111
|
+
} catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!assistant) {
|
|
115
|
+
if (o.type === "result" && o.total_cost_usd != null) result = o;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const m = o.message;
|
|
119
|
+
if (!m?.id || m.model === "<synthetic>") continue;
|
|
120
|
+
for (const b of m.content ?? []) {
|
|
121
|
+
if (b.type === "tool_use" && (b.name === "Skill" || b.name === "Agent")) {
|
|
122
|
+
forks.set(b.id, { stage: stageOf(b.name, b.input), label: b.input?.description || b.input?.skill || b.name });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const prev = messages.get(m.id);
|
|
126
|
+
const u = m.usage ?? {};
|
|
127
|
+
const keep = prev ? { ...prev.usage } : {};
|
|
128
|
+
for (const k of ["input_tokens", "output_tokens", "cache_read_input_tokens", "cache_creation_input_tokens"]) {
|
|
129
|
+
keep[k] = Math.max(keep[k] ?? 0, u[k] ?? 0);
|
|
130
|
+
}
|
|
131
|
+
messages.set(m.id, {
|
|
132
|
+
model: shortModel(m.model ?? prev?.model),
|
|
133
|
+
parent: o.parent_tool_use_id ?? null,
|
|
134
|
+
usage: keep,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const raw = {};
|
|
139
|
+
const groups = new Map();
|
|
140
|
+
const group = key => {
|
|
141
|
+
if (!groups.has(key)) {
|
|
142
|
+
const f =
|
|
143
|
+
key === "root"
|
|
144
|
+
? { stage: "orchestrator", label: "orquestrador" }
|
|
145
|
+
: (forks.get(key) ?? { stage: "other", label: "?" });
|
|
146
|
+
groups.set(key, {
|
|
147
|
+
...f,
|
|
148
|
+
fork: key !== "root",
|
|
149
|
+
cost: 0,
|
|
150
|
+
messages: 0,
|
|
151
|
+
models: {},
|
|
152
|
+
tokens: { in: 0, out: 0, cacheRead: 0, cacheWrite: 0 },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return groups.get(key);
|
|
156
|
+
};
|
|
157
|
+
for (const { model, parent, usage } of messages.values()) {
|
|
158
|
+
const cost = costOf(usage, model);
|
|
159
|
+
raw[model] = (raw[model] ?? 0) + cost;
|
|
160
|
+
const g = group(parent ?? "root");
|
|
161
|
+
g.cost += cost;
|
|
162
|
+
g.messages += 1;
|
|
163
|
+
g.models[model] = (g.models[model] ?? 0) + cost;
|
|
164
|
+
g.tokens.in += usage.input_tokens ?? 0;
|
|
165
|
+
g.tokens.out += usage.output_tokens ?? 0;
|
|
166
|
+
g.tokens.cacheRead += usage.cache_read_input_tokens ?? 0;
|
|
167
|
+
g.tokens.cacheWrite += usage.cache_creation_input_tokens ?? 0;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
v: CACHE_VERSION,
|
|
171
|
+
session_id: result?.session_id ?? null,
|
|
172
|
+
cum: result
|
|
173
|
+
? {
|
|
174
|
+
total: result.total_cost_usd,
|
|
175
|
+
models: Object.fromEntries(
|
|
176
|
+
Object.entries(result.modelUsage ?? {}).map(([m, u]) => [shortModel(m), u.costUSD ?? 0]),
|
|
177
|
+
),
|
|
178
|
+
}
|
|
179
|
+
: null,
|
|
180
|
+
raw_models: raw,
|
|
181
|
+
groups: [...groups.values()].sort((a, b) => b.cost - a.cost),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* the cached reading of a turn; a finished turn never changes, an open one is read again */
|
|
186
|
+
export async function turnCost(file) {
|
|
187
|
+
const st = statSync(file);
|
|
188
|
+
const cache = join(CACHE_DIR, `${file.split("/").slice(-2).join("-")}.json`);
|
|
189
|
+
try {
|
|
190
|
+
const c = JSON.parse(readFileSync(cache, "utf8"));
|
|
191
|
+
if (c.v === CACHE_VERSION && c.cum && c.size === st.size) return c;
|
|
192
|
+
} catch {}
|
|
193
|
+
const c = { ...(await readTurnCost(file)), size: st.size };
|
|
194
|
+
try {
|
|
195
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
196
|
+
const tmp = `${cache}.${process.pid}.tmp`;
|
|
197
|
+
writeFileSync(tmp, JSON.stringify(c));
|
|
198
|
+
renameSync(tmp, cache);
|
|
199
|
+
} catch {}
|
|
200
|
+
return c;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const sum = o => Object.values(o).reduce((a, b) => a + b, 0);
|
|
204
|
+
|
|
205
|
+
/* The `result` of a resumed session is CUMULATIVE (cost and models grow turn after turn, same
|
|
206
|
+
session_id), so a turn's own cost is the difference to the previous result of its session.
|
|
207
|
+
A turn without `result` (running or interrupted) is the uncalibrated estimate of its
|
|
208
|
+
messages, marked `partial`; the next result also holds what that turn spent, so the estimate
|
|
209
|
+
is taken out of it and the session still adds up to its last total.
|
|
210
|
+
`chain` is the running state of one session: { key, total, models, pending: { total, models } }. */
|
|
211
|
+
export function settle(reading, chain) {
|
|
212
|
+
if (!reading.cum) {
|
|
213
|
+
const total = sum(reading.raw_models);
|
|
214
|
+
chain.pending.total += total;
|
|
215
|
+
for (const [m, c] of Object.entries(reading.raw_models))
|
|
216
|
+
chain.pending.models[m] = (chain.pending.models[m] ?? 0) + c;
|
|
217
|
+
return {
|
|
218
|
+
partial: true,
|
|
219
|
+
total: round(total),
|
|
220
|
+
models: roundAll(reading.raw_models),
|
|
221
|
+
groups: reading.groups.map(g => roundGroup(g, () => 1)),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (reading.session_id !== chain.key) Object.assign(chain, { key: reading.session_id, total: 0, models: {} }); // another conversation: starts from zero
|
|
225
|
+
const delta = (cum, before, pending) => Math.max(0, cum - before - pending);
|
|
226
|
+
const total = delta(reading.cum.total, chain.total, chain.pending.total);
|
|
227
|
+
const models = {};
|
|
228
|
+
for (const [m, cum] of Object.entries(reading.cum.models)) {
|
|
229
|
+
const d = delta(cum, chain.models[m] ?? 0, chain.pending.models[m] ?? 0);
|
|
230
|
+
if (d > 0) models[m] = d;
|
|
231
|
+
}
|
|
232
|
+
/* each model's messages are scaled to what the result says that model spent in this turn */
|
|
233
|
+
const factor = m => (reading.raw_models[m] > 0 && models[m] != null ? models[m] / reading.raw_models[m] : 1);
|
|
234
|
+
const groups = reading.groups.map(g => roundGroup(g, factor));
|
|
235
|
+
Object.assign(chain, {
|
|
236
|
+
total: reading.cum.total,
|
|
237
|
+
models: { ...reading.cum.models },
|
|
238
|
+
pending: { total: 0, models: {} },
|
|
239
|
+
});
|
|
240
|
+
return { partial: false, total: round(total), models: roundAll(models), groups };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const roundAll = o => Object.fromEntries(Object.entries(o).map(([k, v]) => [k, round(v)]));
|
|
244
|
+
const roundGroup = (g, factor) => {
|
|
245
|
+
const models = Object.fromEntries(Object.entries(g.models).map(([m, c]) => [m, c * factor(m)]));
|
|
246
|
+
return { ...g, cost: round(sum(models)), models: roundAll(models) };
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/** every turn of every session, one row each: who it belongs to, when, what it cost
|
|
250
|
+
@param {{ sessionsDir?: string, since?: number | null }} [opts] `since`: epoch ms, earlier turns are not listed */
|
|
251
|
+
export async function readTurns({ sessionsDir = SESSIONS_DIR, since = null } = {}) {
|
|
252
|
+
if (!existsSync(sessionsDir)) return [];
|
|
253
|
+
const rows = [];
|
|
254
|
+
for (const id of readdirSync(sessionsDir)) {
|
|
255
|
+
let rec;
|
|
256
|
+
try {
|
|
257
|
+
rec = JSON.parse(readFileSync(join(sessionsDir, id, "session.json"), "utf8"));
|
|
258
|
+
} catch {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const chain = { key: null, total: 0, models: {}, pending: { total: 0, models: {} } };
|
|
262
|
+
for (const t of rec.turns ?? []) {
|
|
263
|
+
const base = {
|
|
264
|
+
session: rec.id,
|
|
265
|
+
type: rec.type,
|
|
266
|
+
target: rec.target,
|
|
267
|
+
n: t.n,
|
|
268
|
+
at: t.started_at,
|
|
269
|
+
provider: t.provider ?? "claude",
|
|
270
|
+
account: t.account ?? null,
|
|
271
|
+
origin: t.origin,
|
|
272
|
+
};
|
|
273
|
+
const file = join(sessionsDir, id, t.file);
|
|
274
|
+
if (base.provider !== "claude" || !existsSync(file)) {
|
|
275
|
+
rows.push({ ...base, total: null, partial: false, models: {}, subagents: 0, groups: [] });
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
/* a turn out of the window is still settled (the chain needs it), only not listed */
|
|
279
|
+
const s = settle(await turnCost(file), chain);
|
|
280
|
+
if (since && Date.parse(t.started_at) < since) continue;
|
|
281
|
+
rows.push({ ...base, ...s, subagents: s.groups.filter(g => g.fork).length });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return rows.sort((a, b) => a.at.localeCompare(b.at));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const add = (o, k, v) => {
|
|
288
|
+
o[k] = round((o[k] ?? 0) + v);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/* turns → the numbers the CLI and the studio show */
|
|
292
|
+
export function summarize(rows) {
|
|
293
|
+
const out = {
|
|
294
|
+
total: 0,
|
|
295
|
+
turns: rows.length,
|
|
296
|
+
sessions: new Set(rows.map(r => r.session)).size,
|
|
297
|
+
partial: 0,
|
|
298
|
+
unmeasured: 0,
|
|
299
|
+
by_kind: {},
|
|
300
|
+
by_stage: {},
|
|
301
|
+
by_model: {},
|
|
302
|
+
by_day: {},
|
|
303
|
+
by_video: {},
|
|
304
|
+
top_forks: [],
|
|
305
|
+
};
|
|
306
|
+
const forks = [];
|
|
307
|
+
for (const r of rows) {
|
|
308
|
+
if (r.total == null) {
|
|
309
|
+
out.unmeasured += 1;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (r.partial) out.partial += 1;
|
|
313
|
+
out.total += r.total;
|
|
314
|
+
add(out.by_kind, r.type, r.total);
|
|
315
|
+
add(out.by_day, r.at.slice(0, 10), r.total);
|
|
316
|
+
for (const [m, c] of Object.entries(r.models)) add(out.by_model, m, c);
|
|
317
|
+
const v = out.by_video[r.target] ?? {
|
|
318
|
+
video: r.target,
|
|
319
|
+
type: r.type,
|
|
320
|
+
total: 0,
|
|
321
|
+
turns: 0,
|
|
322
|
+
first: r.at,
|
|
323
|
+
last: r.at,
|
|
324
|
+
stages: {},
|
|
325
|
+
models: {},
|
|
326
|
+
};
|
|
327
|
+
out.by_video[r.target] = v;
|
|
328
|
+
v.total += r.total;
|
|
329
|
+
v.turns += 1;
|
|
330
|
+
v.last = r.at;
|
|
331
|
+
for (const [m, c] of Object.entries(r.models)) add(v.models, m, c);
|
|
332
|
+
/* the groups are the turn's estimate calibrated per model, so they add up to its total */
|
|
333
|
+
for (const g of r.groups) {
|
|
334
|
+
add(out.by_stage, g.stage, g.cost);
|
|
335
|
+
add(v.stages, g.stage, g.cost);
|
|
336
|
+
if (g.fork)
|
|
337
|
+
forks.push({
|
|
338
|
+
video: r.target,
|
|
339
|
+
session: r.session,
|
|
340
|
+
turn: r.n,
|
|
341
|
+
stage: g.stage,
|
|
342
|
+
label: g.label,
|
|
343
|
+
cost: g.cost,
|
|
344
|
+
messages: g.messages,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
out.total = round(out.total);
|
|
349
|
+
out.by_video = Object.values(out.by_video)
|
|
350
|
+
.map(v => ({ ...v, total: round(v.total) }))
|
|
351
|
+
.sort((a, b) => b.total - a.total);
|
|
352
|
+
out.top_forks = forks.sort((a, b) => b.cost - a.cost).slice(0, 15);
|
|
353
|
+
out.by_day = Object.entries(out.by_day)
|
|
354
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
355
|
+
.map(([day, cost]) => ({ day, cost }));
|
|
356
|
+
return out;
|
|
357
|
+
}
|
package/tools/lib/deps.mjs
CHANGED
|
@@ -241,26 +241,41 @@ export function checkDeps(codeRoot) {
|
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
/** the setup steps that are one of these programs (tools/lib/setup-progress.mjs names them for the user) */
|
|
245
|
+
export const DEP_STEPS = /** @type {const} */ (["video", "render", "voice", "style"]);
|
|
246
|
+
|
|
247
|
+
/** Fetches one program, by the id of the setup step it belongs to; throws when it can't (a retry from the page).
|
|
248
|
+
* @param {string} id @param {string} codeRoot */
|
|
249
|
+
export async function ensureDep(id, codeRoot) {
|
|
250
|
+
if (id === "video") return ensureFfmpeg();
|
|
251
|
+
if (id === "render") return ensureChrome();
|
|
252
|
+
if (id === "voice") return ensurePython(codeRoot);
|
|
253
|
+
if (id === "style") return ensureYtdlp();
|
|
254
|
+
throw new Error(`etapa desconhecida: ${id}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
244
257
|
/**
|
|
245
|
-
* Fetches whatever doesn't answer, one piece at a time, and says how it went.
|
|
246
|
-
*
|
|
258
|
+
* Fetches whatever doesn't answer, one piece at a time, and says how it went. `onStep` hears each piece by the
|
|
259
|
+
* id of the setup step it belongs to (tools/lib/setup-progress.mjs): running, then done or error.
|
|
260
|
+
* @param {string} codeRoot
|
|
261
|
+
* @param {{ log?: (line: string) => void, onStep?: (id: string, state: "running" | "done" | "error", error?: string) => void }} [o]
|
|
247
262
|
* @returns {Promise<{ ok: boolean, missing: string[], errors: string[] }>}
|
|
248
263
|
*/
|
|
249
|
-
export async function ensureDeps(codeRoot, { log = () => {} } = {}) {
|
|
264
|
+
export async function ensureDeps(codeRoot, { log = () => {}, onStep = () => {} } = {}) {
|
|
250
265
|
/** @type {string[]} */
|
|
251
266
|
const errors = [];
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
["Python", () => ensurePython(codeRoot)],
|
|
256
|
-
["yt-dlp", () => ensureYtdlp()],
|
|
257
|
-
]);
|
|
258
|
-
for (const [name, run] of steps) {
|
|
267
|
+
const names = { video: "ffmpeg", render: "Chrome sem janela", voice: "Python", style: "yt-dlp" };
|
|
268
|
+
for (const id of DEP_STEPS) {
|
|
269
|
+
const name = names[id];
|
|
259
270
|
try {
|
|
260
271
|
log(`preparando ${name}…`);
|
|
261
|
-
|
|
272
|
+
onStep(id, "running");
|
|
273
|
+
await ensureDep(id, codeRoot);
|
|
274
|
+
onStep(id, "done");
|
|
262
275
|
} catch (e) {
|
|
263
|
-
|
|
276
|
+
const message = /** @type {Error} */ (e).message;
|
|
277
|
+
errors.push(`${name}: ${message}`);
|
|
278
|
+
onStep(id, "error", message);
|
|
264
279
|
}
|
|
265
280
|
}
|
|
266
281
|
const c = checkDeps(codeRoot);
|
|
@@ -20,6 +20,7 @@ import { mkdirSync, rmSync, statSync } from "node:fs";
|
|
|
20
20
|
import { createServer } from "node:http";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { join } from "node:path";
|
|
23
|
+
import { confirmation, document_ } from "./pages-style.mjs";
|
|
23
24
|
import { openUrl } from "./platform.mjs";
|
|
24
25
|
import { defaultVault } from "./vault.mjs";
|
|
25
26
|
|
|
@@ -155,25 +156,38 @@ export async function login(studioUrl, o) {
|
|
|
155
156
|
res.writeHead(404).end();
|
|
156
157
|
return;
|
|
157
158
|
}
|
|
158
|
-
const
|
|
159
|
-
`<!doctype html><meta charset="utf-8"><title>OmniVideo</title><body style="font:16px system-ui;padding:48px;max-width:32em">${msg}</body>`;
|
|
159
|
+
const html = (/** @type {Parameters<typeof confirmation>[0]} */ c) => document_(c.title, confirmation(c));
|
|
160
160
|
if (u.searchParams.get("state") !== state) {
|
|
161
|
-
res
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" }).end(
|
|
162
|
+
html({
|
|
163
|
+
kind: "error",
|
|
164
|
+
title: "Esse retorno não é deste login",
|
|
165
|
+
text: "Esta página veio de outra tentativa de login. Nada foi conectado.",
|
|
166
|
+
hint: "Abra uma nova sessão do Claude Code para tentar de novo.",
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
171
|
const code = u.searchParams.get("code");
|
|
167
172
|
const error = u.searchParams.get("error");
|
|
168
|
-
res
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
res.writeHead(code ? 200 : 400, { "content-type": "text/html; charset=utf-8" }).end(
|
|
174
|
+
html(
|
|
175
|
+
code
|
|
176
|
+
? {
|
|
177
|
+
kind: "ok",
|
|
178
|
+
title: "Computador conectado",
|
|
179
|
+
text: "Pode fechar esta aba. Estamos preparando este computador em segundo plano: acompanhe cada etapa no Omni.",
|
|
180
|
+
action: { href: studioUrl, label: "Abrir o Omni" },
|
|
181
|
+
hint: "Este login vale só para este computador. Você pode desconectá-lo em Configurações → Agente de IA.",
|
|
182
|
+
}
|
|
183
|
+
: {
|
|
184
|
+
kind: "error",
|
|
185
|
+
title: "Login recusado",
|
|
186
|
+
text: "Este computador não foi conectado ao Omni.",
|
|
187
|
+
hint: "Para tentar de novo, abra uma nova sessão do Claude Code.",
|
|
188
|
+
},
|
|
189
|
+
),
|
|
190
|
+
);
|
|
177
191
|
settle(code ? { code } : { error: error ?? "recusado" });
|
|
178
192
|
});
|
|
179
193
|
await new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* The look of the few pages that are not the studio's web app: the hosted sign-in and the computer's approval
|
|
2
|
+
(cloud/studio/src/pages.mjs, account-pages.mjs) and the page the CLI's loopback shows when the login is done
|
|
3
|
+
(tools/lib/oauth-client.mjs). They come before any session or any app, so they can't reuse the bundle; this
|
|
4
|
+
file carries the same tokens (studio/web/styles/tokens.css: the ElevenLabs layer, Schibsted Grotesk, 12 px
|
|
5
|
+
controls, 7.5 % borders, light and dark) so they read as the same product. Pure strings: no Worker or Node API,
|
|
6
|
+
so both sides import it. Text in Portuguese: it reaches the user as it is. */
|
|
7
|
+
|
|
8
|
+
export const esc = (/** @type {unknown} */ s) => String(s ?? "").replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`);
|
|
9
|
+
|
|
10
|
+
/* the brand ring, black on transparent (studio/web/assets/logos/omni-black.png); the dark theme inverts it */
|
|
11
|
+
const RING =
|
|
12
|
+
"iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAARGVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAADoAEAAwAAAAEAAQAAoAIABAAAAAEAAADAoAMABAAAAAEAAADAAAAAAE07OcoAAAHLaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT4xPC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj44NjA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+ODYwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cl7k0NcAACM7SURBVHgB7Z0JuCVFdcdnBlRGRGVTFNkGRXBHcUEEBEE/DIm7Eo2AuHxGY9zQSHAJiBrFLeKSuOG4myhoRCOiQsBEXCIq4AYIiIqIggqoCMzk9+93T0/dvl3V1X277+2+r873/V9313LOqVPnVFdX1+23YkWiZIFkgWSBZIFkgWSBZIFlZ4GVy67F/gZvRtatwKaj4y04yj7rwTrwZ3AtuAZcNwKHREO2wHILgJvTWduANWBncGewI7gjUPptwSZA5TYCq4ACQLgJKAj+AK4GV4DLwcXgPPB9cCn4LUg0EAssegDckn64K9gD7AvuBbYFW6jh8moP+bJC9lJw/BL8CJwNzgTfAikgMEJfKdShfdW5Sq/bUUDO/giwF9BorxHdJTm4z8ndcnXOV2LMlQWml8Lg2+AMcBr4AUiULNC6BW4Nx8eAj4CfA3NwHTV/1/TFB+UbimUs3Y7K13mxXPFaZVwddK5nh1PBoWBrkChZYGoL7AqHY4BGVtfhcodkVM7PKaPzojPb9TRHV4aPj6uf7gxvAfcBiZIFaltAU5u1QPNrc6zMuXF41wFvGgWAm5ad+9LhN1E2Nq0gu8jHDRLTWatKnwT7gUTJApUWOIASnwd64DQnMscaczg5o4GyY3l1riucOpovDwhW1vTVcT38hRs5/xzYByRKFpiwwH1J+QSQo5jjm0MN+jgKUmvTn2jfWpCmRhgh0YoV22GEE4CmCnKSQTt7rv/Klb52WCDogfl4cHuQaBlaQMuWzwaXAXMKTRl8jtMoXaNvmzxj+Y1G/TLZNj2yNl+Ifs8AeiudaJlY4N60U2vm5gRljrKoaRYAdjQbnIE99gWJFtgCG9O2F4LfAHV86yM+PKcKHHeEdx5qa/EM1YO/OX52dK5lj+vBu4Fe7CVaMAuoUz8DbMQzR6jlXNTvtLwbALGyQg7v8nB4W9vz4yjPbKP9R0cCbdpLtAAW+CvacAmwUd86vpEzO47UqD56zLuetV9HVxdLt0D4JvmPAokGagHttnw5sDV96+Bix7tOEDxfuWJltvYPz6ycgmEIAVGio9mi2F5L11GBoPx/B9rkl2hAFtgCXT8KbDRzO7bY6eXXG14sleePj56lZWKmJ+acdkTnUl5R6VPo7Mg3W0kPs5+2Z78WaCNgop5bQPvwvwrUeVlnOp3b2Lna4IE+jeXPsa7dDWTPC4CWTbWMnKiHFtgdnbRf3px/7g7nBs6Gc++Lqkzf4p1jQ71uAkj8AzJsEHED4QxsvB9IVGEBzcNnRQ9EkDZ+6Q6gAKBP26dOmE6hZkifUF4NkWJjkF2FncBfg13A+UBLy4lKLDCrAHgAsk8C2wGNoi31PZwKVIdxVdmq/ILoPlyaygoC9a1eKh4CVoFzgd4lJHIsMIsA0M8RP0XP3IljtfMzv4ilrKT+VFSpyI4VF10uRp6vjC89WvgGaxgrvS84ABwMfge+DxQgibBA1wFwd2R8GuwAZHTrFE67JQkqE1aWZpqE8qxM18eWdCiyWc+4oh/9Pw5oQLoI6Jdzy566DAC93ZXz32Xl0u5HDrOxt+Qs6hAXa0IrNzrqoN8daFDQc8FTwPbgPKAfFS1b6ioAtsSieuDVqk/1tKdl89cJtJGDtKxBL9m5Tb0ZGt4PPAHcAPSgrOOyoy4CYDVW/CjYH3Tq/EVHd3s4tidVp0k98W9aL1a3GZTTxwQOAvqChl6mLbuvVnQRAK/GkEcAOb+odT8pOv6SmPQ31gJOh2imqKnRthx1N9Cq0Y+Bvm+0LKjtADgcq70eODYeO2/VqK6QKsYqW6d8Fb+u8wv66vnJkrIjf9zrTJ2JhGolVUVkj0y7cf5ksDn4HrgOLDS1GQD6HetHsKiW3cygZuDaRmxcsYakWciooY5bVKrpRpcdOZc95Yy/BlcCvdjSkqY2EmqNX1sfrKwdswTS65DkbAL2gsljOErm+UBvmReSZKw26FYw+QLYC0w19dFQt369xU8bqvWOh9s41/7uuRxcKzRfHx1/ylHTEq3Y/BHIxhq89OnH24A7AK3uaNHh/phwV0yovCwIEGh9oiSXXJl5uiKP+pb332QcA07PC6STCQto3q+O1UghyOAZMGZ+bmmzONJ7c5Gbt83fbrOR2Ul2EzS6fww8EWwHmpLuBnr/8jzwZaCAMRmubDv32Un5ylNd3WneC3YEiQoW0B4f3SplKDOaz6gzT+8qEKr4BgI/czzqm1Oeg91eDHYCXdD9YfoecBUwmeb8sf1l9fTy7Eigu04iLKDb7P8AGajSuaucJoZHW2Wky4z1MaczZ/oObTkMZFMVjl3TXRHwTuAOVqZTVSBYOdNdD8iP71rhIfB/IUpGOT/lKgOkrTJ1HLtO2br6Obz1iUZzHs3nXwQ2A/OgPRH6FWD6mHOPBYGju/WbW876/NPwEb9lSdvR6l8AM4YZqtNjSccE5dUtT3uC/CLzXWcRP9lII++/AG0KnDetRoF/BNcC6Wb6Zm0P2MzK6Wjt+gPnbwfbgWVF76C1M3V+5AWdMzDvDtar4lsnH+dxnUT2EU4BelbqGz0MhS4DFgQxdnLbp3Nro+5sLwWbg4Un7SPRiBZjsKxMYFSJ4jFt/a51dYLPdYofIVcvlvpM90S584EcOaYvxgLACXgLhPPg82iwsESbsy8R1Bk1SoOgLad2+bjn6BnTodVl/EuaxbrmBFcg+xVgKzAEWoPdvoWi0UHg2plzNyjMLz4OP71ZXjh6CC26Hqjz1fCiE0xcu8ZS+eJ1DI9ZlZlCN3W8dlSuBXcGQyM9m3wXWBAE+7bETsUgEB+90DsaLMyyKe1e8R/AjDTh7OR505xpgrdMqH7P8qzDZQvhDKA59ZDpbih/KVB7rH21+qoQGGabc+GnF3zyn0HTvdFec381bMIwanzBABNlyuoNME3OYZ17AedPB3oDuwh0II3Qyo76Tu0M9qH1ecXgZrb6FPyGeHdE7SV6G4dS5yc9aKgFyNdavtponanb+6vB7cCi0ctpkNpZGQCUyfo9cuATT71NPhwMjm6Pxj8DtQxD+VbuCkUDF68lpyuMZGUvszjX/nl94UL7bRaVbkHDvgLU123Z1aZU4imsBfKpwdDT1floGz0qUNZrPHh589x6FbfWKB4uv4bn1mnfpP6jwHKgPWikLXVnd7/YPqNeVb9k9oSftlTsNQRjas/5qaC1AIBXlZH6kG+Ofzn6atvHpmA50Qk0tss+F+/fgWf23ag7o6D2okvhPjhm1zqY4+th8F/BGrAcSUuj+h2C7G3PQLVsH3HXMJ86DhkU7yc9F7VM0VoGoN5gyo86y5z/i+j+oH52x0y1ei3SZJN86hvh1E36XDLeA2a1OxZRcaSo/BzofQBUd0zwo7fm+Nq+cCjYGCRaWrbU3b+VxQzx8cDsfzL5+lpFb+iOaDLzXZ/ILDWUnHz0oa2J/OoAGOeZ8VoKbBlfy5qvAkPZvoCqMyNta5CNJmzeQZrknAJ68/b44FEjbRlrFkaYSkaNlSMZ+wbwEaC3oInKLfAYkmcVAOp7ydJvDfRb87mTXvZIIW8A1B154TWVg9etXxIQao9wFtgfJApbYGuy9QJLNptV30mW3hzPdeUN317xeSBlagdAXwLDCQC1Q7gI/C3YBCSKs8BnKKaP7M4qAOxOcCJy5/Y8tgXCL1bDgTcAyJuJUUZz9iaypL9wDXg9GNQbSPTtAx2FEq0FQI3BUf32T/MywO4I/iOQ0ykAmjjfvOuY82tOeV+QqJkFHk4168sJX6jh0MYj9qj+03PaU5upPV2tQ6guBVob/Ts0VNGg5vjnoP9jpzNDqo0FdgJXg1b8QX5QwxckU0uxe4JWSVscQqRv+4fya+epJR2TFBZ+BV4K9gHavJZoOgvow13C1KTOsdEpkpmKa1lUL8r0QN4aVQXALs5nCtuNhIgm1BBo9lSV68F7wYPB8UDz/kTTW0Ab4/RTz6mp4SCoO/zdwZtADdcIqxsKAAnRXpCMqiRW5Rufjo5qh/AlcCDQ5qqLQKL2LCAHvLY9drU5ycUUO3oWeF7t2g0qaE/G94GE6hmAN7D5Q1Bxvp1do2FpuurGoiYPG/l/AP+ngbktlyF7OZCmkuYPmU9w7e3bUF+G8kI8R/K1g/Q+YGoK3QH0Fm7sdbQ+2hx6JJBlXKKRGdnRzfOdF3l4yomloC0aLwGa7pwIbgSJurNANp0s+oDvOTHUl6G8CvVVVXuF3gBuVlG2MjsUABIy8RauzpfLrZF2LGpTJzCcuqqmeb62Ke8F3gi0OpGoewto1Nen08fIeU4cSy+78PW50n15JXykgaa6zyjJq5UUCgA5v34aV2juJP8aio9VLjKu4GM2OhMmBwG9yb0EJJqdBTYaibK+qC252OfGQOm+PCtTcnwlaTuWpEcnhQJgNVyibjGxihdvnUUtPXzM2BdQ/gigyD+9WDdd99MCvulRC9rKXbYBR0/DKxQAeqC0fDnh1FS8dVYwNMfXvFNLX3uDE8GfQaL5WMAzRvmVqTM98nOZzBk5pPR5Kmj8wyVz8EkJS0/3WYNb8f4yCf40iZTsT4J9wZGglTVo+CRqboHsGaBYfRr/aFo3c0wUob6m6VoIacQqKgBMWLHhHVyrEYK+FvA48ARwDkjUDwto+8IEyT/K0t2CvulvHd8qkzGqfzCyGm2TCAWA8spkuu2KOi823mU6OtdB+Bl4MdB052SQqF8W2MjnsL50U7/m9NeqjR09MpR8c/CCscKRF6EAyP00P4lkqmJunWLjpbEFBecqqpcpJwIta74Z/B4k6p8FvP7i9vec1P5L5D6grmxvg2C0DtfMfFV/6lJVHYJCNhO0uvN4cAT4KUg0QAtU9bc1yQLFjpY+5VHi9eOmF9XlEwqA9U0WZkMKOI2207WU3xek6U7IcAPNs0521bdAsWNZGbd8zXM9C9T6bXcoAJTXqn6jRounpjh6kXU40FfXEi2gBczJQ02LKROq7+SJlV7eHu6kVZ6GAqCysq9AIGqUpSmP5mvaypBoWBZYFejbTltSQ66m09rGE0WdBIBHstpwHjgInOkpk5L7bYEafthuQyLvFCq2E9gnVnooABo3VloUKuvyO+DR4CKQaJgW0GpdL6ngb4+NVbIqAAp8Y9mObWwSj/OBlErOH2/CPpZcWrvro2bjOh3A5RbjSeVXoQBQjYk7T42IsBvBJfB5IrgYJBq2BVbaO50afjBVi2PlOI6qU/2SMWp/UFUATCjvCJrIKySIt1Z7DgP6ZVmiBbFArFPGNje0Y9T1txpyVVR3gUqqHQABjtLVYM8ALyPtzECdlDVQC7iOOW0TYneMRsq0YvuilzbKBamtADChJmwlCR/mIi11mkUW45hthit2dhdNs60yDXlLxV3BLlX1QwEQ285iOfHUprajQDGvSp+U328LrJtFh2r+Ys8aRXNEToOkpj7qULk3KBQARdkx1xa4r6GwgiDRYlnA/K/TOAgxD+WVmHqPkrSxpFYDAOto6vMtJKwdk5IuFsUC9pvgsfZYVIwlzu/CVeeeqFGqs6kXCgB76REbdHJ+ldV2Zn1QN9GCWWDVKlvbyHezZy2MdZAYc7jeG1PeU8bY6DlA/+HIS6EAUKWqtrn5mv6cSx19Rz7RIlpgXfZPwrOWuR3fZlNb5rsluu0W0q8qAEJ18zxbx0X5j5P4hzwjnSyUBWxKQKPG/NQe/NRYG3q7aLjxtqNPxijfdNzZV07pUwWAKcI6rk71ubq0r19WXVyyxRnr+qyllqgL87ppTDDG3GFkvO1oWcXyhfwdrVzZMRQABT6T1Z0C0kEPvz+eLJVSFsgCTpd31yoJKTp1SFqFUtuG6oYCIFSvLO9sEp27ZFmRlDZwC5T6ZWnilA2tcOo63BsHQN12pc+X1OmWYZYt9YkWnbWxVUoVW+K2NYfVPsahO0CAZ85OZcRDD776pVeixbZAyF9KWy4HiXGk0so1En1BiGytBG3mY7WxL4P0OnpfRfn0296AMRcpyxzD53RuW2PKqHwdni7/qnPka0OctkWUUu2ILuWyYsVvSL/Gk5eSF8cC+jepWulxF36iWmcO7iusQLFgsWV1X9mq9JEsYzeTANAd4E9ViqX8wVsg27lc5czTtjJ2e7RPjnk++TrVF84n/s+F1Q3dARw+Vtx7TM7vNc1CZWSrfK5juMHgnhdb7dYp5nV8rWm+93cBoQCweVnervykY40T+95bIHcF17Hd87ZakAuqx9BVRSy8fu7NoJI2t6lyziw/WVJG80FL8i4zLRVNfxfEAuaP1u+dN6sFQWJR+ll3KR8MAPKtwSo7fqGEDdppmSnqv8moWqJkgboWGHPEiMrO/iRN27z/VCUUABJjLp4d7WIkX3cIo9tw4l1qskLpOHgLOF3ebluqHLwo2Ffe0kfLVLqU83s3aIYCoChzosVOlOllw1YTBVLColkgewh2+r219lU6W0GSr3xJuv6jaKMAsGAqiN5wOYoyydQdILjtdEOtdDZkC8gpar8EKGnwNGv9qlvpnBtkKgC8P9AK3QGMhWT55GX2IF98Kn9/aQzTcbAWWFUywjZqzDRr/apboYfrr/o21bU+JWMCwFe3mP4QElzBxfx0PXAL8JPI1qmL6VRBySu5vq6Qll+GmlQRZDkPO3kAJ9vbRToungXW6X8GRZBbyD0vqxqaTlXVLeNXknYFaV5fDgVACS9vkgToQfiR3hIpY6Et4Dqr623ueV0DTFMXWaZS8IPMjQPAuKtRnJuuh3Gp/9iXaLlZQA7hOsWM2x8Q/YOQKrUDwASZxzvMlaQH4X2ctHS6TCyQTWVKnML8pWgGX3qxXOy1R7TeAAd/p1IZACiabYE1RSSoqPxIuA4bkfd3JUWsejoO2wIlfhZukK+CL93HrehzvnKFdG3Tv7iQNnYZCgBFT/YtyOKDSkH5Md3I03PAAWNS0sWiWKDQ9bNrVkPBmv//MqRlKACyt36hyk6egkCQntoT9Erg3YJKXqJhWqChH86tsd9G8o0h6aEAGBvZQ0wKeTKS3gk8t5CeLodvgZC/9Kl1Fqhfr1Iq1CDl2chexcfNl3BBd4EHuhnpfPAWKPWXpiNlh9aQnnr7+80qGaUNGlWyAKjiUcw3e2h/0LtA1D8rKzJJ1720QNa31sGmoQ23dt2To/4x44VVuoQCQO0strWKn5svu+wOTgDptwKuZYZ7nvlDFw4/jaN5zHka6cH5v+qFAkD507ZV9Z8MjhOzRAtjgcb+6qsoR2lpX5BE3AA+G2PtqgDIefgUzwv4T9S2l4JX+4uknIFYIF8ZDPlDKC/UTne53XjU2vi8gfl3OdU/Zq+kmACwb8GUMjNF3cySNAXBy0EKAtdQwztXP4pcX11Kcf5aIScpO5VfWF6Jj4wVt3KVG5/HauUXJ3Pm/RlkXoqTUADkOtJa08etm52XZZSkKUlQELwVbAISDc8CuU+gekk3hxvkVnDPw7Vq5Uo//fpLARBFoQAQA2uwHaOYlhRSfbVZeD4XJ3FMvyDDCAOjjvy2VSt8BW4/jOUYCgDN99TgaZ3fdLEg0PaKg0g8HTzJMtMxWWBKC8i/5LNadYwO1FAANNYnEDHKMkW34/xjYC3YASTqvwVcxwp0czcNiRCof9KigTWaQgEgeY1WplwreTQRb1tROJTzM8GzQNo/hBEGQI38Ygbteh8ytAQaTaEAyKZAEc4cLaxQUEEg9pKzPfg3oJcXDweJemoBG4XNL+zajiG13aiJKV/kJZmeekrWvP8TxTpV16EA0HZoa2cVH59iVfWsPQoCYW/weaCG3A0k6pcFJpY/ox2EdriV69RzTRCo9wbK6R811qJQAJhzVjLMIju69AQ71TSofdLpieAsoDfI+hc3iXpugYBjVmre3HUy1qp+Dqg9+qt2KADUprxdISUV2W50i/EUJJm6G2gT3dHgf8FhQJ+5TjRfC4T8pbFmuZPV5DDySfmKXrBq/b82hRqkh4l1CMn0a6pkmUahYKK8sgWbFt2Z8w+AU8FDQaI5WQBn2WhOosfEOv6jj8TppddnxgrUuAgFgF4l34TjT/MVu1JVIoPJ2mmBsD/Mvgg+BHYtZZwSO7UAHeE+x3plWcd5C1RkRNbXx5mvZubxCtjJRxpRKACuQ5HsP7+0OL2pq6RrCzVyYxL+hqOeD7StIj0fYIQZkgbESrIybudVVnIKWH0naezUyT+ejOBnT8Yq1rzQh64uA5Jno/C8jlqRciGdhAvBc0B6f4ARZkDvR0Yf/EE6aBDcdNo2B+8AMNeHRUUS6KWo+6K3dlSGBhODKlgg7sz5O4DeHxwIEnVrAQ1CIvlD0CeyUt38kR9cA54PrptWRCgA9FnpK2MEzHKKxAOJBYKO6gQFw95A7w+0teIeIFE3FtioG7a1uWp5XF98mJpCASDnunxqCS0z8HxWW0GgzjkE6NZ4LEjPBxihZZp6ylGmj4Y0jWYRpGL/CbSlvhUKBYAEXFIlJVd8BvMgV5dc7gbb2d3gtpR7BfgaeDa4BUg0vQXkK7d32Dhd4KQWTmMKaQahzqsgsdJ2B/Vp1I9dKvhl2VUBcFEVk1zxGvMgtaTMMDVGgqLBjKWOFgh6PtBXKbR0ui9INJ0FNPrfsS6L3D8qKmZ9b05hxw11lPJb8DQw01nJgxGYvRDjaA+erRxpUSt8PHrpYU2QDPWBRowPgvT+ACM0pDXUkxPaANNl/7m8JU/L8U8AM6dtkPgL0EmjOw6CohHXI+/X3PKOoT1bzdySwxf4CJrg2rTT85FvyO80kGmr/FxIU6QvAVNEWyM6bTiyuuSvdgiaSx4K0veKMEIkHUU52a6yf+QjLfiJ9ZX2g82VXoN0KWNTikoDULbvZcy4X0bX/UCiagvoOztRAUC5afvf+ke+N3c6GA2mbVBf68vQej74ANgFJCq3gKbCevicRQCY878RedxI5k93QIXOngPgHRUcLdxSfbdlPRvI6Hrp90qwOUg0boEjuJyl878WeZp+94IUhaeAWRggKhjQpfZ0LCKAbOQ5H/5PAX1564kqcyXZ4XTQdf+Lv1YcjwS9o+eikRSs7XjUiXXqOuVK9WAtuQ6PibKjIFE7hS+Ch4DlTntiAG2LmbCX0qa1+Yiv7K39PU8FvST9KOVqIEWzqUTEiFpqMNUPIcagXcl29LIgUMe/G6j9y5U+TsNlj9JBh/Rgf0bki/cF4KGgt4TPrfgvkAUAx8pGy0ln4KiVeri6NtDHAuFX8DkK3BosJ3oojfWO/uTVsv+ovAJJMNtqdWkH0HsqfRBic2YTIwytjnWWfnytN5IaEBadbk4DtbFQba8e/T3Tz5JBx5xfv+E9Bgxmr9aWKHsxkEGG5sBt6WuBoB2J9weLTC/AeaP7ujh1LXF89YHZT1uZ9xui8Y4dNaIVh/IYKcg7tk5sOdqTyVP5iDo2Eqoj9T+o3g7WgEWjPWjQ1dgjPgA8g+LIpub4f4Svvt+jHbuDJHW2/vlwtGEou+Rgnluk5c/6WOXsyveMagoCu43LDnpB9CLQyV55+M6adKfXNzZL+7hoE8rlA4idF47m/GeQvjcYPGnUU6PMEcZGbJ+BKD9WrpPrKYKsMiAmeVv7rYP/jzbpg16rwFBpNYqfhC2sf6fpM7OL3qscCvRMsRB0F1pxFZBxzAnGDFXlTKO6Y3UGmGZtt6N1uL5Rcz8wNKLbst9Xm/OrXbX7aBQ84qE748vAQr5Z1zyutqGGFhiR+loAmMPILno+OAFsD4ZAetv7ppHzyumtTXUCQO0WfgfeAnYAC0vb0jLbH3RTpKPkD5mx5ZFRpwP6UvYmpoG2v+hntEFfL+jz88Fm6HcikPM2saHqCdrG8FFwT7As6MW0Ug23kS83XpWDV+XDM+cVOq/i4z6PVJUNyamb58g15/gGPB4NUKNXtAaF3N97TPQl2ob6wtp3KuX261XLZqCMRrWz6VIZIWSk3uR5g2DyAbdNneVU5ih9ej54PHpdgk2kWzbl4Tw2AKw9X6eu+Az5wR/1m5M2i+mNXqdB4HXcgQQe9lFAmdNcw/nbwM5gHrQTQtcCC8zM+UfXVQFgbdAv6p4BNgHLnl6PBcYCoInDNqmD3NojteQUZRWvm/CtUSdzImReQZ1jwR3ALEg/atF+Jj2XWH+5zh+ypTn+T6mrlZ0tQKKRBW7DUa+2zaghQ3aeF3LmUB76d67bSEbmdOhigXAp6ccDLZ12MZXYDb7HAckxR/Y5vqWbLaz81dTVzxNnFayIGhY9GHV/D2YaBHUduqx8WRrtMAdo/TiSZ46mozmZtglo89k/gN3BLUET2phKdwfPAacALUtmMpBtcsvaZXk6Kl91rgcfBAv7uUls0hq9BE5v4KF4HaZrxFeVZPUuqYmMsjqxaTFt0edO+eSjWBr9iZMLwXnge+BH4Erwa6A8I90xtEVby9Jyej1XaBnyHjC7lWNLObSp7Mohecnklsm13YX0gyCN+meChaWiMaZpqEaeD4MnAcf207Bc1nWLfaORWc6v9XYjOau2GJQ9jMrpXTIfN75jfUTiqlGCVnb0ovPToMjD5bcQ52aMthqzFYy+ADSfHTPwtAKs98r4MIA2/h9lxteOZfznlObaT+qJ7Lh0tfRX5dyybl4+7DuJxsPqrBq1/SeU0YLGB4F7l3GqLt6pGaPNlt0LZqeB2wEzcpv8J3mNenAyYymlIjuvFlMupkzOcDYnQRs7+uo0Jy70L4ZEvwLvBO8anXNYPjRmlBabfTC89FvSTUHeQWUjtdNBU4lvi89USsy3cm5njxrW10v/YWH9iuup8CHKatTX80aili3wdPhp3qqO0VzSVhd0PrHXflQmyys7p/e8eb7ydeuU8Zl3Ws02yMalGPFRXyj/ZKCVu2VPG3VogXPgraW9AwD2z+8EOq9NMZViytQWXFFhHjIrVCrL1sOyVD0LaHn0OHAZSDQDC7wMGdldgCkQOyX7+yN6PKTWXYZ29bG8eweQ3QX9WEercwvzoxTa0gp1eQcwBb/KyY3gYZYwi+NARua2TSFnF9mIfxHnrwIvALojKzgSORaYRQBI3FlAo+X+QL6pjmrFR31MfOnIXWQyx/8NjXwzeCY4A/wZJOqBBY5EB73IUQBoNOrjFGIQOhHgrp6yp6BnrveBu4JEPbXAoeh1LZh5EPAM4jpN5Xnt8uNOOcFfTluXJ3aa4OOkmeNrinkSeBBINAALPBIdfwHUgaEOzvIyx/GUK4yElbx88kr51AwYH++W0+3Oac5/OvwPBIkGZgFt2jqbEdGCwDq2sRPDL69b6tBOvlu2yXnLI3l+ZyjTuyDLHP8b6H0IuBlINFAL6IcV7wfWqa0GAXzzgGjjvOCIrfKO0M9sdAFlnwVWg0QLYAEGvRV/D37Pid0NZu1cfZZnjq8p49Fga5BoAS2wJ23Sbd06fGZO6Y7s7jm6zEwHR5Y+M6M7odlBPzR6K9geJFpwC9ya9r0RaDtu5d1A82WBsoOBR185vEFtUdv1sYH3g3uBRMvMAvvR3q8BGwUH4+DoXCcozel1NMfXkuanwKJ/fp0mJgpZQA95zweXgoaBEN53VByNi9fIrRV4Neub85vj6/oLQJsHEyUL5BbYlrN/xrmu4GiBYCPmmIOWOWBxTm9l7AjPMR4zvrb2JMfH8InCFtiR7NcBNxA03bCRtDQoKJ+vseu8BzCn1/4cOf5fAO3hSZQsEGWBNZQ6FvwEmDPp6A0A8ubt+K6el6HPO4BWvRIlCzS2wJbUPBScBq4DrpPN2+El39VHo712xD4bbAMS9cQCzCAWgrSt4hHgUUAfldJvkV2SMzYiGUiVeZZwvzzh8jMb2tHk3MCJvuvz2RHSfnyzTI+OxU7rkWqNVFF7dgH7Aa2maCnxTiTaN2+4zCn7EpXryXnO0slElhMEkiO4pCmY3tbK6U8fQR+1SnvxMUJfqdiJfdWzqV7aa6S98fcZYTeOO4KtQdnHpEiOIu2718P4JeCH4FygEV5fcLsKJBqIBRY9AMq6YXMSNQ/X8qqOCoatgIJFb6JXA/1STlMYvY3V84W+r6nPEsrpLwc/BxrtlZ4oWSBZIFkgWSBZIFkgWSBZIFlgSBb4f5ZqkORg1V9cAAAAAElFTkSuQmCC";
|
|
13
|
+
|
|
14
|
+
export const brandMark = (/** @type {number} */ size = 28) =>
|
|
15
|
+
`<img class="ring" src="data:image/png;base64,${RING}" width="${size}" height="${size}" alt="">`;
|
|
16
|
+
|
|
17
|
+
export const brand = () => `<span class="brand">${brandMark(28)}<span>Omni</span></span>`;
|
|
18
|
+
|
|
19
|
+
/** Google's mark, in its own colors (the outlined "Entrar com Google" button) */
|
|
20
|
+
export const GOOGLE_LOGO =
|
|
21
|
+
'<svg class="g" viewBox="0 0 48 48" width="18" height="18" aria-hidden="true"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg>';
|
|
22
|
+
|
|
23
|
+
/** the round badge on the confirmation pages: a check in emerald, a cross in the error color, or a mark in amber
|
|
24
|
+
* @param {"ok" | "error" | "warning"} kind */
|
|
25
|
+
export const badge = kind =>
|
|
26
|
+
kind === "warning"
|
|
27
|
+
? '<span class="badge warning" aria-hidden="true"><svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v6M12 17h.01"/></svg></span>'
|
|
28
|
+
: kind === "ok"
|
|
29
|
+
? '<span class="badge ok" aria-hidden="true"><svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></span>'
|
|
30
|
+
: '<span class="badge error" aria-hidden="true"><svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></svg></span>';
|
|
31
|
+
|
|
32
|
+
export const STYLE = `:root{color-scheme:light dark;--bg:#fff;--surface-2:#f4f4f4;--text:#0f0f10;--text-2:#00000087;--text-3:#00000070;--border:#00000013;--border-strong:#0000001a;--muted:#0000000b;--action:#0f0f10;--action-text:#fff;--action-hover:#2e2e2e;--emerald:#059669;--ok-bg:rgb(0 204 68/.1);--error:#ef4444;--error-bg:rgb(239 68 68/.1);--warning:#c2610c;--warning-bg:rgb(245 158 11/.12)}
|
|
33
|
+
@media (prefers-color-scheme:dark){:root{--bg:#0f0f10;--surface-2:#18181b;--text:#fff;--text-2:#ffffffa4;--text-3:#ffffff87;--border:#ffffff13;--border-strong:#ffffff1a;--muted:#ffffff0b;--action:#fff;--action-text:#0f0f10;--action-hover:#e5e5e5;--emerald:#34d399;--ok-bg:rgb(0 204 68/.16);--error:#ff6b6b;--error-bg:rgb(255 107 107/.14);--warning:#fda05e;--warning-bg:rgb(252 122 55/.15)}.ring{filter:invert(1)}}
|
|
34
|
+
*{box-sizing:border-box}html,body{height:100%}body{margin:0;background:var(--bg);color:var(--text);font:400 14px/20px "Schibsted Grotesk","Helvetica Neue",Arial,sans-serif;-webkit-font-smoothing:antialiased}
|
|
35
|
+
a{color:inherit}code{font:400 13px/20px "Geist Mono",ui-monospace,Menlo,monospace;padding:1px 6px;border:1px solid var(--border-strong);border-radius:6px;overflow-wrap:anywhere}
|
|
36
|
+
.brand{display:inline-flex;align-items:center;gap:8px;font-weight:700;font-size:18px;letter-spacing:-.2px}.ring{display:block}
|
|
37
|
+
h1{margin:0;font:400 28px/36px "Schibsted Grotesk","Helvetica Neue",Arial,sans-serif;letter-spacing:-.21px}p{margin:0;color:var(--text-2)}
|
|
38
|
+
.button{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:100%;height:40px;padding:0 16px;border-radius:12px;border:1px solid transparent;background:var(--action);color:var(--action-text);font-family:inherit;font-weight:500;font-size:14px;line-height:20px;cursor:pointer;text-decoration:none}
|
|
39
|
+
.button:hover{background:var(--action-hover)}.button.outline{background:transparent;color:var(--text);border-color:var(--border-strong)}.button.outline:hover{background:var(--muted)}
|
|
40
|
+
.button:focus-visible{outline:2px solid var(--text);outline-offset:2px}.button .g{flex:none}.row{display:grid;gap:8px}
|
|
41
|
+
.notice{padding:10px 12px;border-radius:12px;font-size:13px;line-height:18px}.notice.error{background:var(--error-bg);color:var(--error)}.notice.warning{background:var(--warning-bg);color:var(--warning)}
|
|
42
|
+
.badge{display:grid;place-items:center;width:72px;height:72px;border-radius:50%}.badge.ok{background:var(--ok-bg);color:var(--emerald)}.badge.error{background:var(--error-bg);color:var(--error)}.badge.warning{background:var(--warning-bg);color:var(--warning)}
|
|
43
|
+
/* the confirmation card: one centered column */
|
|
44
|
+
.center{min-height:100%;display:grid;place-items:center;padding:24px 16px}.card{width:100%;max-width:420px;display:grid;justify-items:center;gap:12px;text-align:center}
|
|
45
|
+
.card .brand{margin-bottom:12px}.card .two{grid-template-columns:1fr 1fr}.code-big{font:600 24px/32px "Geist Mono",ui-monospace,Menlo,monospace;letter-spacing:2px;padding:10px 18px;border:1px solid var(--border-strong);border-radius:12px;background:var(--surface-2)}.card p{max-width:46ch}.card .row{width:100%;margin-top:12px}.card .hint{font-size:13px;color:var(--text-3)}
|
|
46
|
+
/* the sign-in: two columns, the form on the right */
|
|
47
|
+
.split{min-height:100%;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
|
|
48
|
+
.split .intro{display:flex;flex-direction:column;justify-content:space-between;gap:32px;padding:40px 48px;background:var(--surface-2);border-right:1px solid var(--border)}
|
|
49
|
+
.split .intro h2{margin:0 0 12px;font:400 28px/36px "Schibsted Grotesk","Helvetica Neue",Arial,sans-serif;letter-spacing:-.21px;max-width:18ch}.split .intro ul{list-style:none;margin:0;padding:0;display:grid;gap:14px;max-width:44ch}
|
|
50
|
+
.split .intro li{display:grid;grid-template-columns:20px 1fr;gap:10px;color:var(--text-2)}.split .intro li svg{margin-top:2px;color:var(--emerald)}.split .intro li b{display:block;color:var(--text);font-weight:500}
|
|
51
|
+
.split .intro small{color:var(--text-3);font-size:12px}
|
|
52
|
+
.split .side{display:grid;place-items:center;padding:40px 24px}.split form,.split .box{width:100%;max-width:360px;display:grid;gap:16px}.split .box .head{display:grid;gap:6px}
|
|
53
|
+
.split .box .brand-top{display:none}
|
|
54
|
+
@media (max-width:860px){.split{grid-template-columns:1fr}.split .intro{display:none}.split .box .brand-top{display:inline-flex;margin-bottom:8px}}
|
|
55
|
+
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}`;
|
|
56
|
+
|
|
57
|
+
/** The whole document.
|
|
58
|
+
* @param {string} title @param {string} body @param {{ head?: string }} [o] */
|
|
59
|
+
export function document_(title, body, { head = "" } = {}) {
|
|
60
|
+
return `<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(title)} · Omni</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Schibsted+Grotesk:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap">${head}<style>${STYLE}</style></head><body>${body}</body></html>`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** a centered confirmation: the badge, a title and a disclaimer, with an optional button
|
|
64
|
+
* @param {{ kind: "ok" | "error" | "warning", title: string, text: string, hint?: string, action?: { href: string, label: string }, extra?: string }} o
|
|
65
|
+
* `text`, `hint` and `extra` are HTML: whoever calls escapes what came from outside */
|
|
66
|
+
export const confirmation = ({ kind, title, text, hint, action, extra = "" }) =>
|
|
67
|
+
`<main class="center"><div class="card">${brand()}${badge(kind)}<h1>${esc(title)}</h1><p>${text}</p>${extra}${action ? `<div class="row"><a class="button" href="${esc(action.href)}">${esc(action.label)}</a></div>` : ""}${hint ? `<p class="hint">${hint}</p>` : ""}</div></main>`;
|
package/tools/lib/pairing.mjs
CHANGED
|
@@ -77,7 +77,7 @@ export async function finishPairing(url, code, name, d = {}) {
|
|
|
77
77
|
if (!session) throw new Error("o código do pareamento expirou");
|
|
78
78
|
const reg = await call(url, "/runner/register", { body: { name }, token: session }, f);
|
|
79
79
|
if (!reg.ok || !reg.body?.key)
|
|
80
|
-
throw new Error(`o studio não entregou a chave
|
|
80
|
+
throw new Error(`o studio não entregou a chave da máquina (${reg.status}: ${reg.body?.error ?? "?"})`);
|
|
81
81
|
return { key: reg.body.key, name: reg.body.name };
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -121,7 +121,7 @@ export function dueAction(sch, { now, alive, settings }) {
|
|
|
121
121
|
if (!sch.attempts && delay > settings.max_delay_h)
|
|
122
122
|
return {
|
|
123
123
|
action: "missed",
|
|
124
|
-
message: `
|
|
124
|
+
message: `a máquina estava dormindo ou desligada na hora: ${delay.toFixed(1)} h de atraso passa do limite de ${settings.max_delay_h} h (publish.schedule.max_delay_h)`,
|
|
125
125
|
};
|
|
126
126
|
return { action: "run" };
|
|
127
127
|
}
|