sorokeep 1.0.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/CONTRIBUTING.md +296 -0
- package/LICENSE +21 -0
- package/README.md +677 -0
- package/dist/alerts/alerts.d.ts +21 -0
- package/dist/alerts/alerts.d.ts.map +1 -0
- package/dist/alerts/alerts.js +18 -0
- package/dist/alerts/alerts.js.map +1 -0
- package/dist/alerts/discord.d.ts +3 -0
- package/dist/alerts/discord.d.ts.map +1 -0
- package/dist/alerts/discord.js +199 -0
- package/dist/alerts/discord.js.map +1 -0
- package/dist/alerts/dispatcher.d.ts +13 -0
- package/dist/alerts/dispatcher.d.ts.map +1 -0
- package/dist/alerts/dispatcher.js +91 -0
- package/dist/alerts/dispatcher.js.map +1 -0
- package/dist/alerts/keys.d.ts +24 -0
- package/dist/alerts/keys.d.ts.map +1 -0
- package/dist/alerts/keys.js +44 -0
- package/dist/alerts/keys.js.map +1 -0
- package/dist/alerts/pagerduty.d.ts +8 -0
- package/dist/alerts/pagerduty.d.ts.map +1 -0
- package/dist/alerts/pagerduty.js +164 -0
- package/dist/alerts/pagerduty.js.map +1 -0
- package/dist/alerts/resource.d.ts +23 -0
- package/dist/alerts/resource.d.ts.map +1 -0
- package/dist/alerts/resource.js +167 -0
- package/dist/alerts/resource.js.map +1 -0
- package/dist/alerts/slack.d.ts +7 -0
- package/dist/alerts/slack.d.ts.map +1 -0
- package/dist/alerts/slack.js +184 -0
- package/dist/alerts/slack.js.map +1 -0
- package/dist/alerts/telegram.d.ts +9 -0
- package/dist/alerts/telegram.d.ts.map +1 -0
- package/dist/alerts/telegram.js +133 -0
- package/dist/alerts/telegram.js.map +1 -0
- package/dist/alerts/templates.d.ts +109 -0
- package/dist/alerts/templates.d.ts.map +1 -0
- package/dist/alerts/templates.js +139 -0
- package/dist/alerts/templates.js.map +1 -0
- package/dist/alerts/types.d.ts +113 -0
- package/dist/alerts/types.d.ts.map +1 -0
- package/dist/alerts/types.js +87 -0
- package/dist/alerts/types.js.map +1 -0
- package/dist/alerts/webhook.d.ts +13 -0
- package/dist/alerts/webhook.d.ts.map +1 -0
- package/dist/alerts/webhook.js +56 -0
- package/dist/alerts/webhook.js.map +1 -0
- package/dist/cli/schemaFormatter.d.ts +11 -0
- package/dist/cli/schemaFormatter.d.ts.map +1 -0
- package/dist/cli/schemaFormatter.js +74 -0
- package/dist/cli/schemaFormatter.js.map +1 -0
- package/dist/commands/alerts.d.ts +3 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +256 -0
- package/dist/commands/alerts.js.map +1 -0
- package/dist/commands/budget.d.ts +3 -0
- package/dist/commands/budget.d.ts.map +1 -0
- package/dist/commands/budget.js +47 -0
- package/dist/commands/budget.js.map +1 -0
- package/dist/commands/channels.d.ts +3 -0
- package/dist/commands/channels.d.ts.map +1 -0
- package/dist/commands/channels.js +84 -0
- package/dist/commands/channels.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +58 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/completion.d.ts +3 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +36 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/costs.d.ts +15 -0
- package/dist/commands/costs.d.ts.map +1 -0
- package/dist/commands/costs.js +280 -0
- package/dist/commands/costs.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +93 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +140 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/guard.d.ts +3 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +181 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +57 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/inspect.d.ts +3 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +82 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +20 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pause.d.ts +3 -0
- package/dist/commands/pause.d.ts.map +1 -0
- package/dist/commands/pause.js +32 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/resources.d.ts +3 -0
- package/dist/commands/resources.d.ts.map +1 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/resources.js.map +1 -0
- package/dist/commands/restore.d.ts +3 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +92 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/resume.d.ts +3 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +31 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +63 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +163 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/core/aws_secrets.d.ts +13 -0
- package/dist/core/aws_secrets.d.ts.map +1 -0
- package/dist/core/aws_secrets.js +34 -0
- package/dist/core/aws_secrets.js.map +1 -0
- package/dist/core/budget.d.ts +10 -0
- package/dist/core/budget.d.ts.map +1 -0
- package/dist/core/budget.js +31 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/channels.d.ts +67 -0
- package/dist/core/channels.d.ts.map +1 -0
- package/dist/core/channels.js +136 -0
- package/dist/core/channels.js.map +1 -0
- package/dist/core/check.d.ts +25 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +85 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/completion.d.ts +9 -0
- package/dist/core/completion.d.ts.map +1 -0
- package/dist/core/completion.js +68 -0
- package/dist/core/completion.js.map +1 -0
- package/dist/core/costs.d.ts +90 -0
- package/dist/core/costs.d.ts.map +1 -0
- package/dist/core/costs.js +147 -0
- package/dist/core/costs.js.map +1 -0
- package/dist/core/decoder.d.ts +7 -0
- package/dist/core/decoder.d.ts.map +1 -0
- package/dist/core/decoder.js +38 -0
- package/dist/core/decoder.js.map +1 -0
- package/dist/core/discovery.d.ts +38 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +208 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/extension.d.ts +83 -0
- package/dist/core/extension.d.ts.map +1 -0
- package/dist/core/extension.js +429 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/inspect.d.ts +62 -0
- package/dist/core/inspect.d.ts.map +1 -0
- package/dist/core/inspect.js +303 -0
- package/dist/core/inspect.js.map +1 -0
- package/dist/core/introspection.d.ts +8 -0
- package/dist/core/introspection.d.ts.map +1 -0
- package/dist/core/introspection.js +8 -0
- package/dist/core/introspection.js.map +1 -0
- package/dist/core/monitor.d.ts +42 -0
- package/dist/core/monitor.d.ts.map +1 -0
- package/dist/core/monitor.js +188 -0
- package/dist/core/monitor.js.map +1 -0
- package/dist/core/rent_projection.d.ts +153 -0
- package/dist/core/rent_projection.d.ts.map +1 -0
- package/dist/core/rent_projection.js +146 -0
- package/dist/core/rent_projection.js.map +1 -0
- package/dist/core/scvalTranslator.d.ts +11 -0
- package/dist/core/scvalTranslator.d.ts.map +1 -0
- package/dist/core/scvalTranslator.js +36 -0
- package/dist/core/scvalTranslator.js.map +1 -0
- package/dist/core/state_diff.d.ts +45 -0
- package/dist/core/state_diff.d.ts.map +1 -0
- package/dist/core/state_diff.js +112 -0
- package/dist/core/state_diff.js.map +1 -0
- package/dist/core/status.d.ts +24 -0
- package/dist/core/status.d.ts.map +1 -0
- package/dist/core/status.js +57 -0
- package/dist/core/status.js.map +1 -0
- package/dist/core/vault.d.ts +37 -0
- package/dist/core/vault.d.ts.map +1 -0
- package/dist/core/vault.js +179 -0
- package/dist/core/vault.js.map +1 -0
- package/dist/core/watch.d.ts +36 -0
- package/dist/core/watch.d.ts.map +1 -0
- package/dist/core/watch.js +176 -0
- package/dist/core/watch.js.map +1 -0
- package/dist/daemon/loop.d.ts +38 -0
- package/dist/daemon/loop.d.ts.map +1 -0
- package/dist/daemon/loop.js +169 -0
- package/dist/daemon/loop.js.map +1 -0
- package/dist/db/backup.d.ts +12 -0
- package/dist/db/backup.d.ts.map +1 -0
- package/dist/db/backup.js +132 -0
- package/dist/db/backup.js.map +1 -0
- package/dist/db/budget.d.ts +17 -0
- package/dist/db/budget.d.ts.map +1 -0
- package/dist/db/budget.js +31 -0
- package/dist/db/budget.js.map +1 -0
- package/dist/db/database.d.ts +6 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +134 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/migrations/.gitkeep +1 -0
- package/dist/db/migrations/001_resource_usage_logs.sql +28 -0
- package/dist/db/migrator.d.ts +28 -0
- package/dist/db/migrator.d.ts.map +1 -0
- package/dist/db/migrator.js +75 -0
- package/dist/db/migrator.js.map +1 -0
- package/dist/db/repositories.d.ts +474 -0
- package/dist/db/repositories.d.ts.map +1 -0
- package/dist/db/repositories.js +809 -0
- package/dist/db/repositories.js.map +1 -0
- package/dist/db/schema.sql +207 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +20 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +16 -0
- package/dist/lib.js.map +1 -0
- package/dist/logging/index.d.ts +22 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +34 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +14 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +135 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +10 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +14 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +51 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-extension-costs.d.ts +4 -0
- package/dist/mcp/tools/get-extension-costs.d.ts.map +1 -0
- package/dist/mcp/tools/get-extension-costs.js +33 -0
- package/dist/mcp/tools/get-extension-costs.js.map +1 -0
- package/dist/mcp/tools/get_contract_status.d.ts +8 -0
- package/dist/mcp/tools/get_contract_status.d.ts.map +1 -0
- package/dist/mcp/tools/get_contract_status.js +33 -0
- package/dist/mcp/tools/get_contract_status.js.map +1 -0
- package/dist/rpc/client.d.ts +154 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +766 -0
- package/dist/rpc/client.js.map +1 -0
- package/dist/utils/config.d.ts +51 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +86 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +66 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/watch-config.d.ts +13 -0
- package/dist/utils/watch-config.d.ts.map +1 -0
- package/dist/utils/watch-config.js +32 -0
- package/dist/utils/watch-config.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getDatabase } from "../db/database.js";
|
|
3
|
+
import { getExtensionCosts, calculateFeeAdjustedProjection, getMultiPeriodCosts } from "../core/costs.js";
|
|
4
|
+
import { getEntriesForContract } from "../db/repositories.js";
|
|
5
|
+
import { projectRentWindows, DEFAULT_FEE_PER_RENT_1KB, } from "../core/rent_projection.js";
|
|
6
|
+
import { StellarRpcClient } from "../rpc/client.js";
|
|
7
|
+
import { formatContractID } from "../utils/formatting.js";
|
|
8
|
+
import { loadConfig } from "../utils/config.js";
|
|
9
|
+
import { getLogger } from "../logging/index.js";
|
|
10
|
+
const logger = getLogger().child({ component: "CostsCommand" });
|
|
11
|
+
function padRight(str, len) {
|
|
12
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
13
|
+
}
|
|
14
|
+
function padLeft(str, len) {
|
|
15
|
+
return str.length >= len ? str : " ".repeat(len - str.length) + str;
|
|
16
|
+
}
|
|
17
|
+
function printMultiPeriodTable(data, feeStats) {
|
|
18
|
+
const displayName = data.contract.name ?? formatContractID(data.contract.id);
|
|
19
|
+
console.log(`\n${chalk.bold("Extension Costs")} — ${chalk.cyan(displayName)}`);
|
|
20
|
+
console.log(` Network: ${chalk.cyan(data.contract.network)}`);
|
|
21
|
+
const header = ` ${padRight("Period", 12)} ${padLeft("Extensions", 12)} ${padLeft("Total XLM", 16)}`;
|
|
22
|
+
console.log(`\n${chalk.bold(header)}`);
|
|
23
|
+
console.log(` ${"─".repeat(40)}`);
|
|
24
|
+
for (const p of data.periods) {
|
|
25
|
+
const period = padRight(`${p.days} days`, 12);
|
|
26
|
+
const extensions = padLeft(p.totalExtensions.toString(), 12);
|
|
27
|
+
const cost = padLeft(p.totalCostXlm.toFixed(7), 16);
|
|
28
|
+
console.log(` ${period} ${chalk.cyan(extensions)} ${chalk.cyan(cost)}`);
|
|
29
|
+
}
|
|
30
|
+
console.log(`\n ${chalk.bold("Projection")}`);
|
|
31
|
+
console.log(` Estimated 30-day cost: ~${chalk.cyan(data.projection.adjustedProjectedCostXlm.toFixed(7))} XLM`);
|
|
32
|
+
if (feeStats) {
|
|
33
|
+
console.log(` Live base fee: ${chalk.cyan(feeStats.baseFeeStroops.toString())} stroops`);
|
|
34
|
+
console.log(` Surge multiplier: ${chalk.cyan(`${data.projection.surgePricingMultiplier.toFixed(2)}x`)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fallback entry size when mem_bytes is not recorded for any extension of
|
|
39
|
+
* an entry. 1 KB is the smallest meaningful allocation for a Soroban entry
|
|
40
|
+
* and keeps projections conservative.
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_ENTRY_SIZE_BYTES = 1024;
|
|
43
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Render a single RentWindowsResult as a table under the "Forecasted Rent"
|
|
46
|
+
* section. Returns an array of console lines (without printing) so callers can
|
|
47
|
+
* test the output easily.
|
|
48
|
+
*
|
|
49
|
+
* @param result - Output of `projectRentWindows` for one entry.
|
|
50
|
+
* @param entryType - Human-readable entry type label (instance, wasm, …).
|
|
51
|
+
* @param budget - Monthly budget in XLM (optional). When provided, windows
|
|
52
|
+
* that exceed the budget are flagged in red.
|
|
53
|
+
*/
|
|
54
|
+
export function formatForecastedRentEntry(result, entryType, budget) {
|
|
55
|
+
const lines = [];
|
|
56
|
+
lines.push(` ${chalk.dim("Entry type:")} ${entryType}`);
|
|
57
|
+
for (const window of result.windows) {
|
|
58
|
+
const xlm = window.estimatedFeeXlm.toFixed(7);
|
|
59
|
+
const overBudget = budget !== undefined && window.estimatedFeeXlm > budget;
|
|
60
|
+
const budgetFlag = overBudget
|
|
61
|
+
? " " + chalk.bold.red(`⚠ OVER BUDGET (limit: ${budget.toFixed(7)} XLM)`)
|
|
62
|
+
: "";
|
|
63
|
+
const dayLabel = `${window.days}-day`;
|
|
64
|
+
const costStr = overBudget
|
|
65
|
+
? chalk.red(`~${xlm} XLM`)
|
|
66
|
+
: chalk.cyan(`~${xlm} XLM`);
|
|
67
|
+
lines.push(` ${chalk.bold(dayLabel)}: ${costStr}${budgetFlag}`);
|
|
68
|
+
}
|
|
69
|
+
return lines;
|
|
70
|
+
}
|
|
71
|
+
// ─── Command registration ─────────────────────────────────────────────────────
|
|
72
|
+
export function registerCostsCommand(program) {
|
|
73
|
+
program
|
|
74
|
+
.command("costs <contractId>")
|
|
75
|
+
.description("Show rent costs and extension history for a contract")
|
|
76
|
+
.option("--period <days>", "Show costs for the last N days", "30")
|
|
77
|
+
.option("--all", "Show all extension history")
|
|
78
|
+
.option("--json", "Output machine-readable JSON")
|
|
79
|
+
.option("--monthly-budget <xlm>", "Monthly budget in XLM — highlight forecast windows that exceed this limit")
|
|
80
|
+
.action(async (contractId, options = {}) => {
|
|
81
|
+
options = options || {};
|
|
82
|
+
try {
|
|
83
|
+
const db = getDatabase();
|
|
84
|
+
const days = options.all ? undefined : parseInt(options.period ?? "30", 10);
|
|
85
|
+
if (days !== undefined && (!Number.isInteger(days) || days <= 0)) {
|
|
86
|
+
if (options.json) {
|
|
87
|
+
console.log(JSON.stringify({
|
|
88
|
+
success: false,
|
|
89
|
+
error: "invalid_period",
|
|
90
|
+
period: options.period,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.error(chalk.red("--period must be a positive integer number of days"));
|
|
95
|
+
}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Resolve monthly budget: CLI flag > config file
|
|
100
|
+
const config = loadConfig();
|
|
101
|
+
let monthlyBudget;
|
|
102
|
+
if (options.monthlyBudget !== undefined) {
|
|
103
|
+
const parsed = parseFloat(options.monthlyBudget);
|
|
104
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
105
|
+
if (options.json) {
|
|
106
|
+
console.log(JSON.stringify({
|
|
107
|
+
success: false,
|
|
108
|
+
error: "invalid_monthly_budget",
|
|
109
|
+
monthlyBudget: options.monthlyBudget,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.error(chalk.red("--monthly-budget must be a positive number"));
|
|
114
|
+
}
|
|
115
|
+
process.exit(1);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
monthlyBudget = parsed;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
monthlyBudget = config.monthlyBudgetXlm;
|
|
122
|
+
}
|
|
123
|
+
const result = getExtensionCosts(db, contractId, options.all ? { all: true } : { period: days });
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
if (options.json) {
|
|
126
|
+
console.log(JSON.stringify(result));
|
|
127
|
+
}
|
|
128
|
+
else if (result.error === "contract_not_found") {
|
|
129
|
+
console.error(chalk.red(`Contract ${formatContractID(contractId)} not found. Run 'sorokeep watch' first.`));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.error(chalk.red("An error occurred computing extension costs."));
|
|
133
|
+
}
|
|
134
|
+
process.exit(1);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const { data } = result;
|
|
138
|
+
if (options.json) {
|
|
139
|
+
console.log(JSON.stringify(data, null, 2));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const displayName = data.contract.name ?? formatContractID(contractId);
|
|
143
|
+
let feeStats;
|
|
144
|
+
try {
|
|
145
|
+
feeStats = await new StellarRpcClient(data.contract.network).getFeeStats();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
logger.warn("Unable to fetch live fee stats; using historical projection", { error: message });
|
|
150
|
+
}
|
|
151
|
+
const multiResult = getMultiPeriodCosts(db, contractId, feeStats);
|
|
152
|
+
if (multiResult.success) {
|
|
153
|
+
printMultiPeriodTable(multiResult.data, feeStats);
|
|
154
|
+
}
|
|
155
|
+
console.log(`\n${chalk.bold("Extension History")} — ${chalk.cyan(displayName)} (${data.period.label})`);
|
|
156
|
+
console.log(` Network: ${chalk.cyan(data.contract.network)}`);
|
|
157
|
+
if (data.message) {
|
|
158
|
+
console.log(chalk.dim(`\n ${data.message}`));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
console.log(`\n ${chalk.bold("Summary")}`);
|
|
162
|
+
console.log(` Total extensions: ${chalk.cyan(data.summary.totalExtensions.toString())}`);
|
|
163
|
+
console.log(` Total cost: ${chalk.cyan(data.summary.totalCostXlm.toFixed(7))} XLM`);
|
|
164
|
+
console.log(`\n ${chalk.bold("By Entry Type")}`);
|
|
165
|
+
for (const [type, entryData] of Object.entries(data.byEntryType)) {
|
|
166
|
+
console.log(` ${type}: ${entryData.count} extensions (${entryData.costXlm.toFixed(7)} XLM)`);
|
|
167
|
+
}
|
|
168
|
+
if (days && data.summary.totalExtensions > 0) {
|
|
169
|
+
const projection = calculateFeeAdjustedProjection(data.summary.totalCostXlm, days, feeStats);
|
|
170
|
+
console.log(`\n ${chalk.bold("Projection")}`);
|
|
171
|
+
console.log(` Estimated 30-day cost: ~${chalk.cyan(projection.adjustedProjectedCostXlm.toFixed(7))} XLM`);
|
|
172
|
+
if (feeStats) {
|
|
173
|
+
console.log(` Live base fee: ${chalk.cyan(feeStats.baseFeeStroops.toString())} stroops`);
|
|
174
|
+
console.log(` Surge multiplier: ${chalk.cyan(`${projection.surgePricingMultiplier.toFixed(2)}x`)}`);
|
|
175
|
+
}
|
|
176
|
+
// ── Forecasted Rent (30/60/90-day windows per entry) ──────────
|
|
177
|
+
const entries = getEntriesForContract(db, contractId);
|
|
178
|
+
// Build a map of entry_type → max observed mem_bytes from
|
|
179
|
+
// recent extensions. Falls back to DEFAULT_ENTRY_SIZE_BYTES
|
|
180
|
+
// when no size information is available.
|
|
181
|
+
const sizeByEntryType = new Map();
|
|
182
|
+
for (const record of data.recentExtensions) {
|
|
183
|
+
// recentExtensions carries the entry_type label; we need
|
|
184
|
+
// the raw entry_type so look it up from the DB entries.
|
|
185
|
+
const dbEntry = entries.find((e) => (e.label ?? e.entry_type) === record.entryLabel) ?? entries.find((e) => e.entry_type === record.entryType);
|
|
186
|
+
if (!dbEntry)
|
|
187
|
+
continue;
|
|
188
|
+
// mem_bytes on the extension record is the closest proxy
|
|
189
|
+
// for the on-chain entry size we have stored locally.
|
|
190
|
+
// The raw record doesn't carry mem_bytes directly through
|
|
191
|
+
// the ExtensionCostDetail shape — query the history rows
|
|
192
|
+
// that we already have in data.recentExtensions; however
|
|
193
|
+
// that shape has no mem_bytes field. So we query the DB
|
|
194
|
+
// entries table for a stored size_bytes (doesn't exist) or
|
|
195
|
+
// fall back to the DEFAULT. The actual mem_bytes used during
|
|
196
|
+
// the extension transaction is in extension_history.mem_bytes
|
|
197
|
+
// but wasn't surfaced. We access it here via the DB directly.
|
|
198
|
+
if (!sizeByEntryType.has(dbEntry.entry_type)) {
|
|
199
|
+
const row = db
|
|
200
|
+
.prepare(`SELECT mem_bytes FROM extension_history
|
|
201
|
+
WHERE contract_entry_id = ? AND mem_bytes IS NOT NULL
|
|
202
|
+
ORDER BY executed_at DESC LIMIT 1`)
|
|
203
|
+
.get(dbEntry.id);
|
|
204
|
+
sizeByEntryType.set(dbEntry.entry_type, row?.mem_bytes ?? DEFAULT_ENTRY_SIZE_BYTES);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Also account for entry types that exist in the DB but might
|
|
208
|
+
// not have appeared in recentExtensions (e.g. --period filtered
|
|
209
|
+
// them out).
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (!sizeByEntryType.has(entry.entry_type)) {
|
|
212
|
+
const row = db
|
|
213
|
+
.prepare(`SELECT mem_bytes FROM extension_history
|
|
214
|
+
WHERE contract_entry_id = ? AND mem_bytes IS NOT NULL
|
|
215
|
+
ORDER BY executed_at DESC LIMIT 1`)
|
|
216
|
+
.get(entry.id);
|
|
217
|
+
sizeByEntryType.set(entry.entry_type, row?.mem_bytes ?? DEFAULT_ENTRY_SIZE_BYTES);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Only show the section for entry types that actually had
|
|
221
|
+
// extensions in this period.
|
|
222
|
+
const activeEntryTypes = Object.keys(data.byEntryType);
|
|
223
|
+
if (activeEntryTypes.length > 0) {
|
|
224
|
+
const feePerRent1kb = feeStats
|
|
225
|
+
? DEFAULT_FEE_PER_RENT_1KB // TODO: fetch from network config
|
|
226
|
+
: DEFAULT_FEE_PER_RENT_1KB;
|
|
227
|
+
console.log(`\n ${chalk.bold("Forecasted Rent")} (based on Soroban rent formula)`);
|
|
228
|
+
if (monthlyBudget !== undefined) {
|
|
229
|
+
console.log(` Monthly budget: ${chalk.cyan(monthlyBudget.toFixed(7))} XLM`);
|
|
230
|
+
}
|
|
231
|
+
let anyBreach = false;
|
|
232
|
+
for (const entryType of activeEntryTypes) {
|
|
233
|
+
const entrySizeBytes = sizeByEntryType.get(entryType) ?? DEFAULT_ENTRY_SIZE_BYTES;
|
|
234
|
+
const isPersistent = entryType === "instance" ||
|
|
235
|
+
entryType === "wasm" ||
|
|
236
|
+
entryType === "persistent";
|
|
237
|
+
const isCodeEntry = entryType === "wasm";
|
|
238
|
+
const forecast = projectRentWindows({
|
|
239
|
+
entrySizeBytes,
|
|
240
|
+
feePerRent1kb,
|
|
241
|
+
isPersistent,
|
|
242
|
+
isCodeEntry,
|
|
243
|
+
});
|
|
244
|
+
const lines = formatForecastedRentEntry(forecast, entryType, monthlyBudget);
|
|
245
|
+
for (const line of lines) {
|
|
246
|
+
console.log(line);
|
|
247
|
+
}
|
|
248
|
+
// Track whether any window breaches the budget
|
|
249
|
+
if (monthlyBudget !== undefined &&
|
|
250
|
+
forecast.windows.some((w) => w.estimatedFeeXlm > monthlyBudget)) {
|
|
251
|
+
anyBreach = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (anyBreach) {
|
|
255
|
+
console.log(chalk.bold.red(`\n ⚠ One or more forecast windows exceed your monthly budget of ` +
|
|
256
|
+
`${monthlyBudget.toFixed(7)} XLM. ` +
|
|
257
|
+
`Consider raising TTLs or reviewing entry sizes to reduce rent costs.`));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
console.log(`\n ${chalk.bold("Recent Extensions")}`);
|
|
262
|
+
const recent = options.all ? data.recentExtensions : data.recentExtensions.slice(0, 10);
|
|
263
|
+
for (const record of recent) {
|
|
264
|
+
const cost = record.costXlm !== null ? `${record.costXlm.toFixed(7)} XLM` : "N/A";
|
|
265
|
+
console.log(` ${chalk.dim(record.executedAt)} ${record.entryLabel}: ${record.oldTtlFormatted} → ${record.newTtlFormatted} (${cost})`);
|
|
266
|
+
console.log(` ${chalk.dim(`tx: ${record.txHash.slice(0, 16)}...`)}`);
|
|
267
|
+
}
|
|
268
|
+
if (!options.all && data.recentExtensions.length > 10) {
|
|
269
|
+
console.log(chalk.dim(`\n ... and ${data.recentExtensions.length - 10} more. Use --all to see everything.`));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
274
|
+
logger.error("Costs command failed", { error: msg });
|
|
275
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=costs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"costs.js","sourceRoot":"","sources":["../../src/commands/costs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwG5D"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getDatabase } from "../db/database.js";
|
|
3
|
+
import { startDaemon, stopDaemon } from "../daemon/loop.js";
|
|
4
|
+
import { configureLogger, getLogger } from "../logging/index.js";
|
|
5
|
+
import { loadConfig } from "../utils/config.js";
|
|
6
|
+
export function registerDaemonCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("daemon")
|
|
9
|
+
.description("Start the monitoring daemon — polls contracts at a fixed interval")
|
|
10
|
+
.option("--network <network>", "Stellar network to monitor")
|
|
11
|
+
.option("--interval <ms>", "Polling interval in milliseconds")
|
|
12
|
+
.option("-r, --rpc-url <url>", "Custom RPC endpoint URL")
|
|
13
|
+
.option("--log-format <format>", "Log output format: 'pretty' (human-readable) or 'json' (structured)", "pretty")
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
const network = options.network ?? config.network;
|
|
17
|
+
const intervalMs = options.interval !== undefined
|
|
18
|
+
? parseInt(options.interval, 10)
|
|
19
|
+
: config.pollingIntervalSeconds * 1000;
|
|
20
|
+
if (isNaN(intervalMs) || intervalMs < 10000) {
|
|
21
|
+
console.log(chalk.red("Error: --interval must be a number >= 10000 (10 seconds)"));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (options.logFormat !== "pretty" && options.logFormat !== "json") {
|
|
25
|
+
console.log(chalk.red("Error: --log-format must be either 'pretty' or 'json'"));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Reconfigure the global logger for the daemon process so every
|
|
29
|
+
// component (this command and the loop) honours the chosen format.
|
|
30
|
+
configureLogger({ mode: "daemon", format: options.logFormat });
|
|
31
|
+
const logger = getLogger().child({ component: "DaemonCommand" });
|
|
32
|
+
let db;
|
|
33
|
+
try {
|
|
34
|
+
db = getDatabase();
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.log(chalk.red(`Failed to open database: ${msg}`));
|
|
39
|
+
logger.error("Database initialization failed", { error: msg });
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(chalk.bold(" Sorokeep — Daemon"));
|
|
44
|
+
console.log(` Network: ${chalk.cyan(network)}`);
|
|
45
|
+
console.log(` Interval: ${chalk.cyan(Math.floor(intervalMs / 1000) + "s")}`);
|
|
46
|
+
if (options.rpcUrl) {
|
|
47
|
+
console.log(` RPC: ${chalk.cyan(options.rpcUrl)}`);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(chalk.dim(" Press Ctrl+C to stop.\n"));
|
|
51
|
+
// ── Graceful shutdown ────────────────────────────────────
|
|
52
|
+
const shutdown = () => {
|
|
53
|
+
console.log(chalk.yellow("\n Shutting down daemon..."));
|
|
54
|
+
stopDaemon();
|
|
55
|
+
try {
|
|
56
|
+
db.close();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// DB may already be closed — not an error worth surfacing
|
|
60
|
+
}
|
|
61
|
+
process.exit(0);
|
|
62
|
+
};
|
|
63
|
+
process.on("SIGINT", shutdown);
|
|
64
|
+
process.on("SIGTERM", shutdown);
|
|
65
|
+
// ── Start the loop ───────────────────────────────────────
|
|
66
|
+
await startDaemon(db, network, {
|
|
67
|
+
intervalMs,
|
|
68
|
+
rpcUrl: options.rpcUrl,
|
|
69
|
+
feeSponsorSecret: config.feeSponsorSecret,
|
|
70
|
+
onCycle: (result, error) => {
|
|
71
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
72
|
+
if (error) {
|
|
73
|
+
console.log(chalk.dim(` [${timestamp}]`) +
|
|
74
|
+
chalk.red(` Cycle failed: ${error.message}`));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (result) {
|
|
78
|
+
const parts = [
|
|
79
|
+
`Checked ${result.contractsChecked} contract(s)`,
|
|
80
|
+
`updated ${result.entriesUpdated} entries`,
|
|
81
|
+
`${result.thresholdsCrossed} threshold(s) crossed`,
|
|
82
|
+
`${result.alertsResolved} resolved`,
|
|
83
|
+
];
|
|
84
|
+
if (result.errors.length > 0) {
|
|
85
|
+
parts.push(chalk.red(`${result.errors.length} error(s)`));
|
|
86
|
+
}
|
|
87
|
+
console.log(chalk.dim(` [${timestamp}]`) + ` ${parts.join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8HxD"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { getDatabase, vacuumDatabase } from "../db/database.js";
|
|
6
|
+
import { Migrator } from "../db/migrator.js";
|
|
7
|
+
import { exportDatabase, importDatabase } from "../db/backup.js";
|
|
8
|
+
import { getLogger } from "../logging/index.js";
|
|
9
|
+
const logger = getLogger().child({ component: "DbCommand" });
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const migrationsDir = path.resolve(__dirname, "../db/migrations");
|
|
13
|
+
export function registerDbCommand(program) {
|
|
14
|
+
const dbCommand = program
|
|
15
|
+
.command("db")
|
|
16
|
+
.description("Backup, restore, and maintain database state");
|
|
17
|
+
dbCommand
|
|
18
|
+
.command("export")
|
|
19
|
+
.description("Export tracked database tables as JSON to stdout")
|
|
20
|
+
.action(() => {
|
|
21
|
+
try {
|
|
22
|
+
const db = getDatabase();
|
|
23
|
+
const backup = exportDatabase(db);
|
|
24
|
+
process.stdout.write(`${JSON.stringify(backup, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
logger.error("Database export failed", { error: message });
|
|
29
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
dbCommand
|
|
34
|
+
.command("import <file>")
|
|
35
|
+
.description("Import tracked database tables from a JSON backup file")
|
|
36
|
+
.action((file) => {
|
|
37
|
+
try {
|
|
38
|
+
const db = getDatabase();
|
|
39
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
40
|
+
const backup = JSON.parse(raw);
|
|
41
|
+
importDatabase(db, backup);
|
|
42
|
+
console.log(chalk.green("Database import complete"));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
logger.error("Database import failed", { error: message, file });
|
|
47
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
dbCommand
|
|
52
|
+
.command("status")
|
|
53
|
+
.description("Show status of database migrations")
|
|
54
|
+
.action(() => {
|
|
55
|
+
try {
|
|
56
|
+
const db = getDatabase();
|
|
57
|
+
const migrator = new Migrator(db, migrationsDir);
|
|
58
|
+
const applied = migrator.getAppliedMigrations();
|
|
59
|
+
const pending = migrator.getPendingMigrations();
|
|
60
|
+
console.log(chalk.bold("Applied Migrations:"));
|
|
61
|
+
if (applied.length === 0) {
|
|
62
|
+
console.log(" No migrations applied yet.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
applied.forEach((version) => {
|
|
66
|
+
console.log(` - Version ${version}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.bold("Pending Migrations:"));
|
|
71
|
+
if (pending.length === 0) {
|
|
72
|
+
console.log(" No pending migrations.");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
pending.forEach((p) => {
|
|
76
|
+
console.log(` - Version ${p.version} (${p.filename})`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
logger.error("Database status failed", { error: message });
|
|
83
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
dbCommand
|
|
88
|
+
.command("migrate")
|
|
89
|
+
.description("Run all pending database migrations")
|
|
90
|
+
.action(() => {
|
|
91
|
+
try {
|
|
92
|
+
const db = getDatabase();
|
|
93
|
+
const migrator = new Migrator(db, migrationsDir);
|
|
94
|
+
const pending = migrator.getPendingMigrations();
|
|
95
|
+
if (pending.length === 0) {
|
|
96
|
+
console.log("No pending migrations. Database is up to date.");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(`Running ${pending.length} pending migration(s)...`);
|
|
100
|
+
migrator.run();
|
|
101
|
+
console.log(chalk.green("Migrations applied successfully."));
|
|
102
|
+
}
|
|
103
|
+
const applied = migrator.getAppliedMigrations();
|
|
104
|
+
console.log(chalk.bold("Applied Migrations:"));
|
|
105
|
+
applied.forEach((version) => {
|
|
106
|
+
console.log(` - Version ${version}`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
logger.error("Database migration failed", { error: message });
|
|
112
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
dbCommand
|
|
117
|
+
.command("vacuum")
|
|
118
|
+
.description("Run VACUUM to reclaim space and optimize the database")
|
|
119
|
+
.action(() => {
|
|
120
|
+
try {
|
|
121
|
+
const db = getDatabase();
|
|
122
|
+
const success = vacuumDatabase(db);
|
|
123
|
+
if (success) {
|
|
124
|
+
console.log(chalk.green("Database vacuum completed successfully."));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
logger.error("Database vacuum failed or was skipped because of an active transaction or lock");
|
|
128
|
+
console.error(chalk.red("Error: Database vacuum failed or was skipped because of an active transaction or lock."));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
+
logger.error("Database vacuum failed with exception", { error: message });
|
|
135
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/commands/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0M3D"}
|