pythx-cli 0.0.7 → 0.1.1
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/dist/cli.js +171 -51
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,8 +11,8 @@ import meow from "meow";
|
|
|
11
11
|
|
|
12
12
|
// src/app.tsx
|
|
13
13
|
import { useState as useState3 } from "react";
|
|
14
|
-
import { Box as
|
|
15
|
-
import
|
|
14
|
+
import { Box as Box7, Text as Text7, useApp, useInput } from "ink";
|
|
15
|
+
import chalk7 from "chalk";
|
|
16
16
|
|
|
17
17
|
// src/components/header.tsx
|
|
18
18
|
import { useState, useEffect } from "react";
|
|
@@ -114,13 +114,27 @@ function colorScore(score) {
|
|
|
114
114
|
if (score < -0.05) return chalk3.red(formatted);
|
|
115
115
|
return chalk3.gray(formatted);
|
|
116
116
|
}
|
|
117
|
-
function
|
|
118
|
-
const
|
|
117
|
+
function trendArrow(prev, curr) {
|
|
118
|
+
const delta = curr - prev;
|
|
119
|
+
if (delta > 0.02) return chalk3.green("\u25B2");
|
|
120
|
+
if (delta < -0.02) return chalk3.red("\u25BC");
|
|
121
|
+
return chalk3.gray("\u2500");
|
|
122
|
+
}
|
|
123
|
+
function WindowBar({ windows, selected }) {
|
|
124
|
+
const ordered = [...windows];
|
|
119
125
|
return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, flexDirection: "column", children: [
|
|
120
126
|
/* @__PURE__ */ jsx3(Text3, { children: chalk3.dim(" SENTIMENT OVER TIME") }),
|
|
121
127
|
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
122
128
|
" ",
|
|
123
|
-
ordered.map((w) =>
|
|
129
|
+
ordered.map((w, i) => {
|
|
130
|
+
const score = colorScore(w.snapshot.averageScore);
|
|
131
|
+
const arrow = i > 0 ? trendArrow(
|
|
132
|
+
ordered[i - 1].snapshot.averageScore,
|
|
133
|
+
w.snapshot.averageScore
|
|
134
|
+
) + " " : "";
|
|
135
|
+
const label = w.window === selected ? chalk3.bgWhite.black(` ${w.label} `) : chalk3.dim(w.label);
|
|
136
|
+
return `${arrow}${label}${chalk3.dim(":")}${score}`;
|
|
137
|
+
}).join(" ")
|
|
124
138
|
] }) })
|
|
125
139
|
] });
|
|
126
140
|
}
|
|
@@ -143,13 +157,17 @@ function formatPost(post) {
|
|
|
143
157
|
const ellipsis = post.text.length > 70 ? chalk4.dim("...") : "";
|
|
144
158
|
return ` ${tag} ${author}: "${text2}${ellipsis}"`;
|
|
145
159
|
}
|
|
146
|
-
function DetailPanel({ analysis, windows }) {
|
|
147
|
-
const { entity, snapshot } = analysis;
|
|
160
|
+
function DetailPanel({ analysis, windows, selectedWindow }) {
|
|
161
|
+
const { entity, snapshot: liveSnapshot } = analysis;
|
|
162
|
+
const activeWindow = windows?.find((w) => w.window === selectedWindow);
|
|
163
|
+
const snapshot = activeWindow?.snapshot ?? liveSnapshot;
|
|
164
|
+
const posts2 = activeWindow?.posts ?? [
|
|
165
|
+
...liveSnapshot.topPositive,
|
|
166
|
+
...liveSnapshot.topNegative
|
|
167
|
+
];
|
|
148
168
|
const scoreColor = snapshot.averageScore > 0.05 ? chalk4.green : snapshot.averageScore < -0.05 ? chalk4.red : chalk4.gray;
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
...snapshot.topNegative
|
|
152
|
-
].sort((a, b) => b.sentiment.score - a.sentiment.score);
|
|
169
|
+
const sortedPosts = [...posts2].sort((a, b) => b.sentiment.score - a.sentiment.score);
|
|
170
|
+
const windowLabel = selectedWindow && selectedWindow !== "now" ? chalk4.dim(` \u2014 ${selectedWindow.toUpperCase()}`) : chalk4.dim(" \u2014 Detail");
|
|
153
171
|
return /* @__PURE__ */ jsxs4(
|
|
154
172
|
Box4,
|
|
155
173
|
{
|
|
@@ -163,7 +181,8 @@ function DetailPanel({ analysis, windows }) {
|
|
|
163
181
|
chalk4.green("\u25B6"),
|
|
164
182
|
" ",
|
|
165
183
|
chalk4.bold(entity.name.toUpperCase()),
|
|
166
|
-
|
|
184
|
+
windowLabel,
|
|
185
|
+
activeWindow ? chalk4.dim(` (${activeWindow.postCount} posts)`) : ""
|
|
167
186
|
] }),
|
|
168
187
|
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
169
188
|
chalk4.dim("avg: "),
|
|
@@ -172,37 +191,111 @@ function DetailPanel({ analysis, windows }) {
|
|
|
172
191
|
)
|
|
173
192
|
] })
|
|
174
193
|
] }),
|
|
175
|
-
windows && windows.length > 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(WindowBar, { windows }) }),
|
|
176
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children:
|
|
194
|
+
windows && windows.length > 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(WindowBar, { windows, selected: selectedWindow }) }),
|
|
195
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: sortedPosts.length === 0 ? /* @__PURE__ */ jsx4(Text4, { children: chalk4.dim(" No posts to display") }) : sortedPosts.slice(0, 8).map((post) => /* @__PURE__ */ jsx4(Text4, { children: formatPost(post) }, post.id)) })
|
|
177
196
|
]
|
|
178
197
|
}
|
|
179
198
|
);
|
|
180
199
|
}
|
|
181
200
|
|
|
182
|
-
// src/components/
|
|
201
|
+
// src/components/sentiment-chart.tsx
|
|
183
202
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
184
203
|
import chalk5 from "chalk";
|
|
185
204
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
205
|
+
var CHART_HEIGHT = 9;
|
|
206
|
+
var COL_WIDTH = 5;
|
|
207
|
+
var NEUTRAL_ROW = 4;
|
|
208
|
+
function scoreToRow(score) {
|
|
209
|
+
const clamped = Math.max(-1, Math.min(1, score));
|
|
210
|
+
return Math.round((1 - clamped) / 2 * (CHART_HEIGHT - 1));
|
|
211
|
+
}
|
|
212
|
+
function SentimentChart({ windows, selectedWindow }) {
|
|
213
|
+
if (windows.length === 0) return null;
|
|
214
|
+
const cols = [...windows].reverse();
|
|
215
|
+
const dataRows = cols.map((w) => scoreToRow(w.snapshot.averageScore));
|
|
216
|
+
const scores = cols.map((w) => w.snapshot.averageScore);
|
|
217
|
+
const lines = [];
|
|
218
|
+
for (let row = 0; row < CHART_HEIGHT; row++) {
|
|
219
|
+
let yLabel;
|
|
220
|
+
if (row === 0) yLabel = chalk5.green(" Pos");
|
|
221
|
+
else if (row === NEUTRAL_ROW) yLabel = chalk5.dim(" Neu");
|
|
222
|
+
else if (row === CHART_HEIGHT - 1) yLabel = chalk5.red(" Neg");
|
|
223
|
+
else yLabel = " ";
|
|
224
|
+
let line = yLabel + chalk5.dim(" \u2502");
|
|
225
|
+
for (let col = 0; col < cols.length; col++) {
|
|
226
|
+
const dr = dataRows[col];
|
|
227
|
+
const score = scores[col];
|
|
228
|
+
const isPositive = score > 0.05;
|
|
229
|
+
const isNegative = score < -0.05;
|
|
230
|
+
const isSelected = cols[col].window === selectedWindow;
|
|
231
|
+
const marker = isPositive ? "\u25B2" : isNegative ? "\u25BC" : "\u25C6";
|
|
232
|
+
if (row === dr) {
|
|
233
|
+
const color = isPositive ? chalk5.green : isNegative ? chalk5.red : chalk5.white;
|
|
234
|
+
const markerStr = isSelected ? chalk5.bold(color(` ${marker}`)) : color(` ${marker}`);
|
|
235
|
+
if (row === NEUTRAL_ROW) {
|
|
236
|
+
line += chalk5.dim("\u2500\u2500") + (isSelected ? chalk5.bold(color(marker)) : color(marker)) + chalk5.dim("\u2500\u2500");
|
|
237
|
+
line = line;
|
|
238
|
+
} else {
|
|
239
|
+
line += markerStr + " ".repeat(Math.max(0, COL_WIDTH - 2));
|
|
240
|
+
}
|
|
241
|
+
} else if (row === NEUTRAL_ROW) {
|
|
242
|
+
line += chalk5.dim("\u2500".repeat(COL_WIDTH));
|
|
243
|
+
} else if (isPositive && row > dr && row < NEUTRAL_ROW) {
|
|
244
|
+
const fillChar = isSelected ? "\u2588" : "\u2591";
|
|
245
|
+
line += chalk5.green(` ${fillChar}` + " ".repeat(Math.max(0, COL_WIDTH - 2)));
|
|
246
|
+
} else if (isNegative && row < dr && row > NEUTRAL_ROW) {
|
|
247
|
+
const fillChar = isSelected ? "\u2588" : "\u2591";
|
|
248
|
+
line += chalk5.red(` ${fillChar}` + " ".repeat(Math.max(0, COL_WIDTH - 2)));
|
|
249
|
+
} else {
|
|
250
|
+
line += " ".repeat(COL_WIDTH);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
lines.push(line);
|
|
254
|
+
}
|
|
255
|
+
let xAxis = " " + chalk5.dim("\u2514");
|
|
256
|
+
for (let i = 0; i < cols.length; i++) {
|
|
257
|
+
xAxis += chalk5.dim("\u2500".repeat(COL_WIDTH));
|
|
258
|
+
}
|
|
259
|
+
lines.push(xAxis);
|
|
260
|
+
let xLabels = " ";
|
|
261
|
+
for (const w of cols) {
|
|
262
|
+
if (w.window === selectedWindow) {
|
|
263
|
+
xLabels += chalk5.white.bold(w.label.padEnd(COL_WIDTH));
|
|
264
|
+
} else {
|
|
265
|
+
xLabels += chalk5.dim(w.label.padEnd(COL_WIDTH));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
lines.push(xLabels);
|
|
269
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
|
|
270
|
+
/* @__PURE__ */ jsx5(Text5, { children: chalk5.dim("SENTIMENT") }),
|
|
271
|
+
lines.map((l, i) => /* @__PURE__ */ jsx5(Text5, { children: l }, i))
|
|
272
|
+
] });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/components/status-bar.tsx
|
|
276
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
277
|
+
import chalk6 from "chalk";
|
|
278
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
186
279
|
function StatusBar() {
|
|
187
|
-
return /* @__PURE__ */
|
|
188
|
-
|
|
280
|
+
return /* @__PURE__ */ jsx6(
|
|
281
|
+
Box6,
|
|
189
282
|
{
|
|
190
283
|
borderStyle: "single",
|
|
191
284
|
borderColor: "gray",
|
|
192
285
|
paddingX: 1,
|
|
193
286
|
justifyContent: "center",
|
|
194
|
-
children: /* @__PURE__ */
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
287
|
+
children: /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
288
|
+
chalk6.dim("\u2591 "),
|
|
289
|
+
chalk6.white("q"),
|
|
290
|
+
chalk6.dim(":quit "),
|
|
291
|
+
chalk6.white("\u2191\u2193"),
|
|
292
|
+
chalk6.dim(":navigate "),
|
|
293
|
+
chalk6.white("enter"),
|
|
294
|
+
chalk6.dim(":expand "),
|
|
295
|
+
chalk6.white("r"),
|
|
296
|
+
chalk6.dim(":refresh "),
|
|
297
|
+
chalk6.white("w/W"),
|
|
298
|
+
chalk6.dim(":window")
|
|
206
299
|
] })
|
|
207
300
|
}
|
|
208
301
|
);
|
|
@@ -639,6 +732,11 @@ function rowToPost(row) {
|
|
|
639
732
|
// ../core/src/windows.ts
|
|
640
733
|
import { and, eq as eq2, gte } from "drizzle-orm";
|
|
641
734
|
var WINDOW_DEFS = {
|
|
735
|
+
"1min": { label: "1MIN", ms: 60 * 1e3 },
|
|
736
|
+
"5min": { label: "5MIN", ms: 5 * 60 * 1e3 },
|
|
737
|
+
"15min": { label: "15MIN", ms: 15 * 60 * 1e3 },
|
|
738
|
+
"1hr": { label: "1HR", ms: 60 * 60 * 1e3 },
|
|
739
|
+
"6hr": { label: "6HR", ms: 6 * 60 * 60 * 1e3 },
|
|
642
740
|
now: { label: "Now", ms: 6 * 60 * 60 * 1e3 },
|
|
643
741
|
"1d": { label: "1D", ms: 24 * 60 * 60 * 1e3 },
|
|
644
742
|
"1w": { label: "1W", ms: 7 * 24 * 60 * 60 * 1e3 },
|
|
@@ -647,6 +745,15 @@ var WINDOW_DEFS = {
|
|
|
647
745
|
"6m": { label: "6M", ms: 180 * 24 * 60 * 60 * 1e3 },
|
|
648
746
|
"12m": { label: "12M", ms: 365 * 24 * 60 * 60 * 1e3 }
|
|
649
747
|
};
|
|
748
|
+
var ALL_WINDOWS = [
|
|
749
|
+
"now",
|
|
750
|
+
"1d",
|
|
751
|
+
"1w",
|
|
752
|
+
"1m",
|
|
753
|
+
"90d",
|
|
754
|
+
"6m",
|
|
755
|
+
"12m"
|
|
756
|
+
];
|
|
650
757
|
|
|
651
758
|
// src/hooks/use-live-data.ts
|
|
652
759
|
var POLL_INTERVAL = 30 * 6e4;
|
|
@@ -759,12 +866,14 @@ function useLiveData(apiUrl2) {
|
|
|
759
866
|
}
|
|
760
867
|
|
|
761
868
|
// src/app.tsx
|
|
762
|
-
import { jsx as
|
|
869
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
763
870
|
function App({ apiUrl: apiUrl2 }) {
|
|
764
871
|
const { data, timeSeries, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
|
|
765
872
|
const [activeIndex, setActiveIndex] = useState3(0);
|
|
766
873
|
const [showDetail, setShowDetail] = useState3(true);
|
|
874
|
+
const [windowIndex, setWindowIndex] = useState3(0);
|
|
767
875
|
const { exit } = useApp();
|
|
876
|
+
const selectedWindow = ALL_WINDOWS[windowIndex];
|
|
768
877
|
useInput((input, key) => {
|
|
769
878
|
if (input === "q") {
|
|
770
879
|
exit();
|
|
@@ -772,6 +881,12 @@ function App({ apiUrl: apiUrl2 }) {
|
|
|
772
881
|
if (input === "r") {
|
|
773
882
|
refresh();
|
|
774
883
|
}
|
|
884
|
+
if (input === "w") {
|
|
885
|
+
setWindowIndex((i) => (i + 1) % ALL_WINDOWS.length);
|
|
886
|
+
}
|
|
887
|
+
if (input === "W") {
|
|
888
|
+
setWindowIndex((i) => (i - 1 + ALL_WINDOWS.length) % ALL_WINDOWS.length);
|
|
889
|
+
}
|
|
775
890
|
if (key.upArrow) {
|
|
776
891
|
setActiveIndex((i) => Math.max(0, i - 1));
|
|
777
892
|
}
|
|
@@ -783,36 +898,41 @@ function App({ apiUrl: apiUrl2 }) {
|
|
|
783
898
|
}
|
|
784
899
|
});
|
|
785
900
|
const activeAnalysis = data[activeIndex];
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
901
|
+
const activeWindows = timeSeries.find((ts) => ts.entity.id === activeAnalysis?.entity.id)?.windows;
|
|
902
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
903
|
+
/* @__PURE__ */ jsx7(Header, { lastUpdated, loading }),
|
|
904
|
+
error && /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text7, { children: chalk7.red(`Error: ${error}`) }) }),
|
|
905
|
+
loading && data.length === 0 ? /* @__PURE__ */ jsx7(Box7, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx7(Text7, { children: chalk7.yellow("\u27F3 Fetching sentiment data...") }) }) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", alignItems: "flex-start", children: [
|
|
906
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
907
|
+
/* @__PURE__ */ jsx7(Box7, { marginY: 1, children: /* @__PURE__ */ jsx7(Text7, { children: chalk7.dim(
|
|
908
|
+
" " + "ENTITY".padEnd(COL.name) + "SCORE".padStart(COL.score) + " " + "TREND".padEnd(COL.trend) + "POS".padStart(COL.stat) + "NEU".padStart(COL.stat) + "NEG".padStart(COL.stat) + "TOT".padStart(COL.stat)
|
|
909
|
+
) }) }),
|
|
910
|
+
/* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text7, { children: chalk7.dim(" " + "\u2500".repeat(COL.name + COL.score + 2 + COL.trend + COL.stat * 4)) }) }),
|
|
911
|
+
data.map((analysis, i) => /* @__PURE__ */ jsx7(
|
|
912
|
+
EntityRow,
|
|
913
|
+
{
|
|
914
|
+
analysis,
|
|
915
|
+
isActive: i === activeIndex
|
|
916
|
+
},
|
|
917
|
+
analysis.entity.id
|
|
918
|
+
))
|
|
919
|
+
] }),
|
|
920
|
+
activeWindows && activeWindows.length > 0 && /* @__PURE__ */ jsx7(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx7(SentimentChart, { windows: activeWindows, selectedWindow }) })
|
|
802
921
|
] }),
|
|
803
|
-
showDetail && activeAnalysis && /* @__PURE__ */
|
|
922
|
+
showDetail && activeAnalysis && /* @__PURE__ */ jsx7(
|
|
804
923
|
DetailPanel,
|
|
805
924
|
{
|
|
806
925
|
analysis: activeAnalysis,
|
|
807
|
-
windows:
|
|
926
|
+
windows: activeWindows,
|
|
927
|
+
selectedWindow
|
|
808
928
|
}
|
|
809
929
|
),
|
|
810
|
-
/* @__PURE__ */
|
|
930
|
+
/* @__PURE__ */ jsx7(StatusBar, {})
|
|
811
931
|
] });
|
|
812
932
|
}
|
|
813
933
|
|
|
814
934
|
// src/cli.tsx
|
|
815
|
-
import { jsx as
|
|
935
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
816
936
|
var DEFAULT_API_URL = "https://pythx.vercel.app";
|
|
817
937
|
var cli = meow(
|
|
818
938
|
`
|
|
@@ -844,7 +964,7 @@ var cli = meow(
|
|
|
844
964
|
var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
|
|
845
965
|
process.stdout.write("\x1B[?1049h");
|
|
846
966
|
process.stdout.write("\x1B[H");
|
|
847
|
-
var instance = render(/* @__PURE__ */
|
|
967
|
+
var instance = render(/* @__PURE__ */ jsx8(App, { apiUrl }), { patchConsole: false });
|
|
848
968
|
instance.waitUntilExit().then(() => {
|
|
849
969
|
process.stdout.write("\x1B[?1049l");
|
|
850
970
|
});
|