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.
Files changed (2) hide show
  1. package/dist/cli.js +171 -51
  2. 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 Box6, Text as Text6, useApp, useInput } from "ink";
15
- import chalk6 from "chalk";
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 WindowBar({ windows }) {
118
- const ordered = [...windows].reverse();
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) => `${chalk3.dim(w.label + ":")}${colorScore(w.snapshot.averageScore)}`).join(" ")
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 allPosts = [
150
- ...snapshot.topPositive,
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
- chalk4.dim(" \u2014 Detail")
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: allPosts.length === 0 ? /* @__PURE__ */ jsx4(Text4, { children: chalk4.dim(" No posts to display") }) : allPosts.slice(0, 8).map((post) => /* @__PURE__ */ jsx4(Text4, { children: formatPost(post) }, post.id)) })
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/status-bar.tsx
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__ */ jsx5(
188
- Box5,
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__ */ jsxs5(Text5, { children: [
195
- chalk5.dim("\u2591 "),
196
- chalk5.white("q"),
197
- chalk5.dim(":quit "),
198
- chalk5.white("\u2191\u2193"),
199
- chalk5.dim(":navigate "),
200
- chalk5.white("enter"),
201
- chalk5.dim(":expand "),
202
- chalk5.white("r"),
203
- chalk5.dim(":refresh "),
204
- chalk5.white("t"),
205
- chalk5.dim(":timeseries")
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 jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
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
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
787
- /* @__PURE__ */ jsx6(Header, { lastUpdated, loading }),
788
- error && /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.red(`Error: ${error}`) }) }),
789
- loading && data.length === 0 ? /* @__PURE__ */ jsx6(Box6, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.yellow("\u27F3 Fetching sentiment data...") }) }) : /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
790
- /* @__PURE__ */ jsx6(Box6, { marginY: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.dim(
791
- " " + "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)
792
- ) }) }),
793
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.dim(" " + "\u2500".repeat(COL.name + COL.score + 2 + COL.trend + COL.stat * 4)) }) }),
794
- data.map((analysis, i) => /* @__PURE__ */ jsx6(
795
- EntityRow,
796
- {
797
- analysis,
798
- isActive: i === activeIndex
799
- },
800
- analysis.entity.id
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__ */ jsx6(
922
+ showDetail && activeAnalysis && /* @__PURE__ */ jsx7(
804
923
  DetailPanel,
805
924
  {
806
925
  analysis: activeAnalysis,
807
- windows: timeSeries.find((ts) => ts.entity.id === activeAnalysis.entity.id)?.windows
926
+ windows: activeWindows,
927
+ selectedWindow
808
928
  }
809
929
  ),
810
- /* @__PURE__ */ jsx6(StatusBar, {})
930
+ /* @__PURE__ */ jsx7(StatusBar, {})
811
931
  ] });
812
932
  }
813
933
 
814
934
  // src/cli.tsx
815
- import { jsx as jsx7 } from "react/jsx-runtime";
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__ */ jsx7(App, { apiUrl }), { patchConsole: false });
967
+ var instance = render(/* @__PURE__ */ jsx8(App, { apiUrl }), { patchConsole: false });
848
968
  instance.waitUntilExit().then(() => {
849
969
  process.stdout.write("\x1B[?1049l");
850
970
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pythx-cli",
3
- "version": "0.0.7",
3
+ "version": "0.1.1",
4
4
  "description": "Real-time sentiment intelligence terminal for prediction markets",
5
5
  "type": "module",
6
6
  "bin": {