gloomberb 0.9.2 → 0.9.3

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.
Files changed (111) hide show
  1. package/README.md +23 -2
  2. package/bin/gloomberb +1 -1
  3. package/package.json +1 -1
  4. package/src/app.tsx +10 -7
  5. package/src/capabilities/registry.ts +1 -0
  6. package/src/capabilities/types.ts +23 -0
  7. package/src/cli/commands/api.ts +119 -0
  8. package/src/cli/commands/automation.ts +173 -0
  9. package/src/cli/commands/command-utils.ts +53 -0
  10. package/src/cli/commands/market.ts +527 -0
  11. package/src/cli/commands/overview.ts +222 -0
  12. package/src/cli/commands/search.ts +39 -0
  13. package/src/cli/commands/system.ts +553 -0
  14. package/src/cli/commands/ticker.ts +104 -12
  15. package/src/cli/context.ts +35 -2
  16. package/src/cli/entry.ts +51 -0
  17. package/src/cli/errors.ts +56 -5
  18. package/src/cli/index.ts +73 -11
  19. package/src/cli/options.test.ts +38 -0
  20. package/src/cli/options.ts +95 -0
  21. package/src/cli/pane-functions/index.ts +10 -1
  22. package/src/cli/registry.test.ts +40 -0
  23. package/src/cli/registry.ts +46 -5
  24. package/src/cli/result.test.ts +82 -0
  25. package/src/cli/result.ts +149 -0
  26. package/src/cli/types.ts +11 -0
  27. package/src/cli.test.ts +11 -11
  28. package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
  29. package/src/components/chart/comparison-stock-chart/legend.tsx +4 -0
  30. package/src/components/chart/comparison-stock-chart/render-data.ts +37 -8
  31. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +19 -2
  32. package/src/components/chart/comparison-stock-chart/sources.ts +8 -4
  33. package/src/components/chart/comparison-stock-chart/toolbar.tsx +13 -3
  34. package/src/components/chart/comparison-stock-chart/view.tsx +12 -2
  35. package/src/components/chart/core/pane-settings.test.ts +27 -0
  36. package/src/components/chart/core/pane-settings.ts +60 -1
  37. package/src/components/chart/core/pointer.ts +2 -0
  38. package/src/components/chart/stock-chart/resolved/runtime.ts +1 -1
  39. package/src/components/chart/stock-chart/settings.ts +7 -3
  40. package/src/components/chart/stock-chart/viewport/runtime.ts +5 -5
  41. package/src/components/command-bar/pane-templates/workflow-route.ts +10 -0
  42. package/src/components/command-bar/surface/pane.test.tsx +76 -0
  43. package/src/components/data-table/view.tsx +13 -2
  44. package/src/components/feed-data-table/stack-view.tsx +9 -1
  45. package/src/components/index.ts +1 -1
  46. package/src/components/input-search-bar.test.tsx +103 -0
  47. package/src/components/input-search-bar.tsx +16 -0
  48. package/src/components/layout/shell/active-drag.ts +5 -1
  49. package/src/components/layout/shell/drag/runtime.ts +21 -3
  50. package/src/components/layout/shell/fullscreen.ts +10 -0
  51. package/src/components/layout/shell/index.test.tsx +395 -2
  52. package/src/components/layout/shell/index.tsx +128 -2
  53. package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
  54. package/src/components/layout/shell/pane/layers.tsx +26 -14
  55. package/src/components/layout/shell/pane/management-shortcuts.ts +19 -0
  56. package/src/components/layout/shell/shortcuts.ts +4 -0
  57. package/src/components/layout/shell/terminal-pointer-runtime.ts +29 -19
  58. package/src/components/layout/shell/window-mode/index.ts +11 -3
  59. package/src/components/layout/shell/window-mode/overlays.tsx +8 -1
  60. package/src/components/layout/status-bar.test.tsx +105 -0
  61. package/src/components/layout/status-bar.tsx +32 -6
  62. package/src/components/layout/transient-layout.tsx +57 -0
  63. package/src/components/layout/window-edit/mode.test.ts +76 -5
  64. package/src/components/layout/window-edit/mode.ts +47 -2
  65. package/src/components/layout/window-edit/presentation.ts +2 -1
  66. package/src/components/price-performance.tsx +9 -1
  67. package/src/core/app-services.ts +7 -4
  68. package/src/data/config/store/index.ts +1 -2
  69. package/src/plugins/builtin/cloud-tweets/pane.tsx +2 -0
  70. package/src/plugins/builtin/cloud-tweets/search-bar.tsx +3 -0
  71. package/src/plugins/builtin/cloud-tweets/table.tsx +14 -2
  72. package/src/plugins/builtin/comparison-chart/index.test.tsx +130 -6
  73. package/src/plugins/builtin/news/wire/read-state.ts +9 -32
  74. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +23 -0
  75. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +19 -2
  76. package/src/plugins/builtin/portfolio-list/pane/index.tsx +1 -1
  77. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
  78. package/src/plugins/builtin/shared/read-state.ts +43 -0
  79. package/src/plugins/builtin/substack/article-detail.test.tsx +10 -0
  80. package/src/plugins/builtin/substack/article-detail.tsx +39 -15
  81. package/src/plugins/builtin/substack/article-stack.tsx +6 -2
  82. package/src/plugins/builtin/substack/feed-tabs.tsx +12 -9
  83. package/src/plugins/builtin/substack/pane.tsx +8 -3
  84. package/src/plugins/builtin/substack/read-state.ts +55 -0
  85. package/src/plugins/builtin/thirteenf/pane.test.tsx +26 -0
  86. package/src/plugins/builtin/thirteenf/pane.tsx +12 -1
  87. package/src/plugins/pane-manager/docking.ts +9 -2
  88. package/src/plugins/pane-manager/queries.ts +8 -2
  89. package/src/plugins/prediction-markets/controller/index.ts +2 -0
  90. package/src/plugins/prediction-markets/controller/keyboard.ts +34 -5
  91. package/src/plugins/prediction-markets/pane.test.tsx +32 -0
  92. package/src/plugins/prediction-markets/pane.tsx +21 -1
  93. package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
  94. package/src/renderers/electrobun/bun/application-menu/click.test.ts +9 -0
  95. package/src/renderers/electrobun/bun/application-menu/click.ts +1 -0
  96. package/src/renderers/electrobun/bun/application-menu/index.test.ts +9 -0
  97. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
  98. package/src/renderers/electrobun/bun/index.ts +13 -0
  99. package/src/renderers/electrobun/bun/window/focus.test.ts +35 -0
  100. package/src/renderers/electrobun/bun/window/focus.ts +5 -0
  101. package/src/renderers/electrobun/view/app-services.ts +6 -4
  102. package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
  103. package/src/renderers/electrobun/view/host/text.test.ts +22 -0
  104. package/src/renderers/electrobun/view/host/text.tsx +24 -20
  105. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  106. package/src/renderers/opentui/start.tsx +13 -5
  107. package/src/state/app/input-capture.ts +12 -0
  108. package/src/types/plugin.ts +28 -0
  109. package/src/utils/cli-output.ts +6 -0
  110. package/src/utils/search-focus-navigation.ts +24 -0
  111. package/src/version.ts +1 -1
