@moneysiren/cli 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/apps/cli/src/cli.d.ts +56 -0
- package/dist/apps/cli/src/cli.js +182 -0
- package/dist/apps/cli/src/commands/dashboard.d.ts +3 -0
- package/dist/apps/cli/src/commands/dashboard.js +239 -0
- package/dist/apps/cli/src/commands/doctor.d.ts +3 -0
- package/dist/apps/cli/src/commands/doctor.js +25 -0
- package/dist/apps/cli/src/commands/init.d.ts +3 -0
- package/dist/apps/cli/src/commands/init.js +18 -0
- package/dist/apps/cli/src/commands/install.d.ts +3 -0
- package/dist/apps/cli/src/commands/install.js +116 -0
- package/dist/apps/cli/src/commands/modes.d.ts +3 -0
- package/dist/apps/cli/src/commands/modes.js +65 -0
- package/dist/apps/cli/src/commands/notify.d.ts +3 -0
- package/dist/apps/cli/src/commands/notify.js +430 -0
- package/dist/apps/cli/src/commands/report.d.ts +3 -0
- package/dist/apps/cli/src/commands/report.js +206 -0
- package/dist/apps/cli/src/commands/runtime.d.ts +5 -0
- package/dist/apps/cli/src/commands/runtime.js +133 -0
- package/dist/apps/cli/src/commands/shared.d.ts +9 -0
- package/dist/apps/cli/src/commands/shared.js +29 -0
- package/dist/apps/cli/src/commands/summary.d.ts +3 -0
- package/dist/apps/cli/src/commands/summary.js +15 -0
- package/dist/apps/cli/src/commands/sync.d.ts +3 -0
- package/dist/apps/cli/src/commands/sync.js +393 -0
- package/dist/apps/cli/src/commands/theme.d.ts +3 -0
- package/dist/apps/cli/src/commands/theme.js +181 -0
- package/dist/apps/cli/src/home.d.ts +7 -0
- package/dist/apps/cli/src/home.js +97 -0
- package/dist/apps/cli/src/index.d.ts +3 -0
- package/dist/apps/cli/src/index.js +14 -0
- package/dist/apps/cli/src/install-profile.d.ts +35 -0
- package/dist/apps/cli/src/install-profile.js +124 -0
- package/dist/apps/cli/src/install-selector.d.ts +10 -0
- package/dist/apps/cli/src/install-selector.js +66 -0
- package/dist/apps/cli/src/interactive.d.ts +3 -0
- package/dist/apps/cli/src/interactive.js +32 -0
- package/dist/apps/cli/src/postinstall.d.ts +3 -0
- package/dist/apps/cli/src/postinstall.js +42 -0
- package/dist/apps/cli/src/runtime-adapter.d.ts +24 -0
- package/dist/apps/cli/src/runtime-adapter.js +185 -0
- package/dist/apps/cli/src/slash.d.ts +15 -0
- package/dist/apps/cli/src/slash.js +202 -0
- package/dist/apps/cli/src/summary-model.d.ts +51 -0
- package/dist/apps/cli/src/summary-model.js +136 -0
- package/dist/apps/cli/src/theme.d.ts +18 -0
- package/dist/apps/cli/src/theme.js +118 -0
- package/dist/packages/config/src/index.d.ts +3 -0
- package/dist/packages/config/src/index.js +3 -0
- package/dist/packages/config/src/load.d.ts +3 -0
- package/dist/packages/config/src/load.js +77 -0
- package/dist/packages/config/src/schema.d.ts +46 -0
- package/dist/packages/config/src/schema.js +25 -0
- package/dist/packages/connectors/aws/src/cost-explorer.d.ts +34 -0
- package/dist/packages/connectors/aws/src/cost-explorer.js +43 -0
- package/dist/packages/connectors/aws/src/index.d.ts +35 -0
- package/dist/packages/connectors/aws/src/index.js +67 -0
- package/dist/packages/connectors/aws/src/normalize.d.ts +69 -0
- package/dist/packages/connectors/aws/src/normalize.js +141 -0
- package/dist/packages/connectors/aws/src/sdk-client.d.ts +6 -0
- package/dist/packages/connectors/aws/src/sdk-client.js +21 -0
- package/dist/packages/connectors/cloudflare/src/client.d.ts +23 -0
- package/dist/packages/connectors/cloudflare/src/client.js +107 -0
- package/dist/packages/connectors/cloudflare/src/index.d.ts +33 -0
- package/dist/packages/connectors/cloudflare/src/index.js +81 -0
- package/dist/packages/connectors/cloudflare/src/normalize.d.ts +113 -0
- package/dist/packages/connectors/cloudflare/src/normalize.js +288 -0
- package/dist/packages/connectors/mock/src/index.d.ts +58 -0
- package/dist/packages/connectors/mock/src/index.js +66 -0
- package/dist/packages/connectors/openai/src/index.d.ts +55 -0
- package/dist/packages/connectors/openai/src/index.js +169 -0
- package/dist/packages/connectors/openai/src/normalize.d.ts +91 -0
- package/dist/packages/connectors/openai/src/normalize.js +180 -0
- package/dist/packages/connectors/supabase/src/client.d.ts +22 -0
- package/dist/packages/connectors/supabase/src/client.js +132 -0
- package/dist/packages/connectors/supabase/src/index.d.ts +33 -0
- package/dist/packages/connectors/supabase/src/index.js +87 -0
- package/dist/packages/connectors/supabase/src/normalize.d.ts +106 -0
- package/dist/packages/connectors/supabase/src/normalize.js +266 -0
- package/dist/packages/core/src/collector.d.ts +12 -0
- package/dist/packages/core/src/collector.js +68 -0
- package/dist/packages/core/src/index.d.ts +5 -0
- package/dist/packages/core/src/index.js +4 -0
- package/dist/packages/core/src/provider.d.ts +18 -0
- package/dist/packages/core/src/provider.js +2 -0
- package/dist/packages/core/src/risk-engine.d.ts +9 -0
- package/dist/packages/core/src/risk-engine.js +4 -0
- package/dist/packages/core/src/snapshots.d.ts +49 -0
- package/dist/packages/core/src/snapshots.js +9 -0
- package/dist/packages/db/src/client.d.ts +11 -0
- package/dist/packages/db/src/client.js +14 -0
- package/dist/packages/db/src/index.d.ts +6 -0
- package/dist/packages/db/src/index.js +6 -0
- package/dist/packages/db/src/local-store.d.ts +161 -0
- package/dist/packages/db/src/local-store.js +623 -0
- package/dist/packages/db/src/migrate.d.ts +17 -0
- package/dist/packages/db/src/migrate.js +35 -0
- package/dist/packages/db/src/schema.d.ts +5 -0
- package/dist/packages/db/src/schema.js +120 -0
- package/dist/packages/db/src/sqlite-bin.d.ts +3 -0
- package/dist/packages/db/src/sqlite-bin.js +16 -0
- package/dist/packages/local-api/src/index.d.ts +2 -0
- package/dist/packages/local-api/src/index.js +2 -0
- package/dist/packages/local-api/src/server.d.ts +36 -0
- package/dist/packages/local-api/src/server.js +310 -0
- package/dist/packages/report/src/daily.d.ts +24 -0
- package/dist/packages/report/src/daily.js +9 -0
- package/dist/packages/report/src/index.d.ts +4 -0
- package/dist/packages/report/src/index.js +4 -0
- package/dist/packages/report/src/korean.d.ts +3 -0
- package/dist/packages/report/src/korean.js +62 -0
- package/dist/packages/report/src/slack.d.ts +34 -0
- package/dist/packages/report/src/slack.js +134 -0
- package/dist/packages/runtime/src/index.d.ts +2 -0
- package/dist/packages/runtime/src/index.js +2 -0
- package/dist/packages/runtime/src/runtime.d.ts +26 -0
- package/dist/packages/runtime/src/runtime.js +182 -0
- package/dist/packages/view-model/src/index.d.ts +3 -0
- package/dist/packages/view-model/src/index.js +3 -0
- package/dist/packages/view-model/src/notification-preferences-model.d.ts +47 -0
- package/dist/packages/view-model/src/notification-preferences-model.js +218 -0
- package/dist/packages/view-model/src/notification-preferences.d.ts +6 -0
- package/dist/packages/view-model/src/notification-preferences.js +36 -0
- package/dist/packages/view-model/src/view-model.d.ts +193 -0
- package/dist/packages/view-model/src/view-model.js +684 -0
- package/package.json +49 -0
- package/scripts/postinstall.mjs +11 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { collectProviderSnapshots } from "../../../../packages/core/src/index.js";
|
|
4
|
+
import { createAwsCostExplorerConnector, createAwsSdkCostExplorerClient, createStaticCostExplorerClient, } from "../../../../packages/connectors/aws/src/index.js";
|
|
5
|
+
import { createMockProviderConnector } from "../../../../packages/connectors/mock/src/index.js";
|
|
6
|
+
import { createOpenAiUsageCostsClient, createOpenAiUsageCostsConnector, createStaticOpenAiUsageCostsClient, } from "../../../../packages/connectors/openai/src/index.js";
|
|
7
|
+
import { createSupabaseManagementClient, createStaticSupabaseUsageHealthClient, createSupabaseUsageHealthConnector, } from "../../../../packages/connectors/supabase/src/index.js";
|
|
8
|
+
import { createCloudflareBillingUsageClient, createCloudflareBillingUsageConnector, createStaticCloudflareBillingUsageClient, } from "../../../../packages/connectors/cloudflare/src/index.js";
|
|
9
|
+
import { initializeLocalStore, saveLocalProviderCollection } from "../../../../packages/db/src/index.js";
|
|
10
|
+
import { loadCliConfig, readFlag, resolveDbPath } from "./shared.js";
|
|
11
|
+
const AWS_COST_EXPLORER_FIXTURE_ENV_KEY = "MONEYSIREN_AWS_COST_EXPLORER_FIXTURE";
|
|
12
|
+
const AWS_REGION_ENV_KEY = "MONEYSIREN_AWS_REGION";
|
|
13
|
+
const OPENAI_USAGE_FIXTURE_ENV_KEY = "MONEYSIREN_OPENAI_USAGE_FIXTURE";
|
|
14
|
+
const OPENAI_COSTS_FIXTURE_ENV_KEY = "MONEYSIREN_OPENAI_COSTS_FIXTURE";
|
|
15
|
+
const SUPABASE_FIXTURE_ENV_KEY = "MONEYSIREN_SUPABASE_FIXTURE";
|
|
16
|
+
const CLOUDFLARE_FIXTURE_ENV_KEY = "MONEYSIREN_CLOUDFLARE_FIXTURE";
|
|
17
|
+
const CLOUDFLARE_ACCOUNT_IDS_ENV_KEY = "CLOUDFLARE_ACCOUNT_IDS";
|
|
18
|
+
const SYNC_USAGE = "Usage: moneysiren sync --provider <mock|aws|openai|supabase|cloudflare> [--profile <aws-profile>]";
|
|
19
|
+
export async function runSyncCommand(args, context) {
|
|
20
|
+
const providerFlag = readFlag(args, "--provider");
|
|
21
|
+
const awsProfileOption = readAwsProfileOption(providerFlag.remainingArgs);
|
|
22
|
+
if (awsProfileOption.error !== undefined ||
|
|
23
|
+
awsProfileOption.remainingArgs.length > 0 ||
|
|
24
|
+
providerFlag.value === undefined ||
|
|
25
|
+
!isSupportedSyncProvider(providerFlag.value)) {
|
|
26
|
+
context.stderr(awsProfileOption.error ?? SYNC_USAGE);
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
if (providerFlag.value !== "aws" && awsProfileOption.profile !== undefined) {
|
|
30
|
+
context.stderr("--profile is only supported for AWS sync.");
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
const config = loadCliConfig(context.env);
|
|
34
|
+
if (providerFlag.value === "aws") {
|
|
35
|
+
const awsEnv = applyAwsProfileOption(context.env, awsProfileOption.profile);
|
|
36
|
+
const fixturePath = readConfiguredEnvValue(awsEnv[AWS_COST_EXPLORER_FIXTURE_ENV_KEY]);
|
|
37
|
+
if (fixturePath !== undefined) {
|
|
38
|
+
return syncAwsProvider(context, config.dbPath, createStaticCostExplorerClient(await loadAwsFixture(context.cwd, fixturePath)));
|
|
39
|
+
}
|
|
40
|
+
if (!isAwsLiveConfigured(awsEnv) && context.liveClients?.awsCostExplorer === undefined) {
|
|
41
|
+
context.stderr(`AWS sync requires AWS_PROFILE, --profile <profile>, AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, or ` +
|
|
42
|
+
`${AWS_COST_EXPLORER_FIXTURE_ENV_KEY}. If you ran aws sso login --profile <profile>, pass ` +
|
|
43
|
+
`--profile <profile> or set AWS_PROFILE in this shell.`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return withAwsProfileOnProcessEnv(awsProfileOption.profile, () => syncAwsProvider(context, config.dbPath, context.liveClients?.awsCostExplorer ?? createAwsSdkCostExplorerClient(awsSdkOptionsFromEnv(awsEnv))));
|
|
47
|
+
}
|
|
48
|
+
if (providerFlag.value === "openai") {
|
|
49
|
+
const usageFixturePath = readConfiguredEnvValue(context.env[OPENAI_USAGE_FIXTURE_ENV_KEY]);
|
|
50
|
+
const costsFixturePath = readConfiguredEnvValue(context.env[OPENAI_COSTS_FIXTURE_ENV_KEY]);
|
|
51
|
+
if (usageFixturePath !== undefined || costsFixturePath !== undefined) {
|
|
52
|
+
if (usageFixturePath === undefined || costsFixturePath === undefined) {
|
|
53
|
+
context.stderr(`OpenAI fixture sync requires both ${OPENAI_USAGE_FIXTURE_ENV_KEY} and ${OPENAI_COSTS_FIXTURE_ENV_KEY}.`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
return syncOpenAiProvider(context, config.dbPath, createStaticOpenAiUsageCostsClient(await loadOpenAiUsageCostsFixture(context.cwd, usageFixturePath, costsFixturePath)));
|
|
57
|
+
}
|
|
58
|
+
if (!config.providers.openai.configured && context.liveClients?.openaiUsageCosts === undefined) {
|
|
59
|
+
context.stderr(`OpenAI sync requires OPENAI_ADMIN_KEY or fixture mode with ${OPENAI_USAGE_FIXTURE_ENV_KEY} ` +
|
|
60
|
+
`and ${OPENAI_COSTS_FIXTURE_ENV_KEY}.`);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
return syncOpenAiProvider(context, config.dbPath, context.liveClients?.openaiUsageCosts ?? createOpenAiUsageCostsClient({
|
|
64
|
+
adminKey: requireConfiguredEnvValue(context.env.OPENAI_ADMIN_KEY, "OPENAI_ADMIN_KEY"),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
if (providerFlag.value === "supabase") {
|
|
68
|
+
const fixturePath = readConfiguredEnvValue(context.env[SUPABASE_FIXTURE_ENV_KEY]);
|
|
69
|
+
if (fixturePath !== undefined) {
|
|
70
|
+
return syncSupabaseProvider(context, config.dbPath, createStaticSupabaseUsageHealthClient(await loadSupabaseFixture(context.cwd, fixturePath)));
|
|
71
|
+
}
|
|
72
|
+
if (!config.providers.supabase.configured && context.liveClients?.supabaseUsageHealth === undefined) {
|
|
73
|
+
context.stderr(`Supabase sync requires SUPABASE_ACCESS_TOKEN or ${SUPABASE_FIXTURE_ENV_KEY}. ` +
|
|
74
|
+
`Set ${SUPABASE_FIXTURE_ENV_KEY} for fixture mode.`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
return syncSupabaseProvider(context, config.dbPath, context.liveClients?.supabaseUsageHealth ?? createSupabaseManagementClient({
|
|
78
|
+
accessToken: requireConfiguredEnvValue(context.env.SUPABASE_ACCESS_TOKEN, "SUPABASE_ACCESS_TOKEN"),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
if (providerFlag.value === "cloudflare") {
|
|
82
|
+
const fixturePath = readConfiguredEnvValue(context.env[CLOUDFLARE_FIXTURE_ENV_KEY]);
|
|
83
|
+
if (fixturePath !== undefined) {
|
|
84
|
+
return syncCloudflareProvider(context, config.dbPath, createStaticCloudflareBillingUsageClient(await loadCloudflareFixture(context.cwd, fixturePath)));
|
|
85
|
+
}
|
|
86
|
+
if (!config.providers.cloudflare.configured && context.liveClients?.cloudflareBillingUsage === undefined) {
|
|
87
|
+
context.stderr(`Cloudflare sync requires CLOUDFLARE_API_TOKEN and ${CLOUDFLARE_ACCOUNT_IDS_ENV_KEY}, or ${CLOUDFLARE_FIXTURE_ENV_KEY}. ` +
|
|
88
|
+
`Set ${CLOUDFLARE_FIXTURE_ENV_KEY} for fixture mode.`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
return syncCloudflareProvider(context, config.dbPath, context.liveClients?.cloudflareBillingUsage ?? createCloudflareBillingUsageClient({
|
|
92
|
+
apiToken: requireConfiguredEnvValue(context.env.CLOUDFLARE_API_TOKEN, "CLOUDFLARE_API_TOKEN"),
|
|
93
|
+
accountIds: readCloudflareAccountIds(context.env[CLOUDFLARE_ACCOUNT_IDS_ENV_KEY]),
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
return syncMockProvider(context, config.dbPath);
|
|
97
|
+
}
|
|
98
|
+
function isSupportedSyncProvider(provider) {
|
|
99
|
+
return provider === "mock" || provider === "aws" || provider === "openai" || provider === "supabase" ||
|
|
100
|
+
provider === "cloudflare";
|
|
101
|
+
}
|
|
102
|
+
async function syncMockProvider(context, configuredDbPath) {
|
|
103
|
+
const dbPath = resolveDbPath(context.cwd, configuredDbPath);
|
|
104
|
+
await initializeLocalStore({ dbPath });
|
|
105
|
+
const connector = createMockProviderConnector();
|
|
106
|
+
const collection = await collectProviderSnapshots(connector, {
|
|
107
|
+
now: context.now,
|
|
108
|
+
});
|
|
109
|
+
await saveLocalProviderCollection({
|
|
110
|
+
dbPath,
|
|
111
|
+
provider: {
|
|
112
|
+
key: connector.kind,
|
|
113
|
+
displayName: connector.displayName,
|
|
114
|
+
connectorVersion: "0.1.0-alpha.0",
|
|
115
|
+
},
|
|
116
|
+
collectedAt: collection.collectedAt,
|
|
117
|
+
status: collection.status,
|
|
118
|
+
snapshots: collection.snapshots,
|
|
119
|
+
alerts: collection.alerts,
|
|
120
|
+
});
|
|
121
|
+
context.stdout([
|
|
122
|
+
"Synced mock provider snapshots:",
|
|
123
|
+
`usage=${collection.snapshots.usage.length}`,
|
|
124
|
+
`billing=${collection.snapshots.billing.length}`,
|
|
125
|
+
`health=${collection.snapshots.serviceHealth.length}`,
|
|
126
|
+
`estimates=${collection.snapshots.costEstimates.length}`,
|
|
127
|
+
`alerts=${collection.alerts.length}`,
|
|
128
|
+
].join(" "));
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
async function syncAwsProvider(context, configuredDbPath, costExplorerClient) {
|
|
132
|
+
const dbPath = resolveDbPath(context.cwd, configuredDbPath);
|
|
133
|
+
await initializeLocalStore({ dbPath });
|
|
134
|
+
const connector = createAwsCostExplorerConnector({
|
|
135
|
+
costExplorerClient,
|
|
136
|
+
});
|
|
137
|
+
const collection = await collectProviderSnapshots(connector, {
|
|
138
|
+
now: context.now,
|
|
139
|
+
});
|
|
140
|
+
await saveLocalProviderCollection({
|
|
141
|
+
dbPath,
|
|
142
|
+
provider: {
|
|
143
|
+
key: connector.kind,
|
|
144
|
+
displayName: connector.displayName,
|
|
145
|
+
connectorVersion: "0.1.0-alpha.0",
|
|
146
|
+
},
|
|
147
|
+
collectedAt: collection.collectedAt,
|
|
148
|
+
status: collection.status,
|
|
149
|
+
snapshots: collection.snapshots,
|
|
150
|
+
alerts: collection.alerts,
|
|
151
|
+
});
|
|
152
|
+
context.stdout([
|
|
153
|
+
"Synced AWS Cost Explorer snapshots:",
|
|
154
|
+
`usage=${collection.snapshots.usage.length}`,
|
|
155
|
+
`billing=${collection.snapshots.billing.length}`,
|
|
156
|
+
`health=${collection.snapshots.serviceHealth.length}`,
|
|
157
|
+
`estimates=${collection.snapshots.costEstimates.length}`,
|
|
158
|
+
`alerts=${collection.alerts.length}`,
|
|
159
|
+
].join(" "));
|
|
160
|
+
if (collection.status === "error") {
|
|
161
|
+
context.stderr(collection.errors?.[0] ?? collection.alerts[0]?.message ?? "AWS Cost Explorer sync failed.");
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
async function loadAwsFixture(cwd, fixturePath) {
|
|
167
|
+
const resolvedPath = isAbsolute(fixturePath) ? fixturePath : join(cwd, fixturePath);
|
|
168
|
+
return JSON.parse(await readFile(resolvedPath, "utf8"));
|
|
169
|
+
}
|
|
170
|
+
async function syncOpenAiProvider(context, configuredDbPath, client) {
|
|
171
|
+
const dbPath = resolveDbPath(context.cwd, configuredDbPath);
|
|
172
|
+
await initializeLocalStore({ dbPath });
|
|
173
|
+
const connector = createOpenAiUsageCostsConnector({
|
|
174
|
+
client,
|
|
175
|
+
});
|
|
176
|
+
const collection = await collectProviderSnapshots(connector, {
|
|
177
|
+
now: context.now,
|
|
178
|
+
});
|
|
179
|
+
await saveLocalProviderCollection({
|
|
180
|
+
dbPath,
|
|
181
|
+
provider: {
|
|
182
|
+
key: connector.kind,
|
|
183
|
+
displayName: connector.displayName,
|
|
184
|
+
connectorVersion: "0.1.0-alpha.0",
|
|
185
|
+
},
|
|
186
|
+
collectedAt: collection.collectedAt,
|
|
187
|
+
status: collection.status,
|
|
188
|
+
snapshots: collection.snapshots,
|
|
189
|
+
alerts: collection.alerts,
|
|
190
|
+
});
|
|
191
|
+
context.stdout([
|
|
192
|
+
"Synced OpenAI usage and costs snapshots:",
|
|
193
|
+
`usage=${collection.snapshots.usage.length}`,
|
|
194
|
+
`billing=${collection.snapshots.billing.length}`,
|
|
195
|
+
`health=${collection.snapshots.serviceHealth.length}`,
|
|
196
|
+
`estimates=${collection.snapshots.costEstimates.length}`,
|
|
197
|
+
`alerts=${collection.alerts.length}`,
|
|
198
|
+
].join(" "));
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
async function loadOpenAiUsageCostsFixture(cwd, usageFixturePath, costsFixturePath) {
|
|
202
|
+
const [usage, costs] = await Promise.all([
|
|
203
|
+
loadOpenAiFixtureSection(cwd, usageFixturePath, "usage"),
|
|
204
|
+
loadOpenAiFixtureSection(cwd, costsFixturePath, "costs"),
|
|
205
|
+
]);
|
|
206
|
+
return {
|
|
207
|
+
usage,
|
|
208
|
+
costs,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async function loadOpenAiFixtureSection(cwd, fixturePath, section) {
|
|
212
|
+
const resolvedPath = isAbsolute(fixturePath) ? fixturePath : join(cwd, fixturePath);
|
|
213
|
+
const parsed = JSON.parse(await readFile(resolvedPath, "utf8"));
|
|
214
|
+
if (isRecord(parsed) && section in parsed) {
|
|
215
|
+
return parsed[section];
|
|
216
|
+
}
|
|
217
|
+
return parsed;
|
|
218
|
+
}
|
|
219
|
+
async function syncSupabaseProvider(context, configuredDbPath, client) {
|
|
220
|
+
const dbPath = resolveDbPath(context.cwd, configuredDbPath);
|
|
221
|
+
await initializeLocalStore({ dbPath });
|
|
222
|
+
const connector = createSupabaseUsageHealthConnector({
|
|
223
|
+
client,
|
|
224
|
+
});
|
|
225
|
+
const collection = await collectProviderSnapshots(connector, {
|
|
226
|
+
now: context.now,
|
|
227
|
+
});
|
|
228
|
+
await saveLocalProviderCollection({
|
|
229
|
+
dbPath,
|
|
230
|
+
provider: {
|
|
231
|
+
key: connector.kind,
|
|
232
|
+
displayName: connector.displayName,
|
|
233
|
+
connectorVersion: "0.1.0-alpha.0",
|
|
234
|
+
},
|
|
235
|
+
collectedAt: collection.collectedAt,
|
|
236
|
+
status: collection.status,
|
|
237
|
+
snapshots: collection.snapshots,
|
|
238
|
+
alerts: collection.alerts,
|
|
239
|
+
});
|
|
240
|
+
context.stdout([
|
|
241
|
+
"Synced Supabase usage and health snapshots:",
|
|
242
|
+
`usage=${collection.snapshots.usage.length}`,
|
|
243
|
+
`billing=${collection.snapshots.billing.length}`,
|
|
244
|
+
`health=${collection.snapshots.serviceHealth.length}`,
|
|
245
|
+
`estimates=${collection.snapshots.costEstimates.length}`,
|
|
246
|
+
`alerts=${collection.alerts.length}`,
|
|
247
|
+
].join(" "));
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
async function loadSupabaseFixture(cwd, fixturePath) {
|
|
251
|
+
const resolvedPath = isAbsolute(fixturePath) ? fixturePath : join(cwd, fixturePath);
|
|
252
|
+
return JSON.parse(await readFile(resolvedPath, "utf8"));
|
|
253
|
+
}
|
|
254
|
+
async function syncCloudflareProvider(context, configuredDbPath, client) {
|
|
255
|
+
const dbPath = resolveDbPath(context.cwd, configuredDbPath);
|
|
256
|
+
await initializeLocalStore({ dbPath });
|
|
257
|
+
const connector = createCloudflareBillingUsageConnector({
|
|
258
|
+
client,
|
|
259
|
+
});
|
|
260
|
+
const collection = await collectProviderSnapshots(connector, {
|
|
261
|
+
now: context.now,
|
|
262
|
+
});
|
|
263
|
+
await saveLocalProviderCollection({
|
|
264
|
+
dbPath,
|
|
265
|
+
provider: {
|
|
266
|
+
key: connector.kind,
|
|
267
|
+
displayName: connector.displayName,
|
|
268
|
+
connectorVersion: "0.1.0-alpha.0",
|
|
269
|
+
},
|
|
270
|
+
collectedAt: collection.collectedAt,
|
|
271
|
+
status: collection.status,
|
|
272
|
+
snapshots: collection.snapshots,
|
|
273
|
+
alerts: collection.alerts,
|
|
274
|
+
});
|
|
275
|
+
context.stdout([
|
|
276
|
+
"Synced Cloudflare billing and usage snapshots:",
|
|
277
|
+
`usage=${collection.snapshots.usage.length}`,
|
|
278
|
+
`billing=${collection.snapshots.billing.length}`,
|
|
279
|
+
`health=${collection.snapshots.serviceHealth.length}`,
|
|
280
|
+
`estimates=${collection.snapshots.costEstimates.length}`,
|
|
281
|
+
`alerts=${collection.alerts.length}`,
|
|
282
|
+
].join(" "));
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
async function loadCloudflareFixture(cwd, fixturePath) {
|
|
286
|
+
const resolvedPath = isAbsolute(fixturePath) ? fixturePath : join(cwd, fixturePath);
|
|
287
|
+
return JSON.parse(await readFile(resolvedPath, "utf8"));
|
|
288
|
+
}
|
|
289
|
+
function readConfiguredEnvValue(value) {
|
|
290
|
+
const trimmed = value?.trim();
|
|
291
|
+
return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
|
|
292
|
+
}
|
|
293
|
+
function requireConfiguredEnvValue(value, envKey) {
|
|
294
|
+
const trimmed = readConfiguredEnvValue(value);
|
|
295
|
+
if (trimmed === undefined) {
|
|
296
|
+
throw new Error(`${envKey} must be configured for live sync.`);
|
|
297
|
+
}
|
|
298
|
+
return trimmed;
|
|
299
|
+
}
|
|
300
|
+
function isAwsLiveConfigured(env) {
|
|
301
|
+
return readConfiguredEnvValue(env.AWS_PROFILE) !== undefined ||
|
|
302
|
+
(readConfiguredEnvValue(env.AWS_ACCESS_KEY_ID) !== undefined &&
|
|
303
|
+
readConfiguredEnvValue(env.AWS_SECRET_ACCESS_KEY) !== undefined);
|
|
304
|
+
}
|
|
305
|
+
function awsSdkOptionsFromEnv(env) {
|
|
306
|
+
const region = readConfiguredEnvValue(env[AWS_REGION_ENV_KEY]);
|
|
307
|
+
return region === undefined ? {} : { region };
|
|
308
|
+
}
|
|
309
|
+
function readAwsProfileOption(args) {
|
|
310
|
+
const remainingArgs = [];
|
|
311
|
+
let profile;
|
|
312
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
313
|
+
const arg = args[index];
|
|
314
|
+
if (arg === "--profile" || arg === "--aws-profile") {
|
|
315
|
+
const value = args[index + 1];
|
|
316
|
+
const parsed = readConfiguredEnvValue(value);
|
|
317
|
+
if (parsed === undefined || value?.startsWith("--") === true) {
|
|
318
|
+
return {
|
|
319
|
+
remainingArgs,
|
|
320
|
+
error: `${arg} requires a profile name.`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (profile !== undefined && profile !== parsed) {
|
|
324
|
+
return {
|
|
325
|
+
remainingArgs,
|
|
326
|
+
error: "AWS profile was provided more than once with different values.",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
profile = parsed;
|
|
330
|
+
index += 1;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (arg?.startsWith("--profile=") || arg?.startsWith("--aws-profile=")) {
|
|
334
|
+
const [flagName, ...valueParts] = arg.split("=");
|
|
335
|
+
const parsed = readConfiguredEnvValue(valueParts.join("="));
|
|
336
|
+
if (parsed === undefined) {
|
|
337
|
+
return {
|
|
338
|
+
remainingArgs,
|
|
339
|
+
error: `${flagName} requires a profile name.`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (profile !== undefined && profile !== parsed) {
|
|
343
|
+
return {
|
|
344
|
+
remainingArgs,
|
|
345
|
+
error: "AWS profile was provided more than once with different values.",
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
profile = parsed;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (arg !== undefined) {
|
|
352
|
+
remainingArgs.push(arg);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return profile === undefined ? { remainingArgs } : { profile, remainingArgs };
|
|
356
|
+
}
|
|
357
|
+
function applyAwsProfileOption(env, profile) {
|
|
358
|
+
if (profile === undefined) {
|
|
359
|
+
return env;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
...env,
|
|
363
|
+
AWS_PROFILE: profile,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async function withAwsProfileOnProcessEnv(profile, run) {
|
|
367
|
+
if (profile === undefined) {
|
|
368
|
+
return run();
|
|
369
|
+
}
|
|
370
|
+
const previousProfile = process.env.AWS_PROFILE;
|
|
371
|
+
process.env.AWS_PROFILE = profile;
|
|
372
|
+
try {
|
|
373
|
+
return await run();
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
if (previousProfile === undefined) {
|
|
377
|
+
delete process.env.AWS_PROFILE;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
process.env.AWS_PROFILE = previousProfile;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function readCloudflareAccountIds(value) {
|
|
385
|
+
return (value ?? "")
|
|
386
|
+
.split(",")
|
|
387
|
+
.map((accountId) => accountId.trim())
|
|
388
|
+
.filter((accountId) => accountId.length > 0);
|
|
389
|
+
}
|
|
390
|
+
function isRecord(value) {
|
|
391
|
+
return typeof value === "object" && value !== null;
|
|
392
|
+
}
|
|
393
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
const THEME_USAGE = "Usage: moneysiren theme <preview|image-prompt|image-generate> [--out <png> --theme-out <json> --model <model>]";
|
|
5
|
+
const DEFAULT_IMAGE_MODEL = "gpt-image-1.5";
|
|
6
|
+
const DEFAULT_IMAGE_OUTPUT = ".moneysiren/themes/cli-image-reference.png";
|
|
7
|
+
const DEFAULT_THEME_OUTPUT = ".moneysiren/themes/cli-theme.json";
|
|
8
|
+
const IMAGE_PROMPT = `Create a polished Image 2 reference for the MoneySiren CLI theme.
|
|
9
|
+
|
|
10
|
+
Product:
|
|
11
|
+
- MoneySiren is a local-first cloud/SaaS usage, status, and expected billing dashboard for individual developers and small teams.
|
|
12
|
+
- CLI surfaces should feel operational, calm, high-trust, and dense enough for repeated terminal use.
|
|
13
|
+
|
|
14
|
+
Image direction:
|
|
15
|
+
- Show a modern terminal window with a MoneySiren slash-command home screen, provider status rows, and small usage/risk indicators.
|
|
16
|
+
- Use a restrained professional palette with teal as the brand accent, graphite text, warm amber warnings, and clear green success states.
|
|
17
|
+
- Avoid marketing hero art, decorative blobs, oversized typography, and purple/blue gradient dominance.
|
|
18
|
+
- Make spacing comfortable enough that controls do not touch, overlap, or wrap awkwardly.
|
|
19
|
+
|
|
20
|
+
Return a visual reference plus this theme JSON shape:
|
|
21
|
+
{
|
|
22
|
+
"version": 1,
|
|
23
|
+
"source": "image2-dashboard",
|
|
24
|
+
"ansi": {
|
|
25
|
+
"brand": "1;38;5;30",
|
|
26
|
+
"heading": "1;38;5;231",
|
|
27
|
+
"command": "38;5;35",
|
|
28
|
+
"muted": "38;5;244",
|
|
29
|
+
"warning": "38;5;214"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Apply the JSON locally with MONEYSIREN_CLI_THEME_FILE=<path-to-json>.`;
|
|
34
|
+
export async function runThemeCommand(args, context) {
|
|
35
|
+
const [subcommand, ...rest] = args;
|
|
36
|
+
if (subcommand === undefined) {
|
|
37
|
+
context.stderr(THEME_USAGE);
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
if (subcommand === "preview") {
|
|
41
|
+
if (rest.length > 0) {
|
|
42
|
+
context.stderr(THEME_USAGE);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
context.stdout(renderThemePreview(context));
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (subcommand === "image-prompt") {
|
|
49
|
+
if (rest.length > 0) {
|
|
50
|
+
context.stderr(THEME_USAGE);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
context.stdout(IMAGE_PROMPT);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
if (subcommand === "image-generate") {
|
|
57
|
+
return runImageGenerateCommand(rest, context);
|
|
58
|
+
}
|
|
59
|
+
context.stderr(THEME_USAGE);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
function renderThemePreview(context) {
|
|
63
|
+
const { theme } = context;
|
|
64
|
+
return [
|
|
65
|
+
`${theme.brand("MoneySiren")} CLI theme`,
|
|
66
|
+
`Source: ${theme.source}`,
|
|
67
|
+
"",
|
|
68
|
+
`${theme.heading("Heading")} ${theme.command("/sync openai")} ${theme.muted("muted metadata")} ${theme.warning("warning")}`,
|
|
69
|
+
"",
|
|
70
|
+
"Set MONEYSIREN_CLI_THEME=image2-dashboard for the bundled image-reference palette.",
|
|
71
|
+
"Set MONEYSIREN_CLI_THEME_FILE=<json> to apply a palette extracted from an image reference.",
|
|
72
|
+
].join("\n");
|
|
73
|
+
}
|
|
74
|
+
async function runImageGenerateCommand(args, context) {
|
|
75
|
+
const options = parseImageGenerateOptions(args, context.env);
|
|
76
|
+
const apiKey = context.env.OPENAI_API_KEY?.trim();
|
|
77
|
+
if (apiKey === undefined || apiKey.length === 0) {
|
|
78
|
+
context.stderr("OpenAI image generation requires OPENAI_API_KEY. The key is only read from env and is not persisted.");
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
const response = await context.fetch("https://api.openai.com/v1/images/generations", {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
authorization: `Bearer ${apiKey}`,
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
model: options.model,
|
|
89
|
+
prompt: IMAGE_PROMPT,
|
|
90
|
+
n: 1,
|
|
91
|
+
size: "1536x1024",
|
|
92
|
+
quality: "low",
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
context.stderr(`OpenAI image generation failed with status ${response.status}.`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
const payload = await response.json();
|
|
100
|
+
const imageBase64 = payload.data?.[0]?.b64_json;
|
|
101
|
+
if (typeof imageBase64 !== "string" || imageBase64.trim().length === 0) {
|
|
102
|
+
context.stderr("OpenAI image generation response did not include image data.");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
const imagePath = resolveCliPath(context.cwd, options.out);
|
|
106
|
+
const themePath = resolveCliPath(context.cwd, options.themeOut);
|
|
107
|
+
await writeLocalFile(imagePath, Buffer.from(imageBase64, "base64"));
|
|
108
|
+
await writeLocalFile(themePath, Buffer.from(JSON.stringify(themeFileFor(options.model), null, 2), "utf8"));
|
|
109
|
+
context.stdout("Generated MoneySiren CLI image theme reference.");
|
|
110
|
+
context.stdout(`Image: ${options.out}`);
|
|
111
|
+
context.stdout(`Theme: ${options.themeOut}`);
|
|
112
|
+
context.stdout(`Apply: MONEYSIREN_CLI_THEME_FILE=${options.themeOut} moneysiren theme preview`);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
function parseImageGenerateOptions(args, env) {
|
|
116
|
+
let out = DEFAULT_IMAGE_OUTPUT;
|
|
117
|
+
let themeOut = DEFAULT_THEME_OUTPUT;
|
|
118
|
+
let model = env.MONEYSIREN_IMAGE_MODEL?.trim() || DEFAULT_IMAGE_MODEL;
|
|
119
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
120
|
+
const arg = args[index];
|
|
121
|
+
const value = args[index + 1];
|
|
122
|
+
if (arg === "--out" && value !== undefined) {
|
|
123
|
+
out = value;
|
|
124
|
+
index += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (arg === "--theme-out" && value !== undefined) {
|
|
128
|
+
themeOut = value;
|
|
129
|
+
index += 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (arg === "--model" && value !== undefined) {
|
|
133
|
+
model = value;
|
|
134
|
+
index += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
throw new Error(THEME_USAGE);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
out: requireNonBlankPath(out, "--out"),
|
|
141
|
+
themeOut: requireNonBlankPath(themeOut, "--theme-out"),
|
|
142
|
+
model: requireSafeModelName(model),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function themeFileFor(model) {
|
|
146
|
+
return {
|
|
147
|
+
version: 1,
|
|
148
|
+
source: `image-generation:${model}`,
|
|
149
|
+
ansi: {
|
|
150
|
+
brand: "1;38;5;30",
|
|
151
|
+
heading: "1;38;5;231",
|
|
152
|
+
command: "38;5;35",
|
|
153
|
+
muted: "38;5;244",
|
|
154
|
+
warning: "38;5;214",
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async function writeLocalFile(path, content) {
|
|
159
|
+
await mkdir(dirname(path), {
|
|
160
|
+
recursive: true,
|
|
161
|
+
});
|
|
162
|
+
await writeFile(path, content);
|
|
163
|
+
}
|
|
164
|
+
function resolveCliPath(cwd, path) {
|
|
165
|
+
return isAbsolute(path) ? path : join(cwd, path);
|
|
166
|
+
}
|
|
167
|
+
function requireNonBlankPath(value, label) {
|
|
168
|
+
const trimmed = value.trim();
|
|
169
|
+
if (trimmed.length === 0) {
|
|
170
|
+
throw new Error(`${label} must not be blank.`);
|
|
171
|
+
}
|
|
172
|
+
return trimmed;
|
|
173
|
+
}
|
|
174
|
+
function requireSafeModelName(value) {
|
|
175
|
+
const trimmed = value.trim();
|
|
176
|
+
if (!/^[A-Za-z0-9._-]{1,80}$/.test(trimmed)) {
|
|
177
|
+
throw new Error("Image model name is invalid.");
|
|
178
|
+
}
|
|
179
|
+
return trimmed;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export function renderHomeScreen(input) {
|
|
2
|
+
const { theme, version } = input;
|
|
3
|
+
return [
|
|
4
|
+
`${theme.brand("MoneySiren")} ${theme.muted(version)}`,
|
|
5
|
+
"Local-first cloud/SaaS usage, status, and expected billing.",
|
|
6
|
+
"",
|
|
7
|
+
theme.heading("Slash commands"),
|
|
8
|
+
` ${theme.command("/help")} Show CLI usage and slash guide`,
|
|
9
|
+
` ${theme.command("/version")} Print the installed CLI version`,
|
|
10
|
+
` ${theme.command("/doctor")} Check local readiness without printing secrets`,
|
|
11
|
+
` ${theme.command("/install")} Choose CLI, web dashboard, and HUD components`,
|
|
12
|
+
` ${theme.command("/modes")} Show the CLI, web, and desktop modes`,
|
|
13
|
+
` ${theme.command("/init")} Create local SQLite storage`,
|
|
14
|
+
` ${theme.command("/dashboard")} Check the local dashboard API`,
|
|
15
|
+
` ${theme.command("/dashboard check")} Same as /dashboard`,
|
|
16
|
+
` ${theme.command("/summary json")} Print sanitized local summary JSON`,
|
|
17
|
+
` ${theme.command("/notify dry-run")} Preview a sanitized notification digest`,
|
|
18
|
+
` ${theme.command("/notify prefs")} List local notification defaults`,
|
|
19
|
+
` ${theme.command("/desktop status")} Check local runtime status`,
|
|
20
|
+
` ${theme.command("/theme preview")} Preview the active CLI image-reference theme`,
|
|
21
|
+
` ${theme.command("/sync mock")} Sync fake local review snapshots`,
|
|
22
|
+
` ${theme.command("/sync aws")} Sync AWS Cost Explorer snapshots`,
|
|
23
|
+
` ${theme.command("/sync openai")} Sync OpenAI usage/cost snapshots`,
|
|
24
|
+
` ${theme.command("/sync supabase")} Sync Supabase usage/health snapshots`,
|
|
25
|
+
` ${theme.command("/sync cloudflare")} Sync Cloudflare billing/usage snapshots`,
|
|
26
|
+
` ${theme.command("/report ko")} Render the Korean daily report`,
|
|
27
|
+
` ${theme.command("/quit")} Exit the slash prompt`,
|
|
28
|
+
"",
|
|
29
|
+
theme.heading("Classic CLI"),
|
|
30
|
+
" moneysiren doctor",
|
|
31
|
+
" moneysiren install",
|
|
32
|
+
" moneysiren install --status",
|
|
33
|
+
" moneysiren modes",
|
|
34
|
+
" moneysiren init",
|
|
35
|
+
" moneysiren serve [--port <port>]",
|
|
36
|
+
" moneysiren open",
|
|
37
|
+
" moneysiren sync --provider mock",
|
|
38
|
+
" moneysiren summary --json",
|
|
39
|
+
" moneysiren notify once --dry-run",
|
|
40
|
+
" moneysiren notify prefs list",
|
|
41
|
+
" moneysiren desktop status",
|
|
42
|
+
" moneysiren report daily --lang ko",
|
|
43
|
+
" moneysiren dashboard check",
|
|
44
|
+
" moneysiren theme preview",
|
|
45
|
+
" moneysiren theme image-prompt",
|
|
46
|
+
" moneysiren theme image-generate",
|
|
47
|
+
"",
|
|
48
|
+
theme.warning("Security"),
|
|
49
|
+
" Home/help does not call provider APIs, read secret values, create .env, or enable telemetry.",
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
export function renderHelpScreen(version) {
|
|
53
|
+
return `MoneySiren ${version}
|
|
54
|
+
|
|
55
|
+
Local-first cloud/SaaS usage, status, and expected billing dashboard.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
moneysiren
|
|
59
|
+
moneysiren --help
|
|
60
|
+
moneysiren --version
|
|
61
|
+
moneysiren init
|
|
62
|
+
moneysiren install [--status|--all|--cli|--web|--hud|--no-cli|--no-web|--no-hud]
|
|
63
|
+
moneysiren doctor
|
|
64
|
+
moneysiren modes
|
|
65
|
+
moneysiren dashboard check [--url <local-dashboard-url>]
|
|
66
|
+
moneysiren serve [--port <port>]
|
|
67
|
+
moneysiren open
|
|
68
|
+
moneysiren theme preview
|
|
69
|
+
moneysiren theme image-prompt
|
|
70
|
+
moneysiren theme image-generate [--out <png> --theme-out <json> --model <model>]
|
|
71
|
+
moneysiren sync --provider <mock|aws|openai|supabase|cloudflare>
|
|
72
|
+
moneysiren summary --json
|
|
73
|
+
moneysiren notify once --dry-run
|
|
74
|
+
moneysiren notify prefs list
|
|
75
|
+
moneysiren desktop status
|
|
76
|
+
moneysiren report daily --lang ko [--send slack]
|
|
77
|
+
|
|
78
|
+
Slash commands:
|
|
79
|
+
moneysiren /help
|
|
80
|
+
moneysiren /version
|
|
81
|
+
moneysiren /doctor
|
|
82
|
+
moneysiren /install
|
|
83
|
+
moneysiren /modes
|
|
84
|
+
moneysiren /init
|
|
85
|
+
moneysiren /dashboard
|
|
86
|
+
moneysiren /dashboard check
|
|
87
|
+
moneysiren /summary json
|
|
88
|
+
moneysiren /notify dry-run
|
|
89
|
+
moneysiren /notify prefs
|
|
90
|
+
moneysiren /desktop status
|
|
91
|
+
moneysiren /theme <preview|image-prompt|image-generate>
|
|
92
|
+
moneysiren /sync <mock|aws|openai|supabase|cloudflare>
|
|
93
|
+
moneysiren /report ko
|
|
94
|
+
moneysiren /quit
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=home.js.map
|