@clearlysh/beehaven 0.8.16
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/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/__tests__/_cli-switch.js +105 -0
- package/dist/__tests__/agent-wrap-probe.js +136 -0
- package/dist/agent-attribution.js +192 -0
- package/dist/agent-identity-daemon.js +135 -0
- package/dist/agent-onboard.js +156 -0
- package/dist/agent-scope.js +79 -0
- package/dist/agent-sessions.js +370 -0
- package/dist/agent-state.js +482 -0
- package/dist/app-manifest.js +221 -0
- package/dist/app-signer.js +54 -0
- package/dist/app-signing.js +127 -0
- package/dist/audio-play.js +98 -0
- package/dist/avatar-protocol.js +25 -0
- package/dist/avatar-route.js +79 -0
- package/dist/bee/cloud-proxy.js +130 -0
- package/dist/blocked-actions.js +120 -0
- package/dist/build-channel.js +101 -0
- package/dist/buildings/daemon-workspace.js +475 -0
- package/dist/claude-bridge.js +310 -0
- package/dist/claude-credential-store.js +633 -0
- package/dist/clearly-sync.js +518 -0
- package/dist/clearly.js +616 -0
- package/dist/cli-state.js +334 -0
- package/dist/cli.js +6973 -0
- package/dist/commands/agent-skills.js +302 -0
- package/dist/commands/agent.js +1310 -0
- package/dist/commands/app.js +1123 -0
- package/dist/commands/auth.js +171 -0
- package/dist/commands/building-shell.js +392 -0
- package/dist/commands/canvas.js +932 -0
- package/dist/commands/claude.js +218 -0
- package/dist/commands/compliance.js +387 -0
- package/dist/commands/compose.js +507 -0
- package/dist/commands/composition.js +95 -0
- package/dist/commands/connect-session.js +409 -0
- package/dist/commands/conversations.js +160 -0
- package/dist/commands/costs.js +443 -0
- package/dist/commands/d1.js +253 -0
- package/dist/commands/daemon-cmd.js +162 -0
- package/dist/commands/deploy.js +164 -0
- package/dist/commands/design.js +617 -0
- package/dist/commands/dev-generate.js +125 -0
- package/dist/commands/dev.js +109 -0
- package/dist/commands/devices.js +135 -0
- package/dist/commands/explain-router.js +24 -0
- package/dist/commands/explain.js +882 -0
- package/dist/commands/explore.js +264 -0
- package/dist/commands/help.js +2418 -0
- package/dist/commands/init.js +408 -0
- package/dist/commands/install.js +129 -0
- package/dist/commands/library.js +229 -0
- package/dist/commands/marketplace.js +138 -0
- package/dist/commands/mcp-install.js +476 -0
- package/dist/commands/mention.js +257 -0
- package/dist/commands/omni.js +529 -0
- package/dist/commands/secrets.js +323 -0
- package/dist/commands/self-update.js +206 -0
- package/dist/commands/sheet.js +264 -0
- package/dist/commands/shell-output.js +309 -0
- package/dist/commands/shell.js +1048 -0
- package/dist/commands/shop.js +368 -0
- package/dist/commands/skills.js +311 -0
- package/dist/commands/slides.js +418 -0
- package/dist/commands/swarm.js +341 -0
- package/dist/commands/ticket.js +1301 -0
- package/dist/commands/tool.js +312 -0
- package/dist/commands/trash.js +106 -0
- package/dist/commands/treehouse.js +120 -0
- package/dist/commands/user.js +240 -0
- package/dist/commands/voice-tts.js +319 -0
- package/dist/commands/voice.js +166 -0
- package/dist/commands/watch-git.js +401 -0
- package/dist/commands/webhook.js +159 -0
- package/dist/commands/workspace.js +3354 -0
- package/dist/composition-repos.js +91 -0
- package/dist/daemon-avatar.js +130 -0
- package/dist/daemon-claude-code-auto-install.js +52 -0
- package/dist/daemon-client.js +363 -0
- package/dist/daemon-codex.js +349 -0
- package/dist/daemon-entry.js +101 -0
- package/dist/daemon-fetch-allowlist.js +194 -0
- package/dist/daemon-gemini.js +224 -0
- package/dist/daemon-mcp-clearly.js +353 -0
- package/dist/daemon-mcp-sandbox.js +127 -0
- package/dist/daemon-omni-company.js +338 -0
- package/dist/daemon-omni-hooks.js +375 -0
- package/dist/daemon-omni-identity.js +229 -0
- package/dist/daemon-omni-pipeline.js +251 -0
- package/dist/daemon-omni-policies.js +346 -0
- package/dist/daemon-omni-principal.js +580 -0
- package/dist/daemon-omni-tool-gate.js +116 -0
- package/dist/daemon-omni.js +1442 -0
- package/dist/daemon-path-transform.js +120 -0
- package/dist/daemon-write-sandbox.js +191 -0
- package/dist/daemon.js +4471 -0
- package/dist/dashboard.js +846 -0
- package/dist/dep-installer.js +286 -0
- package/dist/dev-mode.js +67 -0
- package/dist/doc-indexer.js +169 -0
- package/dist/e2e-crypto.js +94 -0
- package/dist/feature-flags.js +154 -0
- package/dist/file-browser.js +88 -0
- package/dist/first-party-hives.js +211 -0
- package/dist/folder-sync.js +226 -0
- package/dist/health.js +56 -0
- package/dist/ipc-auth.js +299 -0
- package/dist/ipc-client.js +495 -0
- package/dist/ipc-server.js +684 -0
- package/dist/keychain.js +224 -0
- package/dist/lib/admin-client.js +73 -0
- package/dist/lib/mobile-build-parser.js +156 -0
- package/dist/local-app-manager.js +660 -0
- package/dist/log-redaction.js +89 -0
- package/dist/logger.js +108 -0
- package/dist/manifest.js +143 -0
- package/dist/marketplace.js +793 -0
- package/dist/min-version-copy.js +51 -0
- package/dist/min-version.js +214 -0
- package/dist/public-surface.js +23 -0
- package/dist/relay-presets.js +89 -0
- package/dist/relay.js +2723 -0
- package/dist/repl.js +619 -0
- package/dist/setup-relay.js +363 -0
- package/dist/setup-service.js +536 -0
- package/dist/shared/bee-memory/index.js +6 -0
- package/dist/shared/bee-memory/types.js +682 -0
- package/dist/shared/claude-code/daemon-types.js +112 -0
- package/dist/shared/claude-code/index.js +2 -0
- package/dist/shared/claude-code/types.js +1 -0
- package/dist/shared/status/schema.js +133 -0
- package/dist/shell-session.js +198 -0
- package/dist/speak-lock.js +97 -0
- package/dist/spinner.js +57 -0
- package/dist/telemetry.js +166 -0
- package/dist/terminal/block-parser.js +207 -0
- package/dist/terminal/shell-integration.js +108 -0
- package/dist/transcript-tailer.js +215 -0
- package/dist/tts-qwen3.js +547 -0
- package/dist/types.js +4 -0
- package/dist/utm.js +38 -0
- package/dist/verb-surface.js +331 -0
- package/dist/version-utils.js +85 -0
- package/dist/watcher.js +207 -0
- package/package.json +88 -0
- package/python/qwen3_tts.py +230 -0
- package/scripts/stamp-build-channel.mjs +115 -0
- package/scripts/sync-installed.mjs +159 -0
- package/scripts/sync-skills.mjs +112 -0
- package/skills/README.md +24 -0
- package/skills/agent-doodle-space/SKILL.md +118 -0
- package/skills/agent-home/SKILL.md +98 -0
- package/skills/beehaven-cli/SKILL.md +144 -0
- package/skills/claude-shopper/README.md +90 -0
- package/skills/claude-shopper/SKILL.md +151 -0
- package/skills/clearly-agent/SKILL.md +183 -0
- package/skills/clearly-canvas/SKILL.md +222 -0
- package/skills/clearly-canvas/authoring.md +160 -0
- package/skills/clearly-canvas/communications.md +32 -0
- package/skills/clearly-canvas/composition-patterns.md +84 -0
- package/skills/clearly-canvas/example.mjs +78 -0
- package/skills/clearly-canvas/project-plans.md +156 -0
- package/skills/clearly-canvas/review.mjs +68 -0
- package/skills/clearly-workspace/SKILL.md +79 -0
- package/skills/clearly-workspace/survey.mjs +101 -0
- package/skills/codebase-map/SKILL.md +142 -0
- package/skills/design-craft/SKILL.md +300 -0
- package/skills/index.json +79 -0
- package/skills/layout-systems/SKILL.md +184 -0
- package/skills/pair-on-canvas/SKILL.md +92 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* beehaven costs — Cloudflare cost report
|
|
3
|
+
*
|
|
4
|
+
* Queries CF GraphQL Analytics API and estimates monthly costs.
|
|
5
|
+
* Uses wrangler OAuth token for auth.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { color } from '../spinner.js';
|
|
11
|
+
// CF Pricing (Workers Paid — $5/mo base)
|
|
12
|
+
const PRICING = {
|
|
13
|
+
workerRequests: { included: 10_000_000, perMillion: 0.3 },
|
|
14
|
+
doRequests: { included: 1_000_000, perMillion: 0.15 },
|
|
15
|
+
doDurationGBs: { included: 400_000, perGBs: 12.5 },
|
|
16
|
+
r2StorageGB: { included: 10, perGB: 0.015 },
|
|
17
|
+
r2ClassA: { included: 1_000_000, perMillion: 4.5 },
|
|
18
|
+
r2ClassB: { included: 10_000_000, perMillion: 0.36 },
|
|
19
|
+
baseFee: 5.0,
|
|
20
|
+
};
|
|
21
|
+
function findWranglerToken() {
|
|
22
|
+
// macOS
|
|
23
|
+
const macPath = join(homedir(), 'Library/Preferences/.wrangler/config/default.toml');
|
|
24
|
+
// Linux/Windows
|
|
25
|
+
const linuxPath = join(homedir(), '.wrangler/config/default.toml');
|
|
26
|
+
const configPath = existsSync(macPath) ? macPath : existsSync(linuxPath) ? linuxPath : null;
|
|
27
|
+
if (!configPath)
|
|
28
|
+
return null;
|
|
29
|
+
const toml = readFileSync(configPath, 'utf-8');
|
|
30
|
+
const match = toml.match(/oauth_token\s*=\s*"([^"]+)"/);
|
|
31
|
+
return match?.[1] ?? null;
|
|
32
|
+
}
|
|
33
|
+
async function getAccountId(token) {
|
|
34
|
+
const res = await fetch('https://api.cloudflare.com/client/v4/accounts?page=1&per_page=1', {
|
|
35
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
36
|
+
});
|
|
37
|
+
const json = (await res.json());
|
|
38
|
+
if (!json.result?.[0]?.id)
|
|
39
|
+
throw new Error('Could not determine account ID. Run: wrangler login');
|
|
40
|
+
return json.result[0].id;
|
|
41
|
+
}
|
|
42
|
+
async function gql(token, accountId, query) {
|
|
43
|
+
const res = await fetch('https://api.cloudflare.com/client/v4/graphql', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ query: query.replace(/\$ACCOUNT/g, accountId) }),
|
|
47
|
+
});
|
|
48
|
+
const json = (await res.json());
|
|
49
|
+
if (json.errors?.length)
|
|
50
|
+
throw new Error(json.errors.map((e) => e.message).join('; '));
|
|
51
|
+
return json.data.viewer.accounts[0];
|
|
52
|
+
}
|
|
53
|
+
function overage(usage, tier) {
|
|
54
|
+
const over = Math.max(0, usage - tier.included);
|
|
55
|
+
if (tier.perMillion)
|
|
56
|
+
return (over / 1_000_000) * tier.perMillion;
|
|
57
|
+
if (tier.perGB)
|
|
58
|
+
return over * tier.perGB;
|
|
59
|
+
if (tier.perGBs)
|
|
60
|
+
return over * tier.perGBs;
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
function fmt(n) {
|
|
64
|
+
if (n >= 1_000_000)
|
|
65
|
+
return (n / 1_000_000).toFixed(2) + 'M';
|
|
66
|
+
if (n >= 1_000)
|
|
67
|
+
return (n / 1_000).toFixed(1) + 'K';
|
|
68
|
+
return n.toFixed(0);
|
|
69
|
+
}
|
|
70
|
+
function fmtBytes(bytes) {
|
|
71
|
+
if (bytes >= 1_073_741_824)
|
|
72
|
+
return (bytes / 1_073_741_824).toFixed(2) + ' GB';
|
|
73
|
+
if (bytes >= 1_048_576)
|
|
74
|
+
return (bytes / 1_048_576).toFixed(1) + ' MB';
|
|
75
|
+
if (bytes >= 1_024)
|
|
76
|
+
return (bytes / 1_024).toFixed(1) + ' KB';
|
|
77
|
+
return bytes + ' B';
|
|
78
|
+
}
|
|
79
|
+
function fmtUs(us) {
|
|
80
|
+
if (us < 1000)
|
|
81
|
+
return us + 'µs';
|
|
82
|
+
if (us < 1_000_000)
|
|
83
|
+
return (us / 1000).toFixed(1) + 'ms';
|
|
84
|
+
return (us / 1_000_000).toFixed(1) + 's';
|
|
85
|
+
}
|
|
86
|
+
function fmtDollars(n) {
|
|
87
|
+
return n < 0.01 && n > 0 ? '<$0.01' : '$' + n.toFixed(2);
|
|
88
|
+
}
|
|
89
|
+
function pad(s, len, right = false) {
|
|
90
|
+
return right ? s.padStart(len) : s.padEnd(len);
|
|
91
|
+
}
|
|
92
|
+
function table(headers, rows, aligns) {
|
|
93
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || '').length)));
|
|
94
|
+
const a = aligns || headers.map(() => false);
|
|
95
|
+
const sep = widths.map((w) => '─'.repeat(w + 2)).join('┼');
|
|
96
|
+
const line = (cells) => cells.map((c, i) => ' ' + pad(c, widths[i], a[i]) + ' ').join('│');
|
|
97
|
+
return [line(headers), sep, ...rows.map((r) => line(r))].join('\n');
|
|
98
|
+
}
|
|
99
|
+
export async function costs(opts = {}) {
|
|
100
|
+
const days = opts.days ?? 30;
|
|
101
|
+
// ── Per-action / per-conversation modes — read from the connected DO
|
|
102
|
+
// instead of Cloudflare GraphQL. These dispatch to a convention
|
|
103
|
+
// admin action the app implements against ActionCostLedger.
|
|
104
|
+
if (opts.perAction || opts.perConversation) {
|
|
105
|
+
return perCallReport(days, opts);
|
|
106
|
+
}
|
|
107
|
+
const token = findWranglerToken();
|
|
108
|
+
if (!token) {
|
|
109
|
+
console.error(color.red('✗') + ' Wrangler not authenticated. Run: wrangler login');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const accountId = await getAccountId(token);
|
|
113
|
+
const now = new Date();
|
|
114
|
+
const from = new Date(now.getTime() - days * 86400_000);
|
|
115
|
+
const dateFrom = from.toISOString().split('.')[0] + 'Z';
|
|
116
|
+
const dateTo = now.toISOString().split('.')[0] + 'Z';
|
|
117
|
+
const proj = 30 / days;
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(` ${color.bold('Cloudflare Cost Report')}`);
|
|
120
|
+
console.log(` ${from.toISOString().split('T')[0]} → ${now.toISOString().split('T')[0]} (${days} days)`);
|
|
121
|
+
// Fetch all analytics in parallel
|
|
122
|
+
const [workers, dos, r2Ops, r2Storage] = await Promise.all([
|
|
123
|
+
gql(token, accountId, `{
|
|
124
|
+
viewer { accounts(filter: {accountTag: "$ACCOUNT"}) {
|
|
125
|
+
workersInvocationsAdaptive(limit: 20, filter: {
|
|
126
|
+
datetime_geq: "${dateFrom}", datetime_leq: "${dateTo}"
|
|
127
|
+
}, orderBy: [sum_requests_DESC]) {
|
|
128
|
+
sum { requests subrequests responseBodySize }
|
|
129
|
+
quantiles { cpuTimeP50 cpuTimeP99 }
|
|
130
|
+
dimensions { scriptName status }
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
}`),
|
|
134
|
+
gql(token, accountId, `{
|
|
135
|
+
viewer { accounts(filter: {accountTag: "$ACCOUNT"}) {
|
|
136
|
+
durableObjectsInvocationsAdaptiveGroups(limit: 20, filter: {
|
|
137
|
+
datetime_geq: "${dateFrom}", datetime_leq: "${dateTo}"
|
|
138
|
+
}, orderBy: [sum_requests_DESC]) {
|
|
139
|
+
sum { requests wallTime }
|
|
140
|
+
quantiles { wallTimeP50 wallTimeP99 }
|
|
141
|
+
dimensions { scriptName }
|
|
142
|
+
}
|
|
143
|
+
}}
|
|
144
|
+
}`),
|
|
145
|
+
gql(token, accountId, `{
|
|
146
|
+
viewer { accounts(filter: {accountTag: "$ACCOUNT"}) {
|
|
147
|
+
r2OperationsAdaptiveGroups(limit: 30, filter: {
|
|
148
|
+
datetime_geq: "${dateFrom}", datetime_leq: "${dateTo}"
|
|
149
|
+
}, orderBy: [sum_requests_DESC]) {
|
|
150
|
+
sum { requests responseObjectSize }
|
|
151
|
+
dimensions { bucketName actionType }
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
}`),
|
|
155
|
+
gql(token, accountId, `{
|
|
156
|
+
viewer { accounts(filter: {accountTag: "$ACCOUNT"}) {
|
|
157
|
+
r2StorageAdaptiveGroups(limit: 10, filter: {
|
|
158
|
+
datetime_geq: "${dateTo.split('T')[0]}T00:00:00Z",
|
|
159
|
+
datetime_leq: "${dateTo}"
|
|
160
|
+
}) {
|
|
161
|
+
max { objectCount payloadSize metadataSize }
|
|
162
|
+
dimensions { bucketName }
|
|
163
|
+
}
|
|
164
|
+
}}
|
|
165
|
+
}`),
|
|
166
|
+
]);
|
|
167
|
+
// ── Workers ─────────────────────────────────────────
|
|
168
|
+
const byScript = new Map();
|
|
169
|
+
for (const r of workers.workersInvocationsAdaptive || []) {
|
|
170
|
+
const name = r.dimensions.scriptName;
|
|
171
|
+
const prev = byScript.get(name) || {
|
|
172
|
+
requests: 0,
|
|
173
|
+
subrequests: 0,
|
|
174
|
+
cpuP50: 0,
|
|
175
|
+
cpuP99: 0,
|
|
176
|
+
bytes: 0,
|
|
177
|
+
};
|
|
178
|
+
prev.requests += r.sum.requests;
|
|
179
|
+
prev.subrequests += r.sum.subrequests;
|
|
180
|
+
prev.cpuP50 = Math.max(prev.cpuP50, r.quantiles.cpuTimeP50);
|
|
181
|
+
prev.cpuP99 = Math.max(prev.cpuP99, r.quantiles.cpuTimeP99);
|
|
182
|
+
prev.bytes += r.sum.responseBodySize;
|
|
183
|
+
byScript.set(name, prev);
|
|
184
|
+
}
|
|
185
|
+
const totalWorkerReqs = [...byScript.values()].reduce((s, v) => s + v.requests, 0);
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(' ' + color.bold('Workers'));
|
|
188
|
+
console.log(' ' +
|
|
189
|
+
table(['Script', 'Requests', 'Subreqs', 'CPU p50', 'CPU p99', 'Egress'], [...byScript.entries()]
|
|
190
|
+
.sort((a, b) => b[1].requests - a[1].requests)
|
|
191
|
+
.map(([name, v]) => [
|
|
192
|
+
name === '__unknown__' ? '(deleted)' : name,
|
|
193
|
+
fmt(v.requests),
|
|
194
|
+
fmt(v.subrequests),
|
|
195
|
+
fmtUs(v.cpuP50),
|
|
196
|
+
fmtUs(v.cpuP99),
|
|
197
|
+
fmtBytes(v.bytes),
|
|
198
|
+
]), [false, true, true, true, true, true])
|
|
199
|
+
.split('\n')
|
|
200
|
+
.join('\n '));
|
|
201
|
+
// ── Durable Objects ─────────────────────────────────
|
|
202
|
+
const doRows = (dos.durableObjectsInvocationsAdaptiveGroups || []).map((r) => ({
|
|
203
|
+
name: r.dimensions.scriptName,
|
|
204
|
+
requests: r.sum.requests,
|
|
205
|
+
wallTimeUs: r.sum.wallTime,
|
|
206
|
+
p50Us: r.quantiles.wallTimeP50,
|
|
207
|
+
p99Us: r.quantiles.wallTimeP99,
|
|
208
|
+
}));
|
|
209
|
+
const totalDoReqs = doRows.reduce((s, r) => s + r.requests, 0);
|
|
210
|
+
// Hibernatable DOs: estimate active time from requests × p50 (not raw wallTime which includes WS idle)
|
|
211
|
+
const estActiveUs = doRows.reduce((s, r) => s + r.requests * r.p50Us, 0);
|
|
212
|
+
const estActiveSec = estActiveUs / 1_000_000;
|
|
213
|
+
const doGBs = estActiveSec * 0.128; // 128MB minimum per DO
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(' ' + color.bold('Durable Objects') + color.dim(' (hibernatable — billing excludes WS idle)'));
|
|
216
|
+
console.log(' ' +
|
|
217
|
+
table(['Script', 'Requests', 'p50', 'p99', 'Est. Active'], doRows
|
|
218
|
+
.sort((a, b) => b.requests - a.requests)
|
|
219
|
+
.map((r) => {
|
|
220
|
+
const sec = (r.requests * r.p50Us) / 1_000_000;
|
|
221
|
+
return [
|
|
222
|
+
r.name,
|
|
223
|
+
fmt(r.requests),
|
|
224
|
+
fmtUs(r.p50Us),
|
|
225
|
+
fmtUs(r.p99Us),
|
|
226
|
+
sec < 60 ? sec.toFixed(1) + 's' : (sec / 60).toFixed(1) + 'min',
|
|
227
|
+
];
|
|
228
|
+
}), [false, true, true, true, true])
|
|
229
|
+
.split('\n')
|
|
230
|
+
.join('\n '));
|
|
231
|
+
// ── R2 ──────────────────────────────────────────────
|
|
232
|
+
const classAOps = new Set([
|
|
233
|
+
'PutObject',
|
|
234
|
+
'ListObjects',
|
|
235
|
+
'DeleteObject',
|
|
236
|
+
'DeleteObjects',
|
|
237
|
+
'PutBucket',
|
|
238
|
+
'ListMultipartUploads',
|
|
239
|
+
'ListBuckets',
|
|
240
|
+
]);
|
|
241
|
+
let totalClassA = 0, totalClassB = 0;
|
|
242
|
+
const r2Buckets = new Map();
|
|
243
|
+
for (const r of r2Ops.r2OperationsAdaptiveGroups || []) {
|
|
244
|
+
const bucket = r.dimensions.bucketName || '(global)';
|
|
245
|
+
const prev = r2Buckets.get(bucket) || { classA: 0, classB: 0, egress: 0 };
|
|
246
|
+
if (classAOps.has(r.dimensions.actionType)) {
|
|
247
|
+
prev.classA += r.sum.requests;
|
|
248
|
+
totalClassA += r.sum.requests;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
prev.classB += r.sum.requests;
|
|
252
|
+
totalClassB += r.sum.requests;
|
|
253
|
+
}
|
|
254
|
+
prev.egress += r.sum.responseObjectSize || 0;
|
|
255
|
+
r2Buckets.set(bucket, prev);
|
|
256
|
+
}
|
|
257
|
+
let totalR2Bytes = 0;
|
|
258
|
+
const r2Sizes = new Map();
|
|
259
|
+
for (const r of r2Storage.r2StorageAdaptiveGroups || []) {
|
|
260
|
+
const bytes = r.max.payloadSize + r.max.metadataSize;
|
|
261
|
+
totalR2Bytes += bytes;
|
|
262
|
+
r2Sizes.set(r.dimensions.bucketName, { objects: r.max.objectCount, bytes });
|
|
263
|
+
}
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(' ' + color.bold('R2 Storage'));
|
|
266
|
+
console.log(' ' +
|
|
267
|
+
table(['Bucket', 'Objects', 'Size', 'Writes', 'Reads', 'Egress'], [...r2Sizes.entries()]
|
|
268
|
+
.sort((a, b) => b[1].bytes - a[1].bytes)
|
|
269
|
+
.map(([name, s]) => {
|
|
270
|
+
const ops = r2Buckets.get(name) || { classA: 0, classB: 0, egress: 0 };
|
|
271
|
+
return [
|
|
272
|
+
name,
|
|
273
|
+
fmt(s.objects),
|
|
274
|
+
fmtBytes(s.bytes),
|
|
275
|
+
fmt(ops.classA),
|
|
276
|
+
fmt(ops.classB),
|
|
277
|
+
fmtBytes(ops.egress),
|
|
278
|
+
];
|
|
279
|
+
}), [false, true, true, true, true, true])
|
|
280
|
+
.split('\n')
|
|
281
|
+
.join('\n '));
|
|
282
|
+
// ── Cost Summary ────────────────────────────────────
|
|
283
|
+
const wCost = overage(totalWorkerReqs * proj, PRICING.workerRequests);
|
|
284
|
+
const doReqCost = overage(totalDoReqs * proj, PRICING.doRequests);
|
|
285
|
+
const doDurCost = overage(doGBs * proj, PRICING.doDurationGBs);
|
|
286
|
+
const r2sCost = overage(totalR2Bytes / 1_073_741_824, PRICING.r2StorageGB);
|
|
287
|
+
const r2aCost = overage(totalClassA * proj, PRICING.r2ClassA);
|
|
288
|
+
const r2bCost = overage(totalClassB * proj, PRICING.r2ClassB);
|
|
289
|
+
const total = PRICING.baseFee + wCost + doReqCost + doDurCost + r2sCost + r2aCost + r2bCost;
|
|
290
|
+
console.log('');
|
|
291
|
+
console.log(' ' + color.bold('Estimated Monthly Cost'));
|
|
292
|
+
console.log(' ' +
|
|
293
|
+
table(['Line Item', 'Usage/mo', 'Free Tier', 'Cost'], [
|
|
294
|
+
['Workers Paid (base)', '—', '—', fmtDollars(PRICING.baseFee)],
|
|
295
|
+
[
|
|
296
|
+
'Worker Requests',
|
|
297
|
+
fmt(totalWorkerReqs * proj),
|
|
298
|
+
fmt(PRICING.workerRequests.included),
|
|
299
|
+
fmtDollars(wCost),
|
|
300
|
+
],
|
|
301
|
+
[
|
|
302
|
+
'DO Requests',
|
|
303
|
+
fmt(totalDoReqs * proj),
|
|
304
|
+
fmt(PRICING.doRequests.included),
|
|
305
|
+
fmtDollars(doReqCost),
|
|
306
|
+
],
|
|
307
|
+
[
|
|
308
|
+
'DO Duration',
|
|
309
|
+
(doGBs * proj).toFixed(1) + ' GB-s',
|
|
310
|
+
fmt(PRICING.doDurationGBs.included) + ' GB-s',
|
|
311
|
+
fmtDollars(doDurCost),
|
|
312
|
+
],
|
|
313
|
+
['R2 Storage', fmtBytes(totalR2Bytes), '10 GB', fmtDollars(r2sCost)],
|
|
314
|
+
[
|
|
315
|
+
'R2 Class A',
|
|
316
|
+
fmt(totalClassA * proj),
|
|
317
|
+
fmt(PRICING.r2ClassA.included),
|
|
318
|
+
fmtDollars(r2aCost),
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
'R2 Class B',
|
|
322
|
+
fmt(totalClassB * proj),
|
|
323
|
+
fmt(PRICING.r2ClassB.included),
|
|
324
|
+
fmtDollars(r2bCost),
|
|
325
|
+
],
|
|
326
|
+
['', '', '', '────────'],
|
|
327
|
+
['TOTAL', '', '', fmtDollars(total)],
|
|
328
|
+
], [false, true, true, true])
|
|
329
|
+
.split('\n')
|
|
330
|
+
.join('\n '));
|
|
331
|
+
console.log('');
|
|
332
|
+
if (total <= PRICING.baseFee) {
|
|
333
|
+
console.log(' ' + color.green('All usage within free tiers.') + ' Only paying the $5 base fee.');
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
console.log(` Overage: ${fmtDollars(total - PRICING.baseFee)} above $5 base fee.`);
|
|
337
|
+
}
|
|
338
|
+
console.log(` Based on ${days} days, projected to 30-day month.`);
|
|
339
|
+
console.log('');
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* --per-action / --per-conversation report. Reads from the connected
|
|
343
|
+
* DO's `admin:cost-report` action — a convention action the app
|
|
344
|
+
* implements by calling `ActionCostLedger.byAction()` / `byConversation()`.
|
|
345
|
+
*
|
|
346
|
+
* If the action isn't registered (the app hasn't adopted the SDK
|
|
347
|
+
* primitive), print an install hint instead of a confusing error.
|
|
348
|
+
*/
|
|
349
|
+
async function perCallReport(days, opts) {
|
|
350
|
+
const { isDaemonRunning, daemonGetConnection, daemonRpc } = await import('../daemon-client.js');
|
|
351
|
+
if (!(await isDaemonRunning())) {
|
|
352
|
+
console.error(color.red('✗') + ' Daemon not running. Start it: beehaven start --headless\n (or open the Hive Mac app, which runs one for you.)');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const conn = await daemonGetConnection();
|
|
356
|
+
if (!conn) {
|
|
357
|
+
console.error(color.red('✗') + ' No connected DO. Run `beehaven connect <address>` first.');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const sinceMs = Date.now() - days * 86400_000;
|
|
361
|
+
const gatewayAction = conn.buildingType === 'shopifyStore' ? 'admin:store-rpc' : `admin:${conn.buildingType}-rpc`;
|
|
362
|
+
try {
|
|
363
|
+
const reportInput = {
|
|
364
|
+
sinceMs,
|
|
365
|
+
includeByAction: !!opts.perAction,
|
|
366
|
+
includeByConversation: !!opts.perConversation,
|
|
367
|
+
conversationLimit: 50,
|
|
368
|
+
};
|
|
369
|
+
const res = await daemonRpc(gatewayAction, {
|
|
370
|
+
storeId: conn.buildingId,
|
|
371
|
+
action: 'admin:cost-report',
|
|
372
|
+
input: reportInput,
|
|
373
|
+
});
|
|
374
|
+
const report = res?.result ?? res;
|
|
375
|
+
if (!report) {
|
|
376
|
+
console.log(` No cost data for the last ${days} day(s).`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
console.log(` ${color.bold(`Cost Report — ${conn.buildingType}/${conn.buildingId}`)}`);
|
|
381
|
+
console.log(` Last ${days} day(s)`);
|
|
382
|
+
if (report.totals) {
|
|
383
|
+
const totalCents = Math.round((report.totals.totalMicroCents ?? 0) / 1000);
|
|
384
|
+
console.log(` Total calls: ${report.totals.calls} | Total: ${fmtDollars(totalCents / 100)}`);
|
|
385
|
+
}
|
|
386
|
+
if (opts.perAction && Array.isArray(report.byAction) && report.byAction.length > 0) {
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log(' ' + color.bold('Per action'));
|
|
389
|
+
console.log(' ' +
|
|
390
|
+
table(['Action', 'Calls', 'Total', 'Avg / call', 'Avg ms'], report.byAction.map((r) => [
|
|
391
|
+
r.action,
|
|
392
|
+
fmt(r.calls),
|
|
393
|
+
fmtDollars((r.totalMicroCents ?? 0) / 100_000),
|
|
394
|
+
fmtDollars((r.avgMicroCents ?? 0) / 100_000),
|
|
395
|
+
r.avgDurationMs ? Math.round(r.avgDurationMs).toString() : '—',
|
|
396
|
+
]), [false, true, true, true, true])
|
|
397
|
+
.split('\n')
|
|
398
|
+
.join('\n '));
|
|
399
|
+
}
|
|
400
|
+
if (opts.perConversation &&
|
|
401
|
+
Array.isArray(report.byConversation) &&
|
|
402
|
+
report.byConversation.length > 0) {
|
|
403
|
+
console.log('');
|
|
404
|
+
console.log(' ' + color.bold('Per conversation'));
|
|
405
|
+
console.log(' ' +
|
|
406
|
+
table(['Conversation', 'Calls', 'Actions', 'Total'], report.byConversation.map((r) => [
|
|
407
|
+
r.conversationId.slice(0, 32),
|
|
408
|
+
fmt(r.calls),
|
|
409
|
+
fmt(r.actions),
|
|
410
|
+
fmtDollars((r.totalMicroCents ?? 0) / 100_000),
|
|
411
|
+
]), [false, true, true, true])
|
|
412
|
+
.split('\n')
|
|
413
|
+
.join('\n '));
|
|
414
|
+
}
|
|
415
|
+
console.log('');
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
const msg = String(err?.message ?? err);
|
|
419
|
+
if (/unknown action|action not found|admin:cost-report/i.test(msg)) {
|
|
420
|
+
console.error(color.yellow('!') + ' The connected DO does not implement `admin:cost-report`.');
|
|
421
|
+
console.error(' Adopt the ActionCostLedger pattern:');
|
|
422
|
+
console.error('');
|
|
423
|
+
console.error(" import { ActionCostLedger } from '@clearly-sh/beehaven-core/server';");
|
|
424
|
+
console.error('');
|
|
425
|
+
console.error(' class MyShop extends Workspace {');
|
|
426
|
+
console.error(' costs = new ActionCostLedger(this.state.storage.sql);');
|
|
427
|
+
console.error(" costReport = this.action('admin:cost-report', {");
|
|
428
|
+
console.error(" input: { sinceMs: 'number', includeByAction: 'boolean', includeByConversation: 'boolean', conversationLimit: 'number' },");
|
|
429
|
+
console.error(' handler: async (input) => ({');
|
|
430
|
+
console.error(' totals: this.costs.totals(input.sinceMs),');
|
|
431
|
+
console.error(' byAction: input.includeByAction ? this.costs.byAction(input.sinceMs) : undefined,');
|
|
432
|
+
console.error(' byConversation: input.includeByConversation ? this.costs.byConversation(input.sinceMs, input.conversationLimit) : undefined,');
|
|
433
|
+
console.error(' }),');
|
|
434
|
+
console.error(' });');
|
|
435
|
+
console.error(' }');
|
|
436
|
+
console.error('');
|
|
437
|
+
console.error(' Then call costs.append({...}) inside each action handler.');
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
console.error(color.red('✗') + ' ' + msg);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|