@@ -7,21 +7,17 @@ import {
7
7
  type StyledTextChunk,
8
8
  type TextProps,
9
9
  } from "../../../../ui/host";
10
- import { renderAsciiText } from "../../../../ui/ascii-font";
11
10
  import { WEB_CELL_WIDTH } from "../input-host";
11
+ import { webAsciiTextLines, webAsciiTextWordmarkVariant } from "./ascii-text";
12
12
  import { hasDirectMouseHandler, mouseHandlers } from "./mouse";
13
13
  import { cleanDomProps, commonStyle, textStyle } from "./style";
14
14
 
15
- const WEB_GLOOMBERB_WORDMARK = [
16
- " ____ _ _ _ ",
17
- " / ___| | ___ ___ _ __ ___ | |__ ___ _ __| |__ ",
18
- "| | _| |/ _ \\ / _ \\| '_ ` _ \\| '_ \\ / _ \\ '__| '_ \\ ",
19
- "| |_| | | (_) | (_) | | | | | | |_) | __/ | | |_) |",
20
- " \\____|_|\\___/ \\___/|_| |_| |_|_.__/ \\___|_| |_.__/ ",
21
- ];
22
-
23
15
  const WEB_WORDMARK_CHAR_WIDTH_PX = Math.max(10, WEB_CELL_WIDTH);
