acuvo-code 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 +328 -0
- package/ENTERPRISE.md +927 -0
- package/LICENSE +120 -0
- package/README.md +1245 -0
- package/ROADMAP.md +556 -0
- package/bin/acuvo-mcp.mjs +208 -0
- package/bin/acuvo.mjs +3108 -0
- package/lib/acceptance-consent.mjs +168 -0
- package/lib/acceptance.mjs +859 -0
- package/lib/account.mjs +226 -0
- package/lib/acuvo-dir.mjs +72 -0
- package/lib/acuvo-models.mjs +141 -0
- package/lib/apply-patch.mjs +570 -0
- package/lib/ask-user.mjs +173 -0
- package/lib/audit.mjs +530 -0
- package/lib/auto-lease.mjs +174 -0
- package/lib/background.mjs +842 -0
- package/lib/best-of.mjs +334 -0
- package/lib/board.mjs +232 -0
- package/lib/breaker.mjs +93 -0
- package/lib/budget.mjs +1375 -0
- package/lib/builtin-skills.mjs +135 -0
- package/lib/cache-floor.mjs +204 -0
- package/lib/chain.mjs +303 -0
- package/lib/changed-paths.mjs +84 -0
- package/lib/chat.mjs +434 -0
- package/lib/checkpoint.mjs +637 -0
- package/lib/child-lifetime.mjs +71 -0
- package/lib/cli-args.mjs +1255 -0
- package/lib/code-review.mjs +1382 -0
- package/lib/colour.mjs +82 -0
- package/lib/command.mjs +2847 -0
- package/lib/compact.mjs +1151 -0
- package/lib/completion.mjs +515 -0
- package/lib/creative-engines.mjs +779 -0
- package/lib/db-inspect.mjs +1624 -0
- package/lib/delete.mjs +111 -0
- package/lib/design-loop.mjs +570 -0
- package/lib/diff-preview.mjs +1044 -0
- package/lib/doctor.mjs +2139 -0
- package/lib/dropped.mjs +216 -0
- package/lib/edit-diagnostics.mjs +277 -0
- package/lib/edit.mjs +460 -0
- package/lib/env-file.mjs +250 -0
- package/lib/escalate.mjs +702 -0
- package/lib/evaluate.mjs +284 -0
- package/lib/fetch-text.mjs +952 -0
- package/lib/fleet-budget.mjs +256 -0
- package/lib/gh.mjs +1536 -0
- package/lib/git.mjs +1341 -0
- package/lib/github.mjs +261 -0
- package/lib/h2.mjs +194 -0
- package/lib/handoff.mjs +417 -0
- package/lib/hooks.mjs +626 -0
- package/lib/http-probe.mjs +907 -0
- package/lib/image-director.mjs +322 -0
- package/lib/image-edit.mjs +522 -0
- package/lib/imagegen.mjs +998 -0
- package/lib/interrupt.mjs +234 -0
- package/lib/learned.mjs +353 -0
- package/lib/lease-watch.mjs +115 -0
- package/lib/lease.mjs +868 -0
- package/lib/localize.mjs +834 -0
- package/lib/log-tail.mjs +1052 -0
- package/lib/login.mjs +157 -0
- package/lib/lsp.mjs +1613 -0
- package/lib/mcp-consent.mjs +377 -0
- package/lib/mcp-defaults.mjs +780 -0
- package/lib/mcp-server.mjs +1343 -0
- package/lib/mcp.mjs +1263 -0
- package/lib/media.mjs +1283 -0
- package/lib/memory-workspace.mjs +179 -0
- package/lib/model-json.mjs +132 -0
- package/lib/model-tier.mjs +171 -0
- package/lib/model.mjs +1445 -0
- package/lib/parallel.mjs +144 -0
- package/lib/perchance.mjs +210 -0
- package/lib/plan-coherence.mjs +1461 -0
- package/lib/plan-ledger.mjs +981 -0
- package/lib/plan.mjs +461 -0
- package/lib/policy.mjs +783 -0
- package/lib/prefix-order.mjs +38 -0
- package/lib/project-memory.mjs +127 -0
- package/lib/prompt.mjs +109 -0
- package/lib/python.mjs +862 -0
- package/lib/rcfile.mjs +853 -0
- package/lib/read-window.mjs +743 -0
- package/lib/refute-tools.mjs +34 -0
- package/lib/refute.mjs +806 -0
- package/lib/repl-driver.mjs +264 -0
- package/lib/repl.mjs +324 -0
- package/lib/replay.mjs +1218 -0
- package/lib/repo-map.mjs +1101 -0
- package/lib/report.mjs +419 -0
- package/lib/search-rank.mjs +99 -0
- package/lib/search.mjs +659 -0
- package/lib/secret-paths.mjs +54 -0
- package/lib/session.mjs +1017 -0
- package/lib/skills.mjs +703 -0
- package/lib/slash.mjs +356 -0
- package/lib/spawn-argv.mjs +1151 -0
- package/lib/spend.mjs +250 -0
- package/lib/steer.mjs +280 -0
- package/lib/stream.mjs +253 -0
- package/lib/stuck.mjs +712 -0
- package/lib/subagent.mjs +749 -0
- package/lib/terminal-graphics.mjs +171 -0
- package/lib/tool-prefix.mjs +226 -0
- package/lib/tool-shortlist.mjs +162 -0
- package/lib/tools.mjs +2333 -0
- package/lib/tsserver.mjs +423 -0
- package/lib/turn.mjs +5672 -0
- package/lib/untrusted-block.mjs +271 -0
- package/lib/verify-claim.mjs +299 -0
- package/lib/vision.mjs +330 -0
- package/lib/voice-task.mjs +561 -0
- package/lib/warm-provider.mjs +255 -0
- package/lib/websearch.mjs +401 -0
- package/lib/workspace.mjs +928 -0
- package/lib/write-approval.mjs +235 -0
- package/lib/write-many.mjs +162 -0
- package/package.json +62 -0
- package/scripts/bundle.mjs +768 -0
- package/scripts/cache-floor.mjs +176 -0
- package/scripts/machine.mjs +226 -0
- package/scripts/test.mjs +139 -0
- package/skills/accessibility.md +87 -0
- package/skills/acuvo-design-system.md +123 -0
- package/skills/animation.md +84 -0
- package/skills/api-design.md +82 -0
- package/skills/auth-and-sessions.md +78 -0
- package/skills/build-with-a-framework.md +101 -0
- package/skills/colour-and-contrast.md +112 -0
- package/skills/creative-engines.md +81 -0
- package/skills/css-layout.md +85 -0
- package/skills/data-and-charts.md +77 -0
- package/skills/debugging.md +76 -0
- package/skills/designing-by-looking.md +84 -0
- package/skills/error-handling.md +78 -0
- package/skills/forms-and-validation.md +93 -0
- package/skills/nextjs-app-router.md +75 -0
- package/skills/page-composition.md +103 -0
- package/skills/performance.md +77 -0
- package/skills/plan-before-building.md +52 -0
- package/skills/planning-and-delegating.md +72 -0
- package/skills/refactoring.md +70 -0
- package/skills/security-basics.md +76 -0
- package/skills/state-management.md +73 -0
- package/skills/supabase-multitenant.md +72 -0
- package/skills/typescript-strict.md +90 -0
- package/skills/typography.md +135 -0
- package/skills/verify-your-own-work.md +62 -0
- package/skills/web-app-quality.md +62 -0
- package/skills/working-in-the-background.md +64 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ── ⭐ THE FLEET CEILING — "CHEAP" HAS TO BE TRUE IN AGGREGATE, NOT PER WORKER ──
|
|
3
|
+
*
|
|
4
|
+
* The product direction is seven terminals working one repository at once. Every
|
|
5
|
+
* piece of that is measured and working except one thing nobody had checked:
|
|
6
|
+
* `lib/budget.mjs` caps a RUN. Seven runs multiply the spend by seven, so the
|
|
7
|
+
* ceiling a person actually chose ($0.02) silently becomes a different number
|
|
8
|
+
* ($0.14) as soon as they do the thing the product tells them to do.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ That is the same defect as the per-turn-vs-per-run bug fixed on 2026-08-12,
|
|
11
|
+
* scaled by the fleet: a limit enforced at the wrong granularity is not a limit,
|
|
12
|
+
* it is a unit of measurement. And the governor — *tell me the price before it
|
|
13
|
+
* runs and stop at the number I gave you* — is the one differentiator a public
|
|
14
|
+
* company's finance function structurally cannot copy. A fleet is exactly where
|
|
15
|
+
* it has to hold, or it was never the claim we said it was.
|
|
16
|
+
*
|
|
17
|
+
* ── ⭐⭐ THERE IS NO NEW LEDGER HERE, AND THAT IS THE POINT ───────────────────
|
|
18
|
+
*
|
|
19
|
+
* The obvious build is a shared `.acuvo/fleet-budget.json` with a lock around
|
|
20
|
+
* it: a new file, a new write path, a new race between seven processes, and a
|
|
21
|
+
* new way to be wrong about money. None of that is necessary, because the
|
|
22
|
+
* ledger already exists and every run already writes to it:
|
|
23
|
+
*
|
|
24
|
+
* .acuvo/audit/YYYY-MM-DD.jsonl ← appended by EVERY run in this workspace
|
|
25
|
+
* with `run.costUsd`, one JSON line each
|
|
26
|
+
*
|
|
27
|
+
* Seven terminals in one repository are already writing to one audit directory,
|
|
28
|
+
* in day files, with O_APPEND. So the fleet's spend is a QUESTION asked of data
|
|
29
|
+
* that is already on disk, not a second book to keep in sync with the first.
|
|
30
|
+
* `spend.mjs` already sums it — this module only decides what the sum means.
|
|
31
|
+
*
|
|
32
|
+
* ⭐ And the day file makes the window obvious: the fleet ceiling is a spend cap
|
|
33
|
+
* for this workspace for today, across every terminal. That is the number
|
|
34
|
+
* somebody needs before they dare leave seven workers running overnight, and it
|
|
35
|
+
* composes with the per-run ceiling rather than replacing it — one bounds the
|
|
36
|
+
* blast radius of a single mistake, the other bounds the blast radius of the
|
|
37
|
+
* whole fleet.
|
|
38
|
+
*
|
|
39
|
+
* ── ⚠️ WHAT THIS CANNOT SEE, STATED PLAINLY RATHER THAN GLOSSED ─────────────
|
|
40
|
+
*
|
|
41
|
+
* A run appends its audit record when it FINISHES. So at any moment the log
|
|
42
|
+
* knows what the fleet has SPENT, not what it is CURRENTLY spending: six other
|
|
43
|
+
* workers may each be mid-run with money already committed and nothing written
|
|
44
|
+
* down yet.
|
|
45
|
+
*
|
|
46
|
+
* The overshoot that allows is bounded and worth naming, because a bound you can
|
|
47
|
+
* state is a different thing from a hole:
|
|
48
|
+
*
|
|
49
|
+
* worst case = fleetLimit + (other live workers × their own per-run ceiling)
|
|
50
|
+
*
|
|
51
|
+
* With the default $0.02 per-run ceiling and seven terminals that is at most
|
|
52
|
+
* about twelve cents past the line. ⚠️ It is NOT bounded for a worker running
|
|
53
|
+
* `--budget none`, which is precisely why `fleetVerdict` refuses to report a
|
|
54
|
+
* comfortable number when it knows its inputs are incomplete: see `unknown` and
|
|
55
|
+
* `damaged` below.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { readAuditFiles, summariseSpend } from './spend.mjs';
|
|
59
|
+
import { formatUsd, USD_EPSILON, FLEET_STOP_REASONS } from './budget.mjs';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Reasons a fleet check can come back.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ THE STOPS ARE SPREAD IN FROM `budget.mjs`, NOT RETYPED. They have to
|
|
65
|
+
* appear in `BUDGET_REASONS` too — that is how `escalate.mjs` learns a fleet
|
|
66
|
+
* stop is a budget stop — and two hand-maintained copies of the same strings is
|
|
67
|
+
* how a reason ends up named in one place and never emitted from the other.
|
|
68
|
+
* The import direction is fleet → budget only; budget never imports this file.
|
|
69
|
+
*/
|
|
70
|
+
export const FLEET_REASONS = ['ok', 'no-fleet-budget', ...FLEET_STOP_REASONS];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The UTC start of the day a moment falls in — the same boundary `dayFileName`
|
|
74
|
+
* uses, so the window and the files it reads can never disagree.
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ UTC, not local. The audit files are named in UTC; a local-midnight window
|
|
77
|
+
* would ask for "today" and be handed two half-days for anyone east or west of
|
|
78
|
+
* Greenwich, twice reporting a fleet total that is not the one the day file
|
|
79
|
+
* holds. Being consistently wrong about which 24 hours is far better than being
|
|
80
|
+
* inconsistently right.
|
|
81
|
+
*/
|
|
82
|
+
export function startOfUtcDay(now = new Date()) {
|
|
83
|
+
const at = now instanceof Date ? now : new Date(now);
|
|
84
|
+
return new Date(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate()));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* What has this workspace spent since `since`, across every terminal?
|
|
89
|
+
*
|
|
90
|
+
* Thin by design: the reading and the summing already exist and are already
|
|
91
|
+
* tested. `readImpl` is injected for the same reason every other disk touch in
|
|
92
|
+
* this package is — a money decision must be testable without a filesystem.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} root
|
|
95
|
+
* @param {{ since?: Date|null, readImpl?: (root: string) => {name: string, text: string}[] }} [opts]
|
|
96
|
+
*/
|
|
97
|
+
export function readFleetSpend(root, { since = null, readImpl = readAuditFiles } = {}) {
|
|
98
|
+
let files;
|
|
99
|
+
try {
|
|
100
|
+
files = readImpl(root);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
/**
|
|
103
|
+
* ⚠️ AN UNREADABLE LEDGER IS NOT A ZERO. Returning 0 here would make an
|
|
104
|
+
* unreadable audit directory look like a fresh, unspent day and hand the
|
|
105
|
+
* fleet its entire ceiling again — the failure mode where the safety
|
|
106
|
+
* feature pays for the accident. The verdict below refuses instead.
|
|
107
|
+
*/
|
|
108
|
+
return { ok: false, error: `could not read the fleet ledger: ${err?.message ?? err}`, totalUsd: 0, runs: 0, unknown: 0, damaged: 0 };
|
|
109
|
+
}
|
|
110
|
+
const summary = summariseSpend(files, { since });
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
totalUsd: summary.totalUsd,
|
|
114
|
+
runs: summary.runs,
|
|
115
|
+
/** Runs whose cost is genuinely unknown — never counted as zero. */
|
|
116
|
+
unknown: summary.unknown,
|
|
117
|
+
/** Lines that would not parse. Seven appenders can interleave; say so. */
|
|
118
|
+
damaged: summary.damaged,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Does the fleet have room for what this run is about to do?
|
|
124
|
+
*
|
|
125
|
+
* Pure. Everything that touches the disk happened in `readFleetSpend`, so every
|
|
126
|
+
* branch below is reachable from a test with three numbers and no temp
|
|
127
|
+
* directory.
|
|
128
|
+
*
|
|
129
|
+
* @param {object} args
|
|
130
|
+
* @param {number|null} args.fleetLimitUsd the ceiling across all terminals. null = off.
|
|
131
|
+
* @param {object} args.fleetSpend what `readFleetSpend` returned.
|
|
132
|
+
* @param {number} args.thisRunUsd what THIS run has spent so far (not yet in the log).
|
|
133
|
+
* @param {number} args.projectedUsd what the next round is projected to cost.
|
|
134
|
+
*/
|
|
135
|
+
export function fleetVerdict({ fleetLimitUsd = null, fleetSpend, thisRunUsd = 0, projectedUsd = 0 } = {}) {
|
|
136
|
+
if (fleetLimitUsd === null || fleetLimitUsd === undefined) {
|
|
137
|
+
return { ok: true, reason: 'no-fleet-budget', message: '', fleetSpentUsd: 0, fleetRemainingUsd: Infinity };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!fleetSpend || fleetSpend.ok !== true) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
reason: 'fleet-limit-reached',
|
|
144
|
+
message:
|
|
145
|
+
`the fleet ceiling of ${formatUsd(fleetLimitUsd)} cannot be enforced because the shared ledger could not be read `
|
|
146
|
+
+ `(${fleetSpend?.error ?? 'no reason given'}). Refusing rather than assuming nothing has been spent — `
|
|
147
|
+
+ 'fix the workspace .acuvo/audit directory, or drop --fleet-budget to run without a fleet ceiling.',
|
|
148
|
+
fleetSpentUsd: 0,
|
|
149
|
+
fleetRemainingUsd: 0,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* ⚠️ THIS RUN'S OWN SPEND IS ADDED SEPARATELY, AND IT MUST BE. Its audit
|
|
155
|
+
* record is not written until the run ends, so it is absent from the log by
|
|
156
|
+
* construction — leaving it out would let a single long run spend the fleet's
|
|
157
|
+
* whole ceiling while the ledger insisted the fleet had spent nothing.
|
|
158
|
+
*/
|
|
159
|
+
const fleetSpentUsd = fleetSpend.totalUsd + thisRunUsd;
|
|
160
|
+
const fleetRemainingUsd = fleetLimitUsd - fleetSpentUsd;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* ⭐ THE HONESTY CLAUSE. `unknown` runs had no reported price and were never
|
|
164
|
+
* summed as zero; `damaged` lines could not be parsed at all. Either means the
|
|
165
|
+
* true total is HIGHER than the one being enforced, so the number gets a floor
|
|
166
|
+
* marker rather than being presented as exact. A budget that quietly
|
|
167
|
+
* undercounts is a budget that overspends.
|
|
168
|
+
*/
|
|
169
|
+
const incomplete = fleetSpend.unknown > 0 || fleetSpend.damaged > 0;
|
|
170
|
+
const floorNote = incomplete
|
|
171
|
+
? ` (at least — ${fleetSpend.unknown} run${fleetSpend.unknown === 1 ? '' : 's'} reported no price`
|
|
172
|
+
+ `${fleetSpend.damaged > 0 ? ` and ${fleetSpend.damaged} ledger line${fleetSpend.damaged === 1 ? '' : 's'} could not be read` : ''}, `
|
|
173
|
+
+ 'so the real total is higher than this)'
|
|
174
|
+
: '';
|
|
175
|
+
|
|
176
|
+
/** Every refusal names the flag that lifts it. */
|
|
177
|
+
const wayOut = ' Raise it with --fleet-budget, or drop the flag to bound each terminal only.';
|
|
178
|
+
|
|
179
|
+
if (fleetRemainingUsd <= USD_EPSILON) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
reason: 'fleet-limit-reached',
|
|
183
|
+
message:
|
|
184
|
+
`the fleet has spent ${formatUsd(fleetSpentUsd)}${floorNote} of its ${formatUsd(fleetLimitUsd)} ceiling `
|
|
185
|
+
+ `across ${fleetSpend.runs} run${fleetSpend.runs === 1 ? '' : 's'} in this workspace today. `
|
|
186
|
+
+ `This terminal has spent ${formatUsd(thisRunUsd)} of that.${wayOut}`,
|
|
187
|
+
fleetSpentUsd,
|
|
188
|
+
fleetRemainingUsd: Math.max(0, fleetRemainingUsd),
|
|
189
|
+
incomplete,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (fleetSpentUsd + projectedUsd > fleetLimitUsd + USD_EPSILON) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
reason: 'fleet-would-exceed',
|
|
197
|
+
message:
|
|
198
|
+
`stopping on the fleet ceiling: ${formatUsd(fleetSpentUsd)}${floorNote} of ${formatUsd(fleetLimitUsd)} is spent `
|
|
199
|
+
+ `across every terminal in this workspace today, and the next round is projected at ~${formatUsd(projectedUsd)}, `
|
|
200
|
+
+ `which would cross the line.${wayOut}`,
|
|
201
|
+
fleetSpentUsd,
|
|
202
|
+
fleetRemainingUsd,
|
|
203
|
+
incomplete,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
ok: true,
|
|
209
|
+
reason: 'ok',
|
|
210
|
+
message: `${formatUsd(fleetRemainingUsd)} left of the ${formatUsd(fleetLimitUsd)} fleet ceiling${floorNote}`,
|
|
211
|
+
fleetSpentUsd,
|
|
212
|
+
fleetRemainingUsd,
|
|
213
|
+
incomplete,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The one line a caller needs: turn a `--fleet-budget` value into the gate
|
|
219
|
+
* `createBudget` accepts, or `null` when the flag was never given.
|
|
220
|
+
*
|
|
221
|
+
* ⭐ RETURNING `null` WHEN OFF IS THE WHOLE ERGONOMIC. `createBudget` guards on
|
|
222
|
+
* `if (fleetGate)`, so "no fleet ceiling" costs exactly one falsy check and not
|
|
223
|
+
* a single disk read — the feature is invisible to everyone who has not asked
|
|
224
|
+
* for it, which is the bar for adding anything to a path that spends money.
|
|
225
|
+
*
|
|
226
|
+
* ⚠️ THE LEDGER IS RE-READ ON EVERY CALL, DELIBERATELY. Six other terminals
|
|
227
|
+
* finish work while this one is thinking; a total captured once at start-up
|
|
228
|
+
* would be stale by the second round and would drift further the longer and
|
|
229
|
+
* more expensive the run got — worst exactly when it matters most. A directory
|
|
230
|
+
* read per round is nothing beside the model call it is gating.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} root
|
|
233
|
+
* @param {{ fleetLimitUsd?: number|null, now?: () => Date, readImpl?: Function }} [opts]
|
|
234
|
+
*/
|
|
235
|
+
export function createFleetGate(root, { fleetLimitUsd = null, since = null, now = () => new Date(), readImpl } = {}) {
|
|
236
|
+
if (fleetLimitUsd === null || fleetLimitUsd === undefined) return null;
|
|
237
|
+
return ({ projectedUsd = 0, thisRunUsd = 0 } = {}) => fleetVerdict({
|
|
238
|
+
fleetLimitUsd,
|
|
239
|
+
/**
|
|
240
|
+
* ── ⭐ THE WINDOW, AND WHY IT MATTERS FOR ANYTHING UNATTENDED ────────────
|
|
241
|
+
*
|
|
242
|
+
* The default is today, because the audit log is written in UTC day files
|
|
243
|
+
* and a window that disagrees with the file it reads is a bug waiting to be
|
|
244
|
+
* argued about. That is the right default for a person at a keyboard.
|
|
245
|
+
*
|
|
246
|
+
* ⚠️ It is the WRONG one for a cron. Point a schedule at this and every
|
|
247
|
+
* fire is a new run with a fresh per-run ceiling, so the number somebody
|
|
248
|
+
* chose stops being a limit and becomes a rate — $0.02 an hour is $14 a
|
|
249
|
+
* month, and nobody typed $14. `--budget-window 7d` makes the ceiling mean
|
|
250
|
+
* what an unattended operator actually needs it to mean: a total.
|
|
251
|
+
*/
|
|
252
|
+
fleetSpend: readFleetSpend(root, { since: since ?? startOfUtcDay(now()), readImpl }),
|
|
253
|
+
thisRunUsd,
|
|
254
|
+
projectedUsd,
|
|
255
|
+
});
|
|
256
|
+
}
|