gloomberb 0.9.2 → 0.9.4
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/README.md +24 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +20 -8
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/capabilities/registry.ts +1 -0
- package/src/capabilities/types.ts +23 -0
- package/src/cli/commands/api.ts +119 -0
- package/src/cli/commands/automation.ts +173 -0
- package/src/cli/commands/command-utils.ts +53 -0
- package/src/cli/commands/market.ts +527 -0
- package/src/cli/commands/overview.ts +222 -0
- package/src/cli/commands/search.ts +39 -0
- package/src/cli/commands/system.ts +553 -0
- package/src/cli/commands/ticker.ts +104 -12
- package/src/cli/context.ts +35 -2
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/entry.ts +51 -0
- package/src/cli/errors.ts +56 -5
- package/src/cli/index.ts +73 -11
- package/src/cli/options.test.ts +38 -0
- package/src/cli/options.ts +95 -0
- package/src/cli/pane-functions/index.ts +10 -1
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/cli/registry.test.ts +40 -0
- package/src/cli/registry.ts +46 -5
- package/src/cli/result.test.ts +82 -0
- package/src/cli/result.ts +149 -0
- package/src/cli/types.ts +11 -0
- package/src/cli.test.ts +11 -11
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
- package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
- package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
- package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
- package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
- package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
- package/src/components/chart/core/pane-settings.test.ts +27 -0
- package/src/components/chart/core/pane-settings.ts +60 -1
- package/src/components/chart/core/pointer.ts +34 -6
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
- package/src/components/chart/stock-chart/settings.ts +7 -3
- package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
- package/src/components/command-bar/surface/pane.test.tsx +76 -0
- package/src/components/data-table/view.tsx +13 -2
- package/src/components/feed-data-table/stack-view.tsx +9 -1
- package/src/components/index.ts +1 -1
- package/src/components/input-search-bar.test.tsx +103 -0
- package/src/components/input-search-bar.tsx +16 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +40 -4
- package/src/components/layout/pane/index.tsx +3 -1
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/cursor-occlusion.ts +196 -0
- package/src/components/layout/shell/drag/runtime.ts +28 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +487 -4
- package/src/components/layout/shell/index.tsx +165 -4
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +34 -16
- package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
- package/src/components/layout/shell/shortcuts.ts +4 -0
- package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/shell/window-mode/index.ts +11 -3
- package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
- package/src/components/layout/status-bar.test.tsx +111 -1
- package/src/components/layout/status-bar.tsx +37 -7
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-edit/mode.test.ts +76 -5
- package/src/components/layout/window-edit/mode.ts +47 -2
- package/src/components/layout/window-edit/presentation.ts +2 -1
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/price-performance.tsx +9 -1
- package/src/components/table-view-shared.tsx +12 -4
- package/src/components/ui/data-table/opentui/index.tsx +25 -12
- package/src/components/ui/data-table/types.ts +4 -0
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +26 -10
- package/src/components/ui/ui.test.tsx +87 -0
- package/src/core/app-services.ts +7 -4
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/data/config/store/index.ts +1 -2
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
- package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
- package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +3 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/shared/read-state.ts +43 -0
- package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
- package/src/plugins/builtin/substack/article-detail.tsx +39 -15
- package/src/plugins/builtin/substack/article-stack.tsx +6 -2
- package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
- package/src/plugins/builtin/substack/pane.tsx +8 -3
- package/src/plugins/builtin/substack/read-state.ts +55 -0
- package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager/queries.ts +8 -2
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/controller/index.ts +2 -0
- package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +75 -0
- package/src/plugins/prediction-markets/pane.tsx +71 -23
- package/src/plugins/prediction-markets/test-helpers.tsx +42 -23
- package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/index.ts +13 -0
- package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
- package/src/renderers/electrobun/bun/window/focus.ts +5 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
- package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/host/text.test.ts +22 -0
- package/src/renderers/electrobun/view/host/text.tsx +24 -20
- package/src/renderers/electrobun/view/styles.css +11 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/plugin.ts +28 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { VERSION } from "../../version";
|
|
4
|
+
import { saveConfig } from "../../data/config/store";
|
|
5
|
+
import { NotesFiles } from "../../plugins/builtin/notes/files";
|
|
6
|
+
import { createAlert, deserializeAlerts, serializeAlerts } from "../../plugins/builtin/alerts/alert-engine";
|
|
7
|
+
import type { AlertCondition } from "../../plugins/builtin/alerts/types";
|
|
8
|
+
import type { CliCommandDef } from "../../types/plugin";
|
|
9
|
+
import { debugLog, type LogLevel } from "../../utils/debug-log";
|
|
10
|
+
import { parsePositiveInt, requireArg, takeOption } from "./command-utils";
|
|
11
|
+
|
|
12
|
+
const ALERTS_PLUGIN_ID = "alerts";
|
|
13
|
+
const ALERTS_KEY = "alerts";
|
|
14
|
+
const LOG_LEVELS = new Set<LogLevel>(["debug", "info", "warn", "error"]);
|
|
15
|
+
|
|
16
|
+
function commandRows(commands: CliCommandDef[]) {
|
|
17
|
+
return commands.map((command) => ({
|
|
18
|
+
name: command.name,
|
|
19
|
+
aliases: command.aliases?.join(",") ?? "",
|
|
20
|
+
description: command.description,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseLogLevel(value: string | undefined): LogLevel | undefined {
|
|
25
|
+
return value && LOG_LEVELS.has(value as LogLevel) ? value as LogLevel : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resourceCacheStats(services: Awaited<ReturnType<Parameters<CliCommandDef["execute"]>[1]["initServices"]>>) {
|
|
29
|
+
const row = services.persistence.database.connection
|
|
30
|
+
.query<{ count: number; size: number; expired: number; stale: number }, []>(
|
|
31
|
+
`SELECT COUNT(*) as count,
|
|
32
|
+
COALESCE(SUM(size_bytes), 0) as size,
|
|
33
|
+
SUM(CASE WHEN expires_at < strftime('%s','now') * 1000 THEN 1 ELSE 0 END) as expired,
|
|
34
|
+
SUM(CASE WHEN stale_at < strftime('%s','now') * 1000 THEN 1 ELSE 0 END) as stale
|
|
35
|
+
FROM resource_cache`,
|
|
36
|
+
)
|
|
37
|
+
.get();
|
|
38
|
+
return {
|
|
39
|
+
entries: row?.count ?? 0,
|
|
40
|
+
sizeBytes: row?.size ?? 0,
|
|
41
|
+
staleEntries: row?.stale ?? 0,
|
|
42
|
+
expiredEntries: row?.expired ?? 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createSystemCliCommands(allCommands: () => CliCommandDef[]): CliCommandDef[] {
|
|
47
|
+
const versionCommand: CliCommandDef = {
|
|
48
|
+
name: "version",
|
|
49
|
+
aliases: ["--version", "-v"],
|
|
50
|
+
description: "Print version and runtime information",
|
|
51
|
+
execute: async (_args, ctx) => {
|
|
52
|
+
let dataDir: string | null = null;
|
|
53
|
+
try {
|
|
54
|
+
const config = await ctx.initConfigData();
|
|
55
|
+
dataDir = config.dataDir;
|
|
56
|
+
config.persistence.close();
|
|
57
|
+
} catch {
|
|
58
|
+
dataDir = null;
|
|
59
|
+
}
|
|
60
|
+
ctx.printResult({
|
|
61
|
+
data: [{
|
|
62
|
+
version: VERSION,
|
|
63
|
+
bun: typeof Bun === "undefined" ? null : Bun.version,
|
|
64
|
+
platform: process.platform,
|
|
65
|
+
arch: process.arch,
|
|
66
|
+
dataDir,
|
|
67
|
+
}],
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const doctorCommand: CliCommandDef = {
|
|
73
|
+
name: "doctor",
|
|
74
|
+
description: "Check local CLI runtime, config, plugins, and capability health",
|
|
75
|
+
execute: async (_args, ctx) => {
|
|
76
|
+
const checks: Array<{ check: string; status: string; detail: string }> = [];
|
|
77
|
+
let services: Awaited<ReturnType<typeof ctx.initServices>> | null = null;
|
|
78
|
+
try {
|
|
79
|
+
services = await ctx.initServices();
|
|
80
|
+
checks.push({ check: "config", status: "ok", detail: services.dataDir });
|
|
81
|
+
checks.push({ check: "database", status: "ok", detail: join(services.dataDir, ".gloomberb-cache.db") });
|
|
82
|
+
checks.push({ check: "plugins", status: "ok", detail: String(services.services.pluginRegistry.allPlugins.size) });
|
|
83
|
+
checks.push({ check: "capabilities", status: "ok", detail: String(services.services.pluginRegistry.capabilities.manifests().length) });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
checks.push({ check: "runtime", status: "error", detail: error instanceof Error ? error.message : String(error) });
|
|
86
|
+
} finally {
|
|
87
|
+
services?.destroy();
|
|
88
|
+
}
|
|
89
|
+
ctx.printResult({ data: checks }, {
|
|
90
|
+
columns: [
|
|
91
|
+
{ key: "check", header: "Check" },
|
|
92
|
+
{ key: "status", header: "Status" },
|
|
93
|
+
{ key: "detail", header: "Detail" },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const configCommand: CliCommandDef = {
|
|
100
|
+
name: "config",
|
|
101
|
+
description: "Inspect and update local configuration",
|
|
102
|
+
help: { usage: ["config list", "config get <key>", "config set <key> <value>"] },
|
|
103
|
+
execute: async (args, ctx) => {
|
|
104
|
+
const action = args[0] ?? "list";
|
|
105
|
+
const context = await ctx.initConfigData();
|
|
106
|
+
try {
|
|
107
|
+
const safeConfig: Record<string, unknown> = {
|
|
108
|
+
dataDir: context.config.dataDir,
|
|
109
|
+
baseCurrency: context.config.baseCurrency,
|
|
110
|
+
refreshIntervalMinutes: context.config.refreshIntervalMinutes,
|
|
111
|
+
theme: context.config.theme,
|
|
112
|
+
disabledPlugins: context.config.disabledPlugins,
|
|
113
|
+
disabledSources: context.config.disabledSources,
|
|
114
|
+
portfolios: context.config.portfolios.length,
|
|
115
|
+
watchlists: context.config.watchlists.length,
|
|
116
|
+
brokerInstances: context.config.brokerInstances.length,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (action === "list") {
|
|
120
|
+
ctx.printResult({ data: safeConfig });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (action === "get") {
|
|
124
|
+
const key = requireArg(args[1], "Usage: gloomberb config get <key>", ctx);
|
|
125
|
+
ctx.printResult({
|
|
126
|
+
data: {
|
|
127
|
+
key,
|
|
128
|
+
value: Object.prototype.hasOwnProperty.call(safeConfig, key) ? safeConfig[key] : null,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (action === "set") {
|
|
134
|
+
const key = requireArg(args[1], "Usage: gloomberb config set <key> <value>", ctx);
|
|
135
|
+
const value = requireArg(args[2], "Usage: gloomberb config set <key> <value>", ctx);
|
|
136
|
+
const editable = new Set(["baseCurrency", "refreshIntervalMinutes", "theme", "valueFlashingEnabled"]);
|
|
137
|
+
if (!editable.has(key)) ctx.fail(`Config key "${key}" is not editable from the CLI.`);
|
|
138
|
+
const parsedValue = key === "refreshIntervalMinutes"
|
|
139
|
+
? Number(value)
|
|
140
|
+
: key === "valueFlashingEnabled"
|
|
141
|
+
? value === "true"
|
|
142
|
+
: value;
|
|
143
|
+
const nextConfig = { ...context.config, [key]: parsedValue };
|
|
144
|
+
if (!ctx.cliOptions.dryRun) await saveConfig(nextConfig);
|
|
145
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, key, value: parsedValue } });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
ctx.fail("Usage: gloomberb config list|get|set");
|
|
149
|
+
} finally {
|
|
150
|
+
context.persistence.close();
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const cacheCommand: CliCommandDef = {
|
|
156
|
+
name: "cache",
|
|
157
|
+
description: "Inspect or clear the resource cache",
|
|
158
|
+
help: { usage: ["cache status", "cache clear [namespace]"] },
|
|
159
|
+
execute: async (args, ctx) => {
|
|
160
|
+
const action = args[0] ?? "status";
|
|
161
|
+
const services = await ctx.initServices();
|
|
162
|
+
try {
|
|
163
|
+
if (action === "status") {
|
|
164
|
+
ctx.printResult({ data: [resourceCacheStats(services)] });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (action === "clear") {
|
|
168
|
+
const namespace = args[1];
|
|
169
|
+
const before = resourceCacheStats(services);
|
|
170
|
+
if (!ctx.cliOptions.dryRun) services.persistence.resources.clear(namespace);
|
|
171
|
+
const after = ctx.cliOptions.dryRun ? before : resourceCacheStats(services);
|
|
172
|
+
ctx.printResult({ data: [{ changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, namespace: namespace ?? "all", before: before.entries, after: after.entries }] });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
ctx.fail("Usage: gloomberb cache status|clear");
|
|
176
|
+
} finally {
|
|
177
|
+
services.destroy();
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const providerCommand: CliCommandDef = {
|
|
183
|
+
name: "provider",
|
|
184
|
+
aliases: ["providers"],
|
|
185
|
+
description: "Inspect provider/source capability status",
|
|
186
|
+
help: { usage: ["provider status"] },
|
|
187
|
+
execute: async (_args, ctx) => {
|
|
188
|
+
const services = await ctx.initServices();
|
|
189
|
+
try {
|
|
190
|
+
const disabledSources = new Set(services.config.disabledSources ?? []);
|
|
191
|
+
const rows = services.services.pluginRegistry.capabilities.manifests().map((manifest) => ({
|
|
192
|
+
id: manifest.sourceId ?? manifest.id,
|
|
193
|
+
capability: manifest.id,
|
|
194
|
+
kind: manifest.kind,
|
|
195
|
+
enabled: !disabledSources.has(manifest.sourceId ?? manifest.id),
|
|
196
|
+
operations: manifest.operations.map((operation) => operation.id).join(","),
|
|
197
|
+
}));
|
|
198
|
+
ctx.printResult({ data: rows }, {
|
|
199
|
+
columns: [
|
|
200
|
+
{ key: "id", header: "Source" },
|
|
201
|
+
{ key: "kind", header: "Kind" },
|
|
202
|
+
{ key: "enabled", header: "Enabled" },
|
|
203
|
+
{ key: "operations", header: "Operations" },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
} finally {
|
|
207
|
+
services.destroy();
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const pluginCommand: CliCommandDef = {
|
|
213
|
+
name: "plugin",
|
|
214
|
+
description: "Inspect, enable, disable, or doctor plugins",
|
|
215
|
+
help: { usage: ["plugin list", "plugin info <id>", "plugin enable <id>", "plugin disable <id>", "plugin doctor"] },
|
|
216
|
+
execute: async (args, ctx) => {
|
|
217
|
+
const action = args[0] ?? "list";
|
|
218
|
+
const services = await ctx.initServices();
|
|
219
|
+
try {
|
|
220
|
+
const pluginRows = () => [...services.services.pluginRegistry.allPlugins.values()].map((plugin) => ({
|
|
221
|
+
id: plugin.id,
|
|
222
|
+
name: plugin.name,
|
|
223
|
+
version: plugin.version,
|
|
224
|
+
enabled: !services.config.disabledPlugins.includes(plugin.id),
|
|
225
|
+
panes: services.services.pluginRegistry.getPluginPaneIds(plugin.id).length,
|
|
226
|
+
templates: services.services.pluginRegistry.getPluginPaneTemplateIds(plugin.id).length,
|
|
227
|
+
capabilities: services.services.pluginRegistry.capabilities.manifests().filter((manifest) => (
|
|
228
|
+
services.services.pluginRegistry.getCapabilityPluginId(manifest.id) === plugin.id
|
|
229
|
+
)).length,
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
if (action === "list" || action === "doctor") {
|
|
233
|
+
ctx.printResult({ data: pluginRows() });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (action === "info") {
|
|
237
|
+
const id = requireArg(args[1], "Usage: gloomberb plugin info <id>", ctx);
|
|
238
|
+
const row = pluginRows().find((plugin) => plugin.id === id);
|
|
239
|
+
if (!row) ctx.fail(`Plugin "${id}" is not available.`);
|
|
240
|
+
ctx.printResult({ data: row });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (action === "enable" || action === "disable") {
|
|
244
|
+
const id = requireArg(args[1], `Usage: gloomberb plugin ${action} <id>`, ctx);
|
|
245
|
+
const disabled = new Set(services.config.disabledPlugins ?? []);
|
|
246
|
+
const before = disabled.has(id);
|
|
247
|
+
if (action === "enable") disabled.delete(id);
|
|
248
|
+
else disabled.add(id);
|
|
249
|
+
const nextConfig = { ...services.config, disabledPlugins: [...disabled] };
|
|
250
|
+
if (!ctx.cliOptions.dryRun) await saveConfig(nextConfig);
|
|
251
|
+
ctx.printResult({ data: { changed: before !== disabled.has(id) && !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, id, enabled: !disabled.has(id) } });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
ctx.fail("Usage: gloomberb plugin list|info|enable|disable|doctor");
|
|
255
|
+
} finally {
|
|
256
|
+
services.destroy();
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const layoutCommand: CliCommandDef = {
|
|
262
|
+
name: "layout",
|
|
263
|
+
description: "Inspect saved layouts",
|
|
264
|
+
execute: async (_args, ctx) => {
|
|
265
|
+
const config = await ctx.initConfigData();
|
|
266
|
+
try {
|
|
267
|
+
const rows = config.config.layouts.map((layout, index) => ({
|
|
268
|
+
index,
|
|
269
|
+
active: index === config.config.activeLayoutIndex,
|
|
270
|
+
name: layout.name,
|
|
271
|
+
panes: layout.layout.instances.length,
|
|
272
|
+
floating: layout.layout.floating.length,
|
|
273
|
+
detached: layout.layout.detached.length,
|
|
274
|
+
}));
|
|
275
|
+
ctx.printResult({ data: rows });
|
|
276
|
+
} finally {
|
|
277
|
+
config.persistence.close();
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const paneCommand: CliCommandDef = {
|
|
283
|
+
name: "pane",
|
|
284
|
+
description: "Inspect pane and pane-template inventory",
|
|
285
|
+
help: { usage: ["pane list"] },
|
|
286
|
+
execute: async (_args, ctx) => {
|
|
287
|
+
const services = await ctx.initServices();
|
|
288
|
+
try {
|
|
289
|
+
const rows = [
|
|
290
|
+
...[...services.services.pluginRegistry.panes.entries()].map(([id, pane]) => ({
|
|
291
|
+
kind: "pane",
|
|
292
|
+
id,
|
|
293
|
+
name: pane.name,
|
|
294
|
+
owner: "",
|
|
295
|
+
})),
|
|
296
|
+
...[...services.services.pluginRegistry.paneTemplates.entries()].map(([id, template]) => ({
|
|
297
|
+
kind: "template",
|
|
298
|
+
id,
|
|
299
|
+
name: template.label,
|
|
300
|
+
owner: services.services.pluginRegistry.getPaneTemplatePluginId(id) ?? "",
|
|
301
|
+
})),
|
|
302
|
+
];
|
|
303
|
+
ctx.printResult({ data: rows }, {
|
|
304
|
+
columns: [
|
|
305
|
+
{ key: "kind", header: "Kind" },
|
|
306
|
+
{ key: "id", header: "ID" },
|
|
307
|
+
{ key: "name", header: "Name" },
|
|
308
|
+
{ key: "owner", header: "Owner" },
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
} finally {
|
|
312
|
+
services.destroy();
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const notesCommand: CliCommandDef = {
|
|
318
|
+
name: "notes",
|
|
319
|
+
description: "Read and mutate ticker or quick notes",
|
|
320
|
+
help: { usage: ["notes show <symbol>", "notes set <symbol> TEXT", "notes delete <symbol>", "notes quick list"] },
|
|
321
|
+
execute: async (args, ctx) => {
|
|
322
|
+
const config = await ctx.initConfigData();
|
|
323
|
+
const notes = new NotesFiles(config.dataDir);
|
|
324
|
+
try {
|
|
325
|
+
const action = args[0] ?? "list";
|
|
326
|
+
if (action === "quick") {
|
|
327
|
+
const subaction = args[1] ?? "list";
|
|
328
|
+
if (subaction !== "list") ctx.fail("Usage: gloomberb notes quick list");
|
|
329
|
+
const entries = await notes.loadQuickNotesIndex();
|
|
330
|
+
ctx.printResult({ data: entries });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (action === "show") {
|
|
334
|
+
const symbol = requireArg(args[1]?.toUpperCase(), "Usage: gloomberb notes show <symbol>", ctx);
|
|
335
|
+
ctx.printResult({ data: { symbol, text: await notes.load(symbol) } });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (action === "set") {
|
|
339
|
+
const symbol = requireArg(args[1]?.toUpperCase(), "Usage: gloomberb notes set <symbol> TEXT", ctx);
|
|
340
|
+
const text = args.slice(2).join(" ");
|
|
341
|
+
if (!ctx.cliOptions.dryRun) await notes.save(symbol, text);
|
|
342
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, symbol, bytes: text.length } });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (action === "delete" || action === "rm") {
|
|
346
|
+
const symbol = requireArg(args[1]?.toUpperCase(), "Usage: gloomberb notes delete <symbol>", ctx);
|
|
347
|
+
if (!ctx.cliOptions.dryRun) await notes.delete(symbol);
|
|
348
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, symbol } });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
ctx.fail("Usage: gloomberb notes show|set|delete|quick");
|
|
352
|
+
} finally {
|
|
353
|
+
config.persistence.close();
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const alertsCommand: CliCommandDef = {
|
|
359
|
+
name: "alerts",
|
|
360
|
+
aliases: ["alert"],
|
|
361
|
+
description: "List and manage price alerts",
|
|
362
|
+
help: { usage: ["alerts list", "alerts add <symbol> <above|below|crosses> <price>", "alerts delete <id>", "alerts rearm <id>"] },
|
|
363
|
+
execute: async (args, ctx) => {
|
|
364
|
+
const action = args[0] ?? "list";
|
|
365
|
+
const config = await ctx.initConfigData();
|
|
366
|
+
try {
|
|
367
|
+
const raw = config.config.pluginConfig[ALERTS_PLUGIN_ID]?.[ALERTS_KEY];
|
|
368
|
+
const alerts = deserializeAlerts(typeof raw === "string" ? raw : "[]");
|
|
369
|
+
const saveAlerts = async (nextAlerts: typeof alerts) => {
|
|
370
|
+
const nextConfig = {
|
|
371
|
+
...config.config,
|
|
372
|
+
pluginConfig: {
|
|
373
|
+
...config.config.pluginConfig,
|
|
374
|
+
[ALERTS_PLUGIN_ID]: {
|
|
375
|
+
...(config.config.pluginConfig[ALERTS_PLUGIN_ID] ?? {}),
|
|
376
|
+
[ALERTS_KEY]: serializeAlerts(nextAlerts),
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
if (!ctx.cliOptions.dryRun) await saveConfig(nextConfig);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
if (action === "list") {
|
|
384
|
+
ctx.printResult({ data: alerts });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (action === "add") {
|
|
388
|
+
const symbol = requireArg(args[1]?.toUpperCase(), "Usage: gloomberb alerts add <symbol> <above|below|crosses> <price>", ctx);
|
|
389
|
+
const condition = requireArg(args[2], "Usage: gloomberb alerts add <symbol> <above|below|crosses> <price>", ctx) as AlertCondition;
|
|
390
|
+
if (!["above", "below", "crosses"].includes(condition)) ctx.fail("Condition must be above, below, or crosses.");
|
|
391
|
+
const price = Number(requireArg(args[3], "Usage: gloomberb alerts add <symbol> <above|below|crosses> <price>", ctx));
|
|
392
|
+
if (!Number.isFinite(price)) ctx.fail("Alert price must be a finite number.");
|
|
393
|
+
const alert = createAlert(symbol, condition, price);
|
|
394
|
+
await saveAlerts([...alerts, alert]);
|
|
395
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert } });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (action === "delete" || action === "rm") {
|
|
399
|
+
const id = requireArg(args[1], "Usage: gloomberb alerts delete <id>", ctx);
|
|
400
|
+
const next = alerts.filter((alert) => alert.id !== id);
|
|
401
|
+
await saveAlerts(next);
|
|
402
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun && next.length !== alerts.length, dryRun: ctx.cliOptions.dryRun, id } });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (action === "rearm") {
|
|
406
|
+
const id = requireArg(args[1], "Usage: gloomberb alerts rearm <id>", ctx);
|
|
407
|
+
const next = alerts.map((alert) => alert.id === id ? { ...alert, status: "active" as const, triggeredAt: undefined } : alert);
|
|
408
|
+
await saveAlerts(next);
|
|
409
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, id } });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
ctx.fail("Usage: gloomberb alerts list|add|delete|rearm");
|
|
413
|
+
} finally {
|
|
414
|
+
config.persistence.close();
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const debugCommand: CliCommandDef = {
|
|
420
|
+
name: "debug",
|
|
421
|
+
description: "Inspect, export, clear, or tail in-memory debug logs",
|
|
422
|
+
help: { usage: ["debug logs", "debug export [--output path]", "debug clear", "debug tail [--limit n]"] },
|
|
423
|
+
execute: async (rawArgs, ctx) => {
|
|
424
|
+
const args = [...rawArgs];
|
|
425
|
+
const action = args[0] ?? "logs";
|
|
426
|
+
const source = takeOption(args, "--source");
|
|
427
|
+
const level = parseLogLevel(takeOption(args, "--level"));
|
|
428
|
+
if (action === "clear") {
|
|
429
|
+
debugLog.clear();
|
|
430
|
+
ctx.printResult({ data: { changed: true } });
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (action === "export") {
|
|
434
|
+
const output = takeOption(args, "--output");
|
|
435
|
+
const text = debugLog.exportAsText({ source, level });
|
|
436
|
+
if (output) writeFileSync(output, text);
|
|
437
|
+
ctx.printResult({ data: { output: output ?? null, bytes: text.length, text: output ? undefined : text } });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const entries = debugLog.getEntries({ source, level }).slice(-(ctx.cliOptions.limit ?? 50));
|
|
441
|
+
ctx.printResult({ data: entries }, {
|
|
442
|
+
columns: [
|
|
443
|
+
{ key: "id", header: "ID", align: "right" },
|
|
444
|
+
{ key: "timestamp", header: "Time", value: (row) => new Date(Number(row.timestamp)).toISOString() },
|
|
445
|
+
{ key: "level", header: "Level" },
|
|
446
|
+
{ key: "source", header: "Source" },
|
|
447
|
+
{ key: "message", header: "Message" },
|
|
448
|
+
],
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const changelogCommand: CliCommandDef = {
|
|
454
|
+
name: "changelog",
|
|
455
|
+
description: "Print local changelog or release notes when available",
|
|
456
|
+
execute: async (args, ctx) => {
|
|
457
|
+
const limit = parsePositiveInt(args[0], ctx.cliOptions.limit ?? 80, "Line count", ctx);
|
|
458
|
+
const candidates = ["CHANGELOG.md", "CHANGELOG", "RELEASE_NOTES.md", "README.md"];
|
|
459
|
+
const found = candidates.find((candidate) => existsSync(candidate));
|
|
460
|
+
const text = found ? readFileSync(found, "utf8").split("\n").slice(0, limit).join("\n") : "";
|
|
461
|
+
ctx.printResult({ data: { path: found ?? null, text, version: VERSION } });
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const commandCatalogCommand: CliCommandDef = {
|
|
466
|
+
name: "command",
|
|
467
|
+
aliases: ["commands"],
|
|
468
|
+
description: "List registered first-class CLI commands",
|
|
469
|
+
execute: (_args, ctx) => {
|
|
470
|
+
ctx.printResult({ data: commandRows(allCommands()) }, {
|
|
471
|
+
columns: [
|
|
472
|
+
{ key: "name", header: "Command" },
|
|
473
|
+
{ key: "aliases", header: "Aliases" },
|
|
474
|
+
{ key: "description", header: "Description" },
|
|
475
|
+
],
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const coverageCommand: CliCommandDef = {
|
|
481
|
+
name: "coverage",
|
|
482
|
+
description: "Show CLI coverage for panes, templates, capabilities, and deferred exceptions",
|
|
483
|
+
execute: async (_args, ctx) => {
|
|
484
|
+
const commandNames = new Set(allCommands().map((command) => command.name));
|
|
485
|
+
const services = await ctx.initServices();
|
|
486
|
+
try {
|
|
487
|
+
const rows = [
|
|
488
|
+
...[...services.services.pluginRegistry.panes.entries()].map(([id, pane]) => ({
|
|
489
|
+
surface: "pane",
|
|
490
|
+
id,
|
|
491
|
+
label: pane.name,
|
|
492
|
+
coverage: commandNames.has(id) ? "first-class" : "visual-only",
|
|
493
|
+
command: commandNames.has(id) ? id : "fn/shot",
|
|
494
|
+
})),
|
|
495
|
+
...[...services.services.pluginRegistry.paneTemplates.entries()].map(([id, template]) => {
|
|
496
|
+
const direct = [id, template.paneId, template.shortcut?.prefix?.toLowerCase()]
|
|
497
|
+
.filter((value): value is string => !!value)
|
|
498
|
+
.find((value) => commandNames.has(value));
|
|
499
|
+
return {
|
|
500
|
+
surface: "template",
|
|
501
|
+
id,
|
|
502
|
+
label: template.label,
|
|
503
|
+
coverage: direct ? "first-class" : "visual-only",
|
|
504
|
+
command: direct ?? "fn/shot",
|
|
505
|
+
};
|
|
506
|
+
}),
|
|
507
|
+
...services.services.pluginRegistry.capabilities.manifests().map((manifest) => ({
|
|
508
|
+
surface: "capability",
|
|
509
|
+
id: manifest.id,
|
|
510
|
+
label: manifest.name,
|
|
511
|
+
coverage: "api",
|
|
512
|
+
command: "api list|get|invoke|subscribe",
|
|
513
|
+
})),
|
|
514
|
+
...["auth", "account-management", "chat"].map((id) => ({
|
|
515
|
+
surface: "deferred",
|
|
516
|
+
id,
|
|
517
|
+
label: id,
|
|
518
|
+
coverage: "deferred",
|
|
519
|
+
command: "",
|
|
520
|
+
})),
|
|
521
|
+
];
|
|
522
|
+
ctx.printResult({ data: rows }, {
|
|
523
|
+
columns: [
|
|
524
|
+
{ key: "surface", header: "Surface" },
|
|
525
|
+
{ key: "id", header: "ID" },
|
|
526
|
+
{ key: "coverage", header: "Coverage" },
|
|
527
|
+
{ key: "command", header: "Command" },
|
|
528
|
+
{ key: "label", header: "Label" },
|
|
529
|
+
],
|
|
530
|
+
});
|
|
531
|
+
} finally {
|
|
532
|
+
services.destroy();
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
return [
|
|
538
|
+
versionCommand,
|
|
539
|
+
doctorCommand,
|
|
540
|
+
configCommand,
|
|
541
|
+
cacheCommand,
|
|
542
|
+
providerCommand,
|
|
543
|
+
pluginCommand,
|
|
544
|
+
layoutCommand,
|
|
545
|
+
paneCommand,
|
|
546
|
+
notesCommand,
|
|
547
|
+
alertsCommand,
|
|
548
|
+
debugCommand,
|
|
549
|
+
changelogCommand,
|
|
550
|
+
commandCatalogCommand,
|
|
551
|
+
coverageCommand,
|
|
552
|
+
];
|
|
553
|
+
}
|