24
16
 
17
+ interface WebAsciiTextProps extends AsciiTextProps {
18
+ desktopPlatform?: string;
19
+ }
20
+
25
21
  function isStyledTextContent(value: unknown): value is { chunks: StyledTextChunk[] } {
26
22
  return value instanceof StyledText
27
23
  || (!!value && typeof value === "object" && Array.isArray((value as { chunks?: unknown }).chunks));
@@ -100,16 +96,17 @@ export function WebAsciiText({
100
96
  bg,
101
97
  backgroundColor,
102
98
  selectable = false,
99
+ desktopPlatform,
103
100
  ...props
104
- }: AsciiTextProps) {
105
- const isWordmark = font === "wordmark";
106
- const lines = isWordmark && text.trim().toLowerCase() === "gloomberb"
107
- ? WEB_GLOOMBERB_WORDMARK
108
- : renderAsciiText(text, font);
101
+ }: WebAsciiTextProps) {
102
+ const wordmarkVariant = webAsciiTextWordmarkVariant(text, font, desktopPlatform);
103
+ const isCompatWordmark = wordmarkVariant === "compat";
104
+ const isLegacyWordmark = wordmarkVariant === "legacy";
105
+ const lines = webAsciiTextLines(text, font, desktopPlatform);
109
106
  const resolvedColor = color ?? fg;
110
107
  const resolvedBackground = bg ?? backgroundColor;
111
- const lineHeightPx = isWordmark ? 16 : 12;
112
- const wordmarkWidthPx = isWordmark
108
+ const lineHeightPx = isCompatWordmark ? 16 : 12;
109
+ const wordmarkWidthPx = isCompatWordmark
113
110
  ? Math.max(...lines.map((line) => line.length)) * WEB_WORDMARK_CHAR_WIDTH_PX
114
111
  : undefined;
115
112
  return (
@@ -118,15 +115,16 @@ export function WebAsciiText({
118
115
  data-gloom-role={(props["data-gloom-role"] as string | undefined) ?? "ascii-text"}
119
116
  style={{
120
117
  ...commonStyle({ ...props, fg: resolvedColor, bg: resolvedBackground }),
121
- display: "block",
118
+ display: isLegacyWordmark ? "flex" : "block",
119
+ flexDirection: isLegacyWordmark ? "column" : undefined,
122
120
  flexShrink: 0,
123
121
  ...(wordmarkWidthPx != null && props.width == null
124
122
  ? { width: `${wordmarkWidthPx}px`, minWidth: `${wordmarkWidthPx}px` }
125
123
  : {}),
126
124
  color: resolvedColor,
127
125
  backgroundColor: resolvedBackground,
128
- fontFamily: isWordmark ? "\"Cascadia Mono\", Consolas, \"Courier New\", monospace" : undefined,
129
- fontSize: isWordmark ? "16px" : undefined,
126
+ fontFamily: isCompatWordmark ? "\"Cascadia Mono\", Consolas, \"Courier New\", monospace" : undefined,
127
+ fontSize: isCompatWordmark ? "16px" : undefined,
130
128
  fontVariantLigatures: "none",
131
129
  lineHeight: `${lineHeightPx}px`,
132
130
  whiteSpace: "pre",
@@ -135,7 +133,13 @@ export function WebAsciiText({
135
133
  ...(props.style as CSSProperties | undefined),
136
134
  }}
137
135
  >
138
- {lines.join("\n")}
136
+ {isLegacyWordmark
137
+ ? lines.map((line, index) => (
138
+ <span key={index} style={{ display: "block", height: "12px", lineHeight: "12px" }}>
139
+ {line}
140
+ </span>
141
+ ))
142
+ : lines.join("\n")}
139
143
  </div>
140
144
  );
141
145
  }
@@ -137,7 +137,7 @@ export function createWebUiHost(desktopPlatform?: string): UiHost {
137
137
  *
138
138
  </span>
139
139
  ),
140
- AsciiText: WebAsciiText,
140
+ AsciiText: (props) => <WebAsciiText {...props} desktopPlatform={desktopPlatform} />,
141
141
  };
142
142
  }
143
143
 
@@ -16,18 +16,26 @@ import { ToastHostProvider } from "../../ui/toast";
16
16
  import { colors } from "../../theme/colors";
17
17
  import { startMainThreadMonitor } from "../../utils/main-thread-monitor";
18
18
  import { measurePerfAsync } from "../../utils/perf-marks";
19
+ import type { CliLaunchRequest } from "../../types/plugin";
19
20
 
20
- export async function startOpenTuiApp(): Promise<void> {
21
+ export interface StartOpenTuiAppOptions {
22
+ externalPlugins?: Awaited<ReturnType<typeof loadExternalPlugins>>;
23
+ cliArgs?: string[];
24
+ skipCliDispatch?: boolean;
25
+ cliLaunchRequest?: CliLaunchRequest | null;
26
+ }
27
+
28
+ export async function startOpenTuiApp(options: StartOpenTuiAppOptions = {}): Promise<void> {
21
29
  setConfigStoreHost(nodeConfigStoreHost);
22
30
  debugLog.interceptConsole();
23
31
 
24
32
  const appLog = debugLog.createLogger("app");
25
33
  appLog.info("Gloomberb starting");
26
34
 
27
- const cliArgs = process.argv.slice(2);
28
- const externalPlugins = await measurePerfAsync("startup.opentui.load-external-plugins", () => loadExternalPlugins());
29
- let cliLaunchRequest = null;
30
- if (cliArgs.length > 0) {
35
+ const cliArgs = options.cliArgs ?? process.argv.slice(2);
36
+ const externalPlugins = options.externalPlugins ?? await measurePerfAsync("startup.opentui.load-external-plugins", () => loadExternalPlugins());
37
+ let cliLaunchRequest = options.cliLaunchRequest ?? null;
38
+ if (!options.skipCliDispatch && cliArgs.length > 0) {
31
39
  const dispatchResult = await dispatchCli(cliArgs, { externalPlugins });
32
40
  if (dispatchResult.kind === "handled") return;
33
41
  if (dispatchResult.kind === "launch-ui") {
@@ -0,0 +1,12 @@
1
+ import { useEffect } from "react";
2
+ import { useAppDispatch } from "./context";
3
+
4
+ export function useAppInputCapture(captured: boolean) {
5
+ const dispatch = useAppDispatch();
6
+
7
+ useEffect(() => {
8
+ if (!captured) return;
9
+ dispatch({ type: "SET_INPUT_CAPTURED", captured: true });
10
+ return () => dispatch({ type: "SET_INPUT_CAPTURED", captured: false });
11
+ }, [captured, dispatch]);
12
+ }
@@ -4,6 +4,8 @@ import type { PluginEvents } from "../plugins/event-bus";
4
4
  import type { PluginLogger } from "../utils/debug-log";
5
5
  import type { BrokerAdapter } from "./broker";
6
6
  import type { PluginCapability } from "../capabilities";
7
+ import type { CliGlobalOptions } from "../cli/options";
8
+ import type { CliResult, CliResultRenderOptions } from "../cli/result";
7
9
  import type { ContextMenuContext, ContextMenuItem } from "./context-menu";
8
10
  import type {
9
11
  AppConfig,
@@ -245,6 +247,8 @@ export type CliDispatchResult =
245
247
  export interface CliCommandContext {
246
248
  initConfigData(): Promise<import("../cli/types").ConfigContext>;
247
249
  initMarketData(): Promise<import("../cli/types").MarketContext>;
250
+ initServices(): Promise<import("../cli/types").CliServicesContext>;
251
+ cliOptions: CliGlobalOptions;
248
252
  plugins: GloomPlugin[];
249
253
  fail(message: string, details?: string): never;
250
254
  closeAndFail(
@@ -259,6 +263,10 @@ export interface CliCommandContext {
259
263
  renderStat: typeof import("../utils/cli-output").renderStat;
260
264
  renderTable: typeof import("../utils/cli-output").renderTable;
261
265
  };
266
+ printResult<T, Row extends Record<string, unknown> = Record<string, unknown>>(
267
+ result: CliResult<T>,
268
+ options?: CliResultRenderOptions<T, Row>,
269
+ ): void;
262
270
  log: PluginLogger;
263
271
  }
264
272
 
@@ -270,6 +278,25 @@ export interface CliCommandDef {
270
278
  execute(args: string[], ctx: CliCommandContext): void | CliDispatchResult | Promise<void | CliDispatchResult>;
271
279
  }
272
280
 
281
+ export interface PluginCliCommandDescriptor {
282
+ name: string;
283
+ aliases?: string[];
284
+ summary: string;
285
+ inputShape?: string;
286
+ outputShape?: string;
287
+ examples?: string[];
288
+ sideEffectLevel?: "none" | "local-write" | "network-write" | "external-trade" | "external-side-effect";
289
+ requirements?: string[];
290
+ batch?: boolean;
291
+ formats?: Array<"text" | "json" | "csv" | "ndjson">;
292
+ safety?: string[];
293
+ execute?: CliCommandDef["execute"];
294
+ }
295
+
296
+ export interface PluginCliDescriptor {
297
+ commands?: PluginCliCommandDescriptor[];
298
+ }
299
+
273
300
  export interface CommandDef {
274
301
  id: string;
275
302
  label: string;
@@ -489,6 +516,7 @@ export interface GloomPlugin {
489
516
  toggleable?: boolean;
490
517
  order?: number;
491
518
  cliCommands?: CliCommandDef[];
519
+ cli?: PluginCliDescriptor;
492
520
 
493
521
  setup?(ctx: GloomPluginContext): void | Promise<void>;
494
522
  dispose?(): void;
@@ -8,11 +8,17 @@ export interface CliTableColumn {
8
8
  }
9
9
 
10
10
  const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
11
+ let colorEnabledOverride: boolean | null = null;
11
12
 
12
13
  function colorEnabled(): boolean {
14
+ if (colorEnabledOverride != null) return colorEnabledOverride;
13
15
  return !!process.stdout.isTTY && process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb";
14
16
  }
15
17
 
18
+ export function setCliColorEnabledOverride(value: boolean | null): void {
19
+ colorEnabledOverride = value;
20
+ }
21
+
16
22
  function applyAnsi(text: string, code: string): string {
17
23
  if (!colorEnabled()) return text;
18
24
  return `\x1b[${code}m${text}\x1b[0m`;
@@ -0,0 +1,24 @@
1
+ import { isPlainKeyboardEvent, type KeyboardModifierEventLike } from "./keyboard";
2
+
3
+ export interface SearchFocusNavigationEvent extends KeyboardModifierEventLike {
4
+ sequence?: string;
5
+ preventDefault?: () => void;
6
+ stopPropagation?: () => void;
7
+ }
8
+
9
+ export function isPlainArrowDown(event: SearchFocusNavigationEvent): boolean {
10
+ if (!isPlainKeyboardEvent(event)) return false;
11
+ const name = event.name?.toLowerCase();
12
+ return name === "down" || event.sequence === "\u001b[B" || event.sequence === "\u001bOB";
13
+ }
14
+
15
+ export function isPlainArrowUp(event: SearchFocusNavigationEvent): boolean {
16
+ if (!isPlainKeyboardEvent(event)) return false;
17
+ const name = event.name?.toLowerCase();
18
+ return name === "up" || event.sequence === "\u001b[A" || event.sequence === "\u001bOA";
19
+ }
20
+
21
+ export function stopSearchFocusNavigation(event: SearchFocusNavigationEvent): void {
22
+ event.preventDefault?.();
23
+ event.stopPropagation?.();
24
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.9.2";
1
+ export const VERSION = "0.9.3";