pythx-cli 0.1.0 → 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 +158 -49
- 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";
|
|
@@ -120,8 +120,8 @@ function trendArrow(prev, curr) {
|
|
|
120
120
|
if (delta < -0.02) return chalk3.red("\u25BC");
|
|
121
121
|
return chalk3.gray("\u2500");
|
|
122
122
|
}
|
|
123
|
-
function WindowBar({ windows }) {
|
|
124
|
-
const ordered = [...windows]
|
|
123
|
+
function WindowBar({ windows, selected }) {
|
|
124
|
+
const ordered = [...windows];
|
|
125
125
|
return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, flexDirection: "column", children: [
|
|
126
126
|
/* @__PURE__ */ jsx3(Text3, { children: chalk3.dim(" SENTIMENT OVER TIME") }),
|
|
127
127
|
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
@@ -132,7 +132,8 @@ function WindowBar({ windows }) {
|
|
|
132
132
|
ordered[i - 1].snapshot.averageScore,
|
|
133
133
|
w.snapshot.averageScore
|
|
134
134
|
) + " " : "";
|
|
135
|
-
|
|
135
|
+
const label = w.window === selected ? chalk3.bgWhite.black(` ${w.label} `) : chalk3.dim(w.label);
|
|
136
|
+
return `${arrow}${label}${chalk3.dim(":")}${score}`;
|
|
136
137
|
}).join(" ")
|
|
137
138
|
] }) })
|
|
138
139
|
] });
|
|
@@ -156,13 +157,17 @@ function formatPost(post) {
|
|
|
156
157
|
const ellipsis = post.text.length > 70 ? chalk4.dim("...") : "";
|
|
157
158
|
return ` ${tag} ${author}: "${text2}${ellipsis}"`;
|
|
158
159
|
}
|
|
159
|
-
function DetailPanel({ analysis, windows }) {
|
|
160
|
-
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
|
+
];
|
|
161
168
|
const scoreColor = snapshot.averageScore > 0.05 ? chalk4.green : snapshot.averageScore < -0.05 ? chalk4.red : chalk4.gray;
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
...snapshot.topNegative
|
|
165
|
-
].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");
|
|
166
171
|
return /* @__PURE__ */ jsxs4(
|
|
167
172
|
Box4,
|
|
168
173
|
{
|
|
@@ -176,7 +181,8 @@ function DetailPanel({ analysis, windows }) {
|
|
|
176
181
|
chalk4.green("\u25B6"),
|
|
177
182
|
" ",
|
|
178
183
|
chalk4.bold(entity.name.toUpperCase()),
|
|
179
|
-
|
|
184
|
+
windowLabel,
|
|
185
|
+
activeWindow ? chalk4.dim(` (${activeWindow.postCount} posts)`) : ""
|
|
180
186
|
] }),
|
|
181
187
|
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
182
188
|
chalk4.dim("avg: "),
|
|
@@ -185,35 +191,111 @@ function DetailPanel({ analysis, windows }) {
|
|
|
185
191
|
)
|
|
186
192
|
] })
|
|
187
193
|
] }),
|
|
188
|
-
windows && windows.length > 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(WindowBar, { windows }) }),
|
|
189
|
-
/* @__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)) })
|
|
190
196
|
]
|
|
191
197
|
}
|
|
192
198
|
);
|
|
193
199
|
}
|
|
194
200
|
|
|
195
|
-
// src/components/
|
|
201
|
+
// src/components/sentiment-chart.tsx
|
|
196
202
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
197
203
|
import chalk5 from "chalk";
|
|
198
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";
|
|
199
279
|
function StatusBar() {
|
|
200
|
-
return /* @__PURE__ */
|
|
201
|
-
|
|
280
|
+
return /* @__PURE__ */ jsx6(
|
|
281
|
+
Box6,
|
|
202
282
|
{
|
|
203
283
|
borderStyle: "single",
|
|
204
284
|
borderColor: "gray",
|
|
205
285
|
paddingX: 1,
|
|
206
286
|
justifyContent: "center",
|
|
207
|
-
children: /* @__PURE__ */
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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")
|
|
217
299
|
] })
|
|
218
300
|
}
|
|
219
301
|
);
|
|
@@ -650,6 +732,11 @@ function rowToPost(row) {
|
|
|
650
732
|
// ../core/src/windows.ts
|
|
651
733
|
import { and, eq as eq2, gte } from "drizzle-orm";
|
|
652
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 },
|
|
653
740
|
now: { label: "Now", ms: 6 * 60 * 60 * 1e3 },
|
|
654
741
|
"1d": { label: "1D", ms: 24 * 60 * 60 * 1e3 },
|
|
655
742
|
"1w": { label: "1W", ms: 7 * 24 * 60 * 60 * 1e3 },
|
|
@@ -658,6 +745,15 @@ var WINDOW_DEFS = {
|
|
|
658
745
|
"6m": { label: "6M", ms: 180 * 24 * 60 * 60 * 1e3 },
|
|
659
746
|
"12m": { label: "12M", ms: 365 * 24 * 60 * 60 * 1e3 }
|
|
660
747
|
};
|
|
748
|
+
var ALL_WINDOWS = [
|
|
749
|
+
"now",
|
|
750
|
+
"1d",
|
|
751
|
+
"1w",
|
|
752
|
+
"1m",
|
|
753
|
+
"90d",
|
|
754
|
+
"6m",
|
|
755
|
+
"12m"
|
|
756
|
+
];
|
|
661
757
|
|
|
662
758
|
// src/hooks/use-live-data.ts
|
|
663
759
|
var POLL_INTERVAL = 30 * 6e4;
|
|
@@ -770,12 +866,14 @@ function useLiveData(apiUrl2) {
|
|
|
770
866
|
}
|
|
771
867
|
|
|
772
868
|
// src/app.tsx
|
|
773
|
-
import { jsx as
|
|
869
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
774
870
|
function App({ apiUrl: apiUrl2 }) {
|
|
775
871
|
const { data, timeSeries, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
|
|
776
872
|
const [activeIndex, setActiveIndex] = useState3(0);
|
|
777
873
|
const [showDetail, setShowDetail] = useState3(true);
|
|
874
|
+
const [windowIndex, setWindowIndex] = useState3(0);
|
|
778
875
|
const { exit } = useApp();
|
|
876
|
+
const selectedWindow = ALL_WINDOWS[windowIndex];
|
|
779
877
|
useInput((input, key) => {
|
|
780
878
|
if (input === "q") {
|
|
781
879
|
exit();
|
|
@@ -783,6 +881,12 @@ function App({ apiUrl: apiUrl2 }) {
|
|
|
783
881
|
if (input === "r") {
|
|
784
882
|
refresh();
|
|
785
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
|
+
}
|
|
786
890
|
if (key.upArrow) {
|
|
787
891
|
setActiveIndex((i) => Math.max(0, i - 1));
|
|
788
892
|
}
|
|
@@ -794,36 +898,41 @@ function App({ apiUrl: apiUrl2 }) {
|
|
|
794
898
|
}
|
|
795
899
|
});
|
|
796
900
|
const activeAnalysis = data[activeIndex];
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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 }) })
|
|
813
921
|
] }),
|
|
814
|
-
showDetail && activeAnalysis && /* @__PURE__ */
|
|
922
|
+
showDetail && activeAnalysis && /* @__PURE__ */ jsx7(
|
|
815
923
|
DetailPanel,
|
|
816
924
|
{
|
|
817
925
|
analysis: activeAnalysis,
|
|
818
|
-
windows:
|
|
926
|
+
windows: activeWindows,
|
|
927
|
+
selectedWindow
|
|
819
928
|
}
|
|
820
929
|
),
|
|
821
|
-
/* @__PURE__ */
|
|
930
|
+
/* @__PURE__ */ jsx7(StatusBar, {})
|
|
822
931
|
] });
|
|
823
932
|
}
|
|
824
933
|
|
|
825
934
|
// src/cli.tsx
|
|
826
|
-
import { jsx as
|
|
935
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
827
936
|
var DEFAULT_API_URL = "https://pythx.vercel.app";
|
|
828
937
|
var cli = meow(
|
|
829
938
|
`
|
|
@@ -855,7 +964,7 @@ var cli = meow(
|
|
|
855
964
|
var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
|
|
856
965
|
process.stdout.write("\x1B[?1049h");
|
|
857
966
|
process.stdout.write("\x1B[H");
|
|
858
|
-
var instance = render(/* @__PURE__ */
|
|
967
|
+
var instance = render(/* @__PURE__ */ jsx8(App, { apiUrl }), { patchConsole: false });
|
|
859
968
|
instance.waitUntilExit().then(() => {
|
|
860
969
|
process.stdout.write("\x1B[?1049l");
|
|
861
970
|
});
|