gloomberb 0.9.1 → 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.
- package/README.md +23 -2
- package/bin/gloomberb +1 -1
- package/package.json +1 -1
- package/src/app.tsx +10 -7
- 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/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/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/comparison-stock-chart/helpers.ts +0 -32
- package/src/components/chart/comparison-stock-chart/interactions.ts +7 -1
- package/src/components/chart/comparison-stock-chart/layout-metrics.ts +6 -6
- package/src/components/chart/comparison-stock-chart/legend.tsx +45 -44
- 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 +56 -6
- 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 +2 -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/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 +5 -2
- package/src/components/layout/header.tsx +13 -5
- package/src/components/layout/shell/active-drag.ts +5 -1
- package/src/components/layout/shell/drag/runtime.ts +21 -3
- package/src/components/layout/shell/fullscreen.ts +10 -0
- package/src/components/layout/shell/index.test.tsx +395 -2
- package/src/components/layout/shell/index.tsx +128 -2
- package/src/components/layout/shell/native/pointer-runtime.ts +10 -4
- package/src/components/layout/shell/pane/layers.tsx +26 -14
- 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/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 +105 -0
- package/src/components/layout/status-bar.tsx +32 -6
- package/src/components/layout/transient-layout.tsx +57 -0
- package/src/components/layout/window-controls.tsx +19 -10
- 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/price-performance.tsx +183 -0
- package/src/core/app-services.ts +7 -4
- package/src/data/config/store/index.ts +1 -2
- package/src/market-data/performance.test.ts +54 -0
- package/src/market-data/performance.ts +109 -0
- 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 +218 -15
- package/src/plugins/builtin/congress-trades/detail.tsx +204 -68
- package/src/plugins/builtin/congress-trades/footer.ts +14 -8
- package/src/plugins/builtin/congress-trades/keyboard.ts +4 -23
- package/src/plugins/builtin/congress-trades/model.ts +29 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +8 -13
- package/src/plugins/builtin/congress-trades/table.tsx +10 -1
- package/src/plugins/builtin/news/wire/read-state.ts +9 -32
- 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/pane/index.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -7
- 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/overview/model.ts +0 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -0
- package/src/plugins/pane-manager/docking.ts +9 -2
- package/src/plugins/pane-manager/queries.ts +8 -2
- 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/pane.test.tsx +32 -0
- package/src/plugins/prediction-markets/pane.tsx +21 -1
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -0
- 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/desktop/detached-windows.ts +9 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/window-controls.ts +11 -2
- package/src/renderers/electrobun/bun/desktop/window-style.ts +27 -1
- package/src/renderers/electrobun/bun/desktop/windows-custom-chrome.ts +58 -0
- package/src/renderers/electrobun/bun/desktop/windows-icons.ts +11 -64
- package/src/renderers/electrobun/bun/desktop/windows-native.ts +75 -0
- package/src/renderers/electrobun/bun/index.ts +22 -2
- 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/shared/protocol.ts +1 -0
- package/src/renderers/electrobun/view/app-services.ts +6 -4
- package/src/renderers/electrobun/view/host/ascii-text.ts +50 -0
- 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/main.tsx +2 -1
- package/src/renderers/electrobun/view/styles.css +9 -8
- package/src/renderers/electrobun/view/ui-host.tsx +102 -77
- package/src/renderers/opentui/start.tsx +13 -5
- package/src/state/app/input-capture.ts +12 -0
- package/src/types/plugin.ts +28 -0
- package/src/utils/cli-output.ts +6 -0
- package/src/utils/search-focus-navigation.ts +24 -0
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -70,22 +70,43 @@ Open command mode with `Ctrl+P`, then type a command. Press `` ` `` to open tick
|
|
|
70
70
|
|
|
71
71
|
## CLI
|
|
72
72
|
|
|
73
|
-
Running `gloomberb` with no arguments launches the terminal UI.
|
|
73
|
+
Running `gloomberb` with no arguments launches the terminal UI. Normal commands run through a headless CLI path; use `gloomberb launch-ui` when a script should explicitly open the UI.
|
|
74
|
+
|
|
75
|
+
Human-readable output is the default. Automation can opt into structured output with `--json`, `--csv`, or `--ndjson`. JSON output favors the richest fetched model available and includes display-column metadata when a command has table columns; CSV and NDJSON use the command's tabular row view. Common global flags include `--limit`, `--refresh`, `--quiet`, `--no-color`, `--dry-run`, and `--yes`.
|
|
74
76
|
|
|
75
77
|
| Command | Use |
|
|
76
78
|
|---------|-----|
|
|
77
79
|
| `gloomberb` | Launch the terminal UI |
|
|
80
|
+
| `gloomberb launch-ui` | Explicitly launch the terminal UI |
|
|
78
81
|
| `gloomberb help` | Show all CLI commands |
|
|
79
|
-
| `gloomberb
|
|
82
|
+
| `gloomberb api list|get|invoke|subscribe` | Inspect and call plugin capabilities directly |
|
|
83
|
+
| `gloomberb quote <symbols>` | Fetch current quotes |
|
|
84
|
+
| `gloomberb search <query>` / `provider-search <query>` | Search tickers and provider symbols |
|
|
80
85
|
| `gloomberb ticker <symbol>` | Show quote, ownership, and financials |
|
|
86
|
+
| `gloomberb history|financials|fundamentals|options <symbol>` | Fetch research data |
|
|
87
|
+
| `gloomberb news|filings|holders|insider|13f|analyst|events|valuation <symbol>` | Fetch company research feeds |
|
|
88
|
+
| `gloomberb movers|indices|sectors|fx|fear-greed|earnings` | Fetch market overview data |
|
|
89
|
+
| `gloomberb econ|fred|yield-curve` | Fetch macro data |
|
|
90
|
+
| `gloomberb compare|correlation|relationship <symbols>` | Compare securities |
|
|
81
91
|
| `gloomberb portfolio [action]` | Manage manual portfolios |
|
|
82
92
|
| `gloomberb watchlist [action]` | Manage watchlists |
|
|
93
|
+
| `gloomberb notes|alerts [action]` | Manage local notes and alerts |
|
|
94
|
+
| `gloomberb broker|ibkr [action]` | Inspect broker integrations; trading actions require explicit account/profile and `--yes` |
|
|
95
|
+
| `gloomberb ai providers|ask|screen` | Use configured AI providers and screeners |
|
|
96
|
+
| `gloomberb rss fetch <url>` | Fetch an RSS feed |
|
|
97
|
+
| `gloomberb buildout|congress|substack|x-feed|tweets` | Access cloud and social data sources when an existing session is available |
|
|
98
|
+
| `gloomberb provider status` | Inspect enabled data providers |
|
|
99
|
+
| `gloomberb config|cache|plugin|layout|pane|debug|doctor|version|changelog` | Inspect and manage local app state |
|
|
100
|
+
| `gloomberb fn [...]` | Run a pane-backed report command |
|
|
101
|
+
| `gloomberb shot [...]` | Capture a pane-backed screenshot |
|
|
83
102
|
| `gloomberb predictions [...]` | Launch Prediction Markets |
|
|
84
103
|
| `gloomberb plugins` | List installed plugins |
|
|
85
104
|
| `gloomberb install <user/repo>` | Install a plugin from GitHub |
|
|
86
105
|
| `gloomberb remove <name>` | Remove an installed plugin |
|
|
87
106
|
| `gloomberb update [name]` | Update plugins |
|
|
88
107
|
|
|
108
|
+
Commands that need a signed-in cloud session may return `auth_required`; sign-in, account management, and chat workflows are still handled in the app UI for now.
|
|
109
|
+
|
|
89
110
|
## Plugins
|
|
90
111
|
|
|
91
112
|
Everything from the portfolio list to broker integrations is a plugin. Plugins can add panes, tabs, columns, command bar commands, CLI commands, status bar widgets, and data providers.
|
package/bin/gloomberb
CHANGED
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { Header } from "./components/layout/header";
|
|
|
14
14
|
import { StatusBar } from "./components/layout/status-bar";
|
|
15
15
|
import { Shell } from "./components/layout/shell";
|
|
16
16
|
import { DetachedPaneShell } from "./components/layout/detached-pane-shell";
|
|
17
|
+
import { TransientLayoutProvider } from "./components/layout/transient-layout";
|
|
17
18
|
import { CommandBar } from "./components/command-bar/surface";
|
|
18
19
|
import { OnboardingWizard } from "./components/onboarding/onboarding-wizard";
|
|
19
20
|
import { useDialog } from "./ui/dialog";
|
|
@@ -293,13 +294,15 @@ function AppInner({
|
|
|
293
294
|
<ContextMenuProvider pluginRegistry={pluginRegistry}>
|
|
294
295
|
<ThemedAppRoot>
|
|
295
296
|
<Header />
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
297
|
+
<TransientLayoutProvider>
|
|
298
|
+
<Shell
|
|
299
|
+
pluginRegistry={pluginRegistry}
|
|
300
|
+
desktopWindowBridge={desktopWindowBridge}
|
|
301
|
+
desktopDockPreview={desktopDockPreview}
|
|
302
|
+
commandBarNativeOccluder={commandBarNativeOccluder}
|
|
303
|
+
/>
|
|
304
|
+
<StatusBar />
|
|
305
|
+
</TransientLayoutProvider>
|
|
303
306
|
{state.commandBarOpen && (
|
|
304
307
|
<CommandBar
|
|
305
308
|
dataProvider={dataProvider}
|
|
@@ -3,6 +3,7 @@ import type { AssetDataProvider } from "../types/data-provider";
|
|
|
3
3
|
import type { NewsDataProvider } from "../types/capability-route-source";
|
|
4
4
|
|
|
5
5
|
type CapabilityOperationKind = "read" | "query" | "action" | "stream";
|
|
6
|
+
type CapabilitySideEffectLevel = "none" | "local-write" | "network-write" | "external-trade" | "external-side-effect";
|
|
6
7
|
|
|
7
8
|
type CapabilityKind =
|
|
8
9
|
| "asset-data"
|
|
@@ -20,9 +21,22 @@ interface CapabilityHandlerContext {
|
|
|
20
21
|
|
|
21
22
|
type CapabilityStreamEmit<T = unknown> = (event: T) => void;
|
|
22
23
|
|
|
24
|
+
export interface CapabilityOperationCliManifest {
|
|
25
|
+
summary?: string;
|
|
26
|
+
inputShape?: string;
|
|
27
|
+
outputShape?: string;
|
|
28
|
+
examples?: string[];
|
|
29
|
+
sideEffectLevel?: CapabilitySideEffectLevel;
|
|
30
|
+
requirements?: string[];
|
|
31
|
+
batch?: boolean;
|
|
32
|
+
formats?: Array<"text" | "json" | "csv" | "ndjson">;
|
|
33
|
+
safety?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
export interface CapabilityOperation<I = unknown, O = unknown, E = unknown> {
|
|
24
37
|
kind: CapabilityOperationKind;
|
|
25
38
|
rendererSafe?: boolean;
|
|
39
|
+
cli?: CapabilityOperationCliManifest;
|
|
26
40
|
input?: CapabilitySchema<I>;
|
|
27
41
|
output?: CapabilitySchema<O>;
|
|
28
42
|
cachePolicy?: CachePolicyMap[keyof CachePolicyMap];
|
|
@@ -55,6 +69,15 @@ export interface CapabilityOperationManifest {
|
|
|
55
69
|
id: string;
|
|
56
70
|
kind: CapabilityOperationKind;
|
|
57
71
|
rendererSafe: boolean;
|
|
72
|
+
summary?: string;
|
|
73
|
+
inputShape?: string;
|
|
74
|
+
outputShape?: string;
|
|
75
|
+
examples?: string[];
|
|
76
|
+
sideEffectLevel?: CapabilitySideEffectLevel;
|
|
77
|
+
requirements?: string[];
|
|
78
|
+
batch?: boolean;
|
|
79
|
+
formats?: Array<"text" | "json" | "csv" | "ndjson">;
|
|
80
|
+
safety?: string[];
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
export interface CapabilityManifest {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { CliCommandDef } from "../../types/plugin";
|
|
2
|
+
import { parseJsonPayload, requireArg, takeOption } from "./command-utils";
|
|
3
|
+
|
|
4
|
+
function splitOperationTarget(target: string): { capabilityId: string; operationId: string } {
|
|
5
|
+
const separator = target.lastIndexOf(".");
|
|
6
|
+
if (separator <= 0 || separator === target.length - 1) {
|
|
7
|
+
throw new Error("Use <capability-id>.<operation-id>.");
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
capabilityId: target.slice(0, separator),
|
|
11
|
+
operationId: target.slice(separator + 1),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseOperationTarget(target: string, ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
16
|
+
try {
|
|
17
|
+
return splitOperationTarget(target);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
ctx.fail("Invalid capability operation target.", error instanceof Error ? error.message : String(error));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const apiCliCommand: CliCommandDef = {
|
|
24
|
+
name: "api",
|
|
25
|
+
description: "List, inspect, invoke, or subscribe to plugin capabilities",
|
|
26
|
+
help: {
|
|
27
|
+
usage: [
|
|
28
|
+
"api list [--kind kind]",
|
|
29
|
+
"api get <capability-id>",
|
|
30
|
+
"api invoke <capability.operation> [json-payload]",
|
|
31
|
+
"api subscribe <capability.operation> [json-payload] [--limit n]",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
execute: async (rawArgs, ctx) => {
|
|
35
|
+
const action = rawArgs[0] ?? "list";
|
|
36
|
+
const args = rawArgs.slice(1);
|
|
37
|
+
const services = await ctx.initServices();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (action === "list") {
|
|
41
|
+
const kind = takeOption(args, "--kind");
|
|
42
|
+
const manifests = services.services.pluginRegistry.capabilities
|
|
43
|
+
.manifests()
|
|
44
|
+
.filter((manifest) => !kind || manifest.kind === kind);
|
|
45
|
+
ctx.printResult({ data: manifests }, {
|
|
46
|
+
rows: (data) => data.map((manifest) => ({
|
|
47
|
+
id: manifest.id,
|
|
48
|
+
kind: manifest.kind,
|
|
49
|
+
name: manifest.name,
|
|
50
|
+
operations: manifest.operations.map((operation) => operation.id).join(", "),
|
|
51
|
+
})),
|
|
52
|
+
columns: [
|
|
53
|
+
{ key: "id", header: "Capability" },
|
|
54
|
+
{ key: "kind", header: "Kind" },
|
|
55
|
+
{ key: "name", header: "Name" },
|
|
56
|
+
{ key: "operations", header: "Operations" },
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (action === "get") {
|
|
63
|
+
const capabilityId = requireArg(args[0], "Usage: gloomberb api get <capability-id>", ctx);
|
|
64
|
+
const manifest = services.services.pluginRegistry.capabilities
|
|
65
|
+
.manifests()
|
|
66
|
+
.find((entry) => entry.id === capabilityId);
|
|
67
|
+
if (!manifest) ctx.fail(`Capability "${capabilityId}" is not available.`);
|
|
68
|
+
ctx.printResult({ data: manifest });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (action === "invoke") {
|
|
73
|
+
const target = requireArg(args[0], "Usage: gloomberb api invoke <capability.operation> [json-payload]", ctx);
|
|
74
|
+
const operationTarget = parseOperationTarget(target, ctx);
|
|
75
|
+
const payload = parseJsonPayload(args[1], ctx);
|
|
76
|
+
const data = await services.services.pluginRegistry.capabilities.invoke(
|
|
77
|
+
operationTarget.capabilityId,
|
|
78
|
+
operationTarget.operationId,
|
|
79
|
+
payload,
|
|
80
|
+
);
|
|
81
|
+
ctx.printResult({ data });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (action === "subscribe") {
|
|
86
|
+
const target = requireArg(args[0], "Usage: gloomberb api subscribe <capability.operation> [json-payload] [--limit n]", ctx);
|
|
87
|
+
const operationTarget = parseOperationTarget(target, ctx);
|
|
88
|
+
const limit = ctx.cliOptions.limit ?? 10;
|
|
89
|
+
const payload = parseJsonPayload(args[1], ctx);
|
|
90
|
+
let count = 0;
|
|
91
|
+
let subscriptionId: string | null = null;
|
|
92
|
+
await new Promise<void>(async (resolve, reject) => {
|
|
93
|
+
try {
|
|
94
|
+
subscriptionId = await services.services.pluginRegistry.capabilities.subscribe(
|
|
95
|
+
operationTarget.capabilityId,
|
|
96
|
+
operationTarget.operationId,
|
|
97
|
+
payload,
|
|
98
|
+
(event) => {
|
|
99
|
+
count += 1;
|
|
100
|
+
ctx.printResult({ data: event });
|
|
101
|
+
if (count >= limit && subscriptionId) {
|
|
102
|
+
services.services.pluginRegistry.capabilities.unsubscribe(subscriptionId);
|
|
103
|
+
resolve();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
reject(error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ctx.fail("Usage: gloomberb api list|get|invoke|subscribe");
|
|
115
|
+
} finally {
|
|
116
|
+
services.destroy();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { detectProviders, getAiProvider } from "../../plugins/builtin/ai/providers";
|
|
2
|
+
import { runAiPrompt } from "../../plugins/builtin/ai/runner";
|
|
3
|
+
import { createRssNewsCapability } from "../../plugins/builtin/news/wire/rss/source";
|
|
4
|
+
import type { CliCommandDef } from "../../types/plugin";
|
|
5
|
+
import { requireArg, takeOption } from "./command-utils";
|
|
6
|
+
|
|
7
|
+
function deferredResult(command: string, code: "auth_required" | "not_implemented", message: string) {
|
|
8
|
+
return {
|
|
9
|
+
command,
|
|
10
|
+
status: code,
|
|
11
|
+
code,
|
|
12
|
+
message,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const brokerCliCommand: CliCommandDef = {
|
|
17
|
+
name: "broker",
|
|
18
|
+
aliases: ["brokers"],
|
|
19
|
+
description: "Inspect broker profiles and guarded broker operations",
|
|
20
|
+
help: { usage: ["broker list", "broker status", "broker add|edit|remove|connect|sync"] },
|
|
21
|
+
execute: async (args, ctx) => {
|
|
22
|
+
const action = args[0] ?? "list";
|
|
23
|
+
const services = await ctx.initServices();
|
|
24
|
+
try {
|
|
25
|
+
if (action === "list" || action === "status") {
|
|
26
|
+
const rows = services.config.brokerInstances.map((instance) => ({
|
|
27
|
+
id: instance.id,
|
|
28
|
+
type: instance.brokerType,
|
|
29
|
+
label: instance.label,
|
|
30
|
+
enabled: instance.enabled !== false,
|
|
31
|
+
connectionMode: instance.connectionMode ?? "",
|
|
32
|
+
lastSyncedAt: instance.lastSyncedAt ? new Date(instance.lastSyncedAt).toISOString() : "",
|
|
33
|
+
}));
|
|
34
|
+
ctx.printResult({ data: rows });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ctx.printResult({
|
|
39
|
+
data: deferredResult("broker", "not_implemented", "Broker mutation and connection commands need shared non-UI broker service extraction before they can safely run headless."),
|
|
40
|
+
});
|
|
41
|
+
} finally {
|
|
42
|
+
services.destroy();
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const ibkrCliCommand: CliCommandDef = {
|
|
48
|
+
name: "ibkr",
|
|
49
|
+
description: "Guarded IBKR account, order preview, and order action commands",
|
|
50
|
+
help: { usage: ["ibkr accounts|positions|orders|preview|place|cancel"] },
|
|
51
|
+
execute: async (args, ctx) => {
|
|
52
|
+
const action = args[0] ?? "status";
|
|
53
|
+
const profile = takeOption(args, "--profile");
|
|
54
|
+
const account = takeOption(args, "--account");
|
|
55
|
+
if ((action === "place" || action === "cancel") && (!profile || !account || !ctx.cliOptions.yes)) {
|
|
56
|
+
ctx.fail(`ibkr ${action} requires --profile, --account, and --yes.`);
|
|
57
|
+
}
|
|
58
|
+
const services = await ctx.initServices();
|
|
59
|
+
try {
|
|
60
|
+
const ibkrProfiles = services.config.brokerInstances.filter((instance) => instance.brokerType === "ibkr");
|
|
61
|
+
if (action === "accounts" || action === "status") {
|
|
62
|
+
ctx.printResult({ data: ibkrProfiles.map((instance) => ({
|
|
63
|
+
id: instance.id,
|
|
64
|
+
label: instance.label,
|
|
65
|
+
enabled: instance.enabled !== false,
|
|
66
|
+
connectionMode: instance.connectionMode ?? "",
|
|
67
|
+
lastSyncedAt: instance.lastSyncedAt ? new Date(instance.lastSyncedAt).toISOString() : "",
|
|
68
|
+
})) });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
ctx.printResult({
|
|
72
|
+
data: {
|
|
73
|
+
...deferredResult("ibkr", "not_implemented", "IBKR trading commands are registered with safety gates, but headless order/account service extraction is still required."),
|
|
74
|
+
action,
|
|
75
|
+
profile: profile ?? null,
|
|
76
|
+
account: account ?? null,
|
|
77
|
+
dryRun: ctx.cliOptions.dryRun,
|
|
78
|
+
confirmed: ctx.cliOptions.yes,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
} finally {
|
|
82
|
+
services.destroy();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const aiCliCommand: CliCommandDef = {
|
|
88
|
+
name: "ai",
|
|
89
|
+
description: "Inspect AI providers and run guarded headless AI prompts",
|
|
90
|
+
help: { usage: ["ai providers", "ai ask [--provider id] <prompt>", "ai screen list|show|delete|refresh|export"] },
|
|
91
|
+
execute: async (args, ctx) => {
|
|
92
|
+
const action = args[0] ?? "providers";
|
|
93
|
+
if (action === "providers") {
|
|
94
|
+
ctx.printResult({ data: detectProviders().map((provider) => ({
|
|
95
|
+
id: provider.id,
|
|
96
|
+
name: provider.name,
|
|
97
|
+
command: provider.command,
|
|
98
|
+
available: provider.available,
|
|
99
|
+
})) });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (action === "ask") {
|
|
103
|
+
const rawArgs = args.slice(1);
|
|
104
|
+
const providerId = takeOption(rawArgs, "--provider") ?? detectProviders().find((provider) => provider.available)?.id;
|
|
105
|
+
const provider = getAiProvider(providerId);
|
|
106
|
+
if (!provider) ctx.fail("Unknown AI provider.");
|
|
107
|
+
const selectedProvider = provider;
|
|
108
|
+
if (!selectedProvider.available) ctx.fail(`${selectedProvider.name} is not installed or not available in PATH.`);
|
|
109
|
+
const prompt = rawArgs.join(" ").trim();
|
|
110
|
+
if (!prompt) ctx.fail("Usage: gloomberb ai ask [--provider id] <prompt>");
|
|
111
|
+
const controller = runAiPrompt({ provider: selectedProvider, prompt, cwd: process.cwd() });
|
|
112
|
+
const text = await controller.done;
|
|
113
|
+
ctx.printResult({ data: { provider: selectedProvider.id, text } });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (action === "screen") {
|
|
117
|
+
ctx.printResult({
|
|
118
|
+
data: deferredResult("ai screen", "not_implemented", "AI screener tabs are still pane-state backed; expose them through a shared non-UI screener store before headless mutation."),
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
ctx.fail("Usage: gloomberb ai providers|ask|screen");
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const rssCliCommand: CliCommandDef = {
|
|
127
|
+
name: "rss",
|
|
128
|
+
description: "Fetch an RSS feed as news rows",
|
|
129
|
+
help: { usage: ["rss fetch <url> [--name label]"] },
|
|
130
|
+
execute: async (args, ctx) => {
|
|
131
|
+
const action = args[0] ?? "fetch";
|
|
132
|
+
if (action !== "fetch") ctx.fail("Usage: gloomberb rss fetch <url> [--name label]");
|
|
133
|
+
const rawArgs = args.slice(1);
|
|
134
|
+
const name = takeOption(rawArgs, "--name") ?? "RSS";
|
|
135
|
+
const url = requireArg(rawArgs[0], "Usage: gloomberb rss fetch <url> [--name label]", ctx);
|
|
136
|
+
const capability = createRssNewsCapability([{
|
|
137
|
+
id: "cli-feed",
|
|
138
|
+
url,
|
|
139
|
+
name,
|
|
140
|
+
category: "cli",
|
|
141
|
+
authority: 50,
|
|
142
|
+
enabled: true,
|
|
143
|
+
}]);
|
|
144
|
+
const articles = await capability.provider.fetchNews({ feed: "latest", limit: ctx.cliOptions.limit ?? 20 });
|
|
145
|
+
ctx.printResult({ data: articles.map((article) => ({
|
|
146
|
+
title: article.title,
|
|
147
|
+
source: article.source,
|
|
148
|
+
publishedAt: article.publishedAt.toISOString(),
|
|
149
|
+
url: article.url,
|
|
150
|
+
summary: article.summary ?? "",
|
|
151
|
+
})) });
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
function cloudCommand(name: string, description: string): CliCommandDef {
|
|
156
|
+
return {
|
|
157
|
+
name,
|
|
158
|
+
description,
|
|
159
|
+
execute: (_args, ctx) => {
|
|
160
|
+
ctx.printResult({
|
|
161
|
+
data: deferredResult(name, "auth_required", "This command needs an existing cloud/session token. New auth/account/chat CLI workflows are deferred in this pass."),
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const cloudCliCommands: CliCommandDef[] = [
|
|
168
|
+
cloudCommand("buildout", "Fetch Buildout company data through an existing cloud session"),
|
|
169
|
+
cloudCommand("congress", "Fetch congressional trade data through an existing cloud session"),
|
|
170
|
+
cloudCommand("substack", "Fetch Substack reader data through an existing session"),
|
|
171
|
+
cloudCommand("x-feed", "Fetch X/Twitter feed data through an existing cloud session"),
|
|
172
|
+
cloudCommand("tweets", "Alias for x-feed"),
|
|
173
|
+
];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CliCommandContext } from "../../types/plugin";
|
|
2
|
+
|
|
3
|
+
export function takeOption(args: string[], name: string): string | undefined {
|
|
4
|
+
const equalsPrefix = `${name}=`;
|
|
5
|
+
const equalsIndex = args.findIndex((arg) => arg.startsWith(equalsPrefix));
|
|
6
|
+
if (equalsIndex >= 0) {
|
|
7
|
+
const [value] = args.splice(equalsIndex, 1);
|
|
8
|
+
return value!.slice(equalsPrefix.length);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const index = args.indexOf(name);
|
|
12
|
+
if (index < 0) return undefined;
|
|
13
|
+
const value = args[index + 1];
|
|
14
|
+
args.splice(index, value == null ? 1 : 2);
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function takeFlag(args: string[], name: string): boolean {
|
|
19
|
+
const index = args.indexOf(name);
|
|
20
|
+
if (index < 0) return false;
|
|
21
|
+
args.splice(index, 1);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseJsonPayload(value: string | undefined, ctx: CliCommandContext): unknown {
|
|
26
|
+
if (!value) return {};
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(value);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
ctx.fail("Payload must be valid JSON.", error instanceof Error ? error.message : String(error));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parsePositiveInt(value: string | undefined, fallback: number, label: string, ctx: CliCommandContext): number {
|
|
35
|
+
if (value == null || value === "") return fallback;
|
|
36
|
+
const parsed = Number(value);
|
|
37
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
38
|
+
ctx.fail(`${label} must be a positive integer.`);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function requireArg(value: string | undefined, usage: string, ctx: CliCommandContext): string {
|
|
44
|
+
if (!value) ctx.fail(usage);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isoDate(value: unknown): string {
|
|
49
|
+
if (value instanceof Date) return value.toISOString();
|
|
50
|
+
if (typeof value === "number") return new Date(value).toISOString();
|
|
51
|
+
if (typeof value === "string") return value;
|
|
52
|
+
return "";
|
|
53
|
+
}
|