sportsing 0.1.1 → 0.1.2
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 +14 -5
- package/package.json +7 -1
- package/src/stream.ts +57 -32
package/README.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Sports in your terminal — the FIFA World Cup 2026 schedule, favorites, live
|
|
4
4
|
scores, **ambient fav-alerts**, browser streaming, highlights, stats, and AI
|
|
5
|
-
analysis.
|
|
6
|
-
|
|
5
|
+
analysis. Runs on [Bun](https://bun.sh); its one runtime dependency,
|
|
6
|
+
[ui-leaf](https://www.npmjs.com/package/@openthink/ui-leaf), powers the
|
|
7
|
+
browser-streaming window and is installed (with its native binary) automatically.
|
|
7
8
|
|
|
8
9
|
```
|
|
9
10
|
sportsing fifa today
|
|
@@ -14,12 +15,20 @@ sportsing today # the `fifa` prefix is optional during the World Cu
|
|
|
14
15
|
## Install
|
|
15
16
|
|
|
16
17
|
```sh
|
|
17
|
-
bun install #
|
|
18
|
+
bun install # deps: ui-leaf (+ its browser binary) and dev types
|
|
18
19
|
bun run build # compiles a standalone binary → dist/sportsing
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
Then run `./dist/sportsing …`, or put it on your `PATH`.
|
|
22
|
-
|
|
22
|
+
Then run `./dist/sportsing …`, or put it on your `PATH`.
|
|
23
|
+
|
|
24
|
+
> **Streaming from the compiled binary.** `bun run build` produces a single
|
|
25
|
+
> file, but it can't embed ui-leaf's ~64MB native browser binary. Running from
|
|
26
|
+
> source (`bun run src/index.ts …`) or installing from npm resolves that binary
|
|
27
|
+
> automatically; for the standalone `dist/sportsing`, point streaming at an
|
|
28
|
+
> installed copy with `UI_LEAF_BINARY_PATH=/path/to/ui-leaf-bin`. Everything
|
|
29
|
+
> except the `watch` browser window works with no binary present.
|
|
30
|
+
|
|
31
|
+
For live data, add a free [football-data.org](https://www.football-data.org) API key:
|
|
23
32
|
|
|
24
33
|
```sh
|
|
25
34
|
sportsing fifa setup # paste your key (or set FOOTBALL_DATA_API_KEY)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sportsing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Sportsing — the FIFA World Cup 2026 in your terminal: schedule, favorites, live scores, ambient fav-alerts, browser streaming, highlights, stats, and AI analysis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,12 @@
|
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@openthink/ui-leaf": "^1.5.1"
|
|
50
|
+
},
|
|
51
|
+
"trustedDependencies": [
|
|
52
|
+
"@openthink/ui-leaf"
|
|
53
|
+
],
|
|
48
54
|
"devDependencies": {
|
|
49
55
|
"@types/node": "^25.9.2",
|
|
50
56
|
"bun-types": "^1.3.14",
|
package/src/stream.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
// Launch a live-stream window via ui-leaf
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Launch a live-stream window via ui-leaf: a chromeless app-mode Chrome window
|
|
2
|
+
// on a *persistent* profile (so provider logins survive), pointed at a streaming
|
|
3
|
+
// URL through a tiny redirect view.
|
|
4
|
+
//
|
|
5
|
+
// ui-leaf is a declared dependency (`@openthink/ui-leaf`), driven through its
|
|
6
|
+
// `spawnUiLeaf` SDK — NOT a globally-installed CLI resolved off PATH. The version
|
|
7
|
+
// is pinned in package.json and the platform-specific native binary is fetched by
|
|
8
|
+
// ui-leaf's postinstall, so there's no separate global-install step and no
|
|
9
|
+
// version skew between sportsing and ui-leaf.
|
|
4
10
|
//
|
|
5
11
|
// Why a redirect view: ui-leaf is bring-your-own-view (it serves a local view),
|
|
6
|
-
// so we serve a one-line view that navigates the window to the external
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
12
|
+
// so we serve a one-line view that navigates the window to the external provider
|
|
13
|
+
// URL. The window survives the navigation; the persistent profile keeps the
|
|
14
|
+
// login. The SDK holds the binary's stdin open for the window's lifetime; the
|
|
15
|
+
// returned handle's kill() tears it down.
|
|
10
16
|
|
|
11
17
|
import { homedir, tmpdir } from "os";
|
|
12
18
|
import { join } from "path";
|
|
13
19
|
import { mkdtemp, writeFile, mkdir } from "fs/promises";
|
|
14
20
|
import { rmSync, existsSync } from "fs";
|
|
21
|
+
import { mount } from "@openthink/ui-leaf";
|
|
15
22
|
import { c } from "./ansi.ts";
|
|
16
23
|
|
|
17
24
|
/** Persistent Chrome profile path for a provider — one per provider so logins
|
|
@@ -77,20 +84,15 @@ export interface StreamWindow {
|
|
|
77
84
|
|
|
78
85
|
/**
|
|
79
86
|
* Spawn a persistent-profile app-mode Chrome window forwarded to `url`, without
|
|
80
|
-
*
|
|
81
|
-
*
|
|
87
|
+
* installing signal handlers — the caller owns lifecycle. Pass `debugPort` to
|
|
88
|
+
* expose CDP (for the overlay). Awaits the window coming up; returns null (with
|
|
89
|
+
* an actionable message) if ui-leaf's native binary can't launch.
|
|
82
90
|
*/
|
|
83
91
|
export async function spawnStreamWindow(
|
|
84
92
|
url: string,
|
|
85
93
|
label: string,
|
|
86
94
|
opts: { debugPort?: number; windowSize?: { width: number; height: number } } = {},
|
|
87
95
|
): Promise<StreamWindow | null> {
|
|
88
|
-
if (!Bun.which("ui-leaf")) {
|
|
89
|
-
console.error(c.yellow("Streaming needs the `ui-leaf` CLI (>=1.3.0)."));
|
|
90
|
-
console.error(c.dim("Install it: npm i -g @openthink/ui-leaf@latest"));
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
96
|
// Embed the view + write it to a temp viewsRoot at launch, so this works from
|
|
95
97
|
// the compiled binary (no views/ dir to ship alongside it).
|
|
96
98
|
const viewsRoot = await mkdtemp(join(tmpdir(), "sportsing-stream-"));
|
|
@@ -98,18 +100,6 @@ export async function spawnStreamWindow(
|
|
|
98
100
|
const dir = profileDir(label);
|
|
99
101
|
await mkdir(dir, { recursive: true });
|
|
100
102
|
|
|
101
|
-
const config = {
|
|
102
|
-
version: "1",
|
|
103
|
-
view: "stream",
|
|
104
|
-
viewsRoot,
|
|
105
|
-
data: { url, label },
|
|
106
|
-
shell: "app",
|
|
107
|
-
profile: { dir },
|
|
108
|
-
...(opts.debugPort ? { debugPort: opts.debugPort } : {}),
|
|
109
|
-
...(opts.windowSize ? { windowSize: opts.windowSize } : {}),
|
|
110
|
-
port: 0,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
103
|
const cleanup = () => {
|
|
114
104
|
try {
|
|
115
105
|
rmSync(viewsRoot, { recursive: true, force: true });
|
|
@@ -118,14 +108,49 @@ export async function spawnStreamWindow(
|
|
|
118
108
|
}
|
|
119
109
|
};
|
|
120
110
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
111
|
+
// Teardown goes through an AbortSignal, not view.close(): aborting the signal
|
|
112
|
+
// is the path the SDK wires to "send close, then SIGKILL after a 5s grace", so
|
|
113
|
+
// it reliably reaps the ui-leaf/Chrome tree even if the binary stops responding
|
|
114
|
+
// to the graceful close message. view.close() alone is graceful-only and can
|
|
115
|
+
// hang. (This is the clean-reap behavior the old proc.kill() relied on.)
|
|
116
|
+
const ac = new AbortController();
|
|
117
|
+
|
|
118
|
+
// mount() resolves once the window is up and rejects if the native binary is
|
|
119
|
+
// missing or the child dies before serving. The SDK resolves that binary from
|
|
120
|
+
// this package's node_modules; a standalone `bun build --compile` artifact has
|
|
121
|
+
// none, so honor UI_LEAF_BINARY_PATH as the escape hatch to point at one.
|
|
122
|
+
let view;
|
|
123
|
+
try {
|
|
124
|
+
view = await mount({
|
|
125
|
+
view: "stream",
|
|
126
|
+
viewsRoot,
|
|
127
|
+
data: { url, label },
|
|
128
|
+
shell: "app",
|
|
129
|
+
profile: { dir },
|
|
130
|
+
...(opts.debugPort ? { debugPort: opts.debugPort } : {}),
|
|
131
|
+
...(opts.windowSize ? { windowSize: opts.windowSize } : {}),
|
|
132
|
+
port: 0,
|
|
133
|
+
silent: true, // keep the launch quiet (matches the old stdout/stderr: "ignore")
|
|
134
|
+
signal: ac.signal,
|
|
135
|
+
...(process.env.UI_LEAF_BINARY_PATH ? { binaryPath: process.env.UI_LEAF_BINARY_PATH } : {}),
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
cleanup();
|
|
139
|
+
console.error(c.yellow("Couldn't launch the ui-leaf browser window."));
|
|
140
|
+
console.error(
|
|
141
|
+
c.dim(
|
|
142
|
+
"If you installed sportsing from npm, reinstall so its ui-leaf binary is fetched (npm i -g sportsing). " +
|
|
143
|
+
"If you're running the standalone binary, set UI_LEAF_BINARY_PATH to an installed ui-leaf-bin.",
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
console.error(c.dim(String(err instanceof Error ? err.message : err)));
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
124
149
|
|
|
125
150
|
return {
|
|
126
|
-
exited:
|
|
151
|
+
exited: view.closed.then(() => cleanup()),
|
|
127
152
|
close: () => {
|
|
128
|
-
|
|
153
|
+
ac.abort();
|
|
129
154
|
cleanup();
|
|
130
155
|
},
|
|
131
156
|
};
|