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.
Files changed (2) hide show
  1. package/dist/cli.js +158 -49
  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";
@@ -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].reverse();
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
- return `${arrow}${chalk3.dim(w.label + ":")}${score}`;
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 allPosts = [
163
- ...snapshot.topPositive,
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
- chalk4.dim(" \u2014 Detail")
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: 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)) })
190
196
  ]
191
197
  }
192
198
  );
193
199
  }
194
200
 
195
- // src/components/status-bar.tsx
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__ */ jsx5(
201
- Box5,
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__ */ jsxs5(Text5, { children: [
208
- chalk5.dim("\u2591 "),
209
- chalk5.white("q"),
210
- chalk5.dim(":quit "),
211
- chalk5.white("\u2191\u2193"),
212
- chalk5.dim(":navigate "),
213
- chalk5.white("enter"),
214
- chalk5.dim(":expand "),
215
- chalk5.white("r"),
216
- chalk5.dim(":refresh")
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 jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
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
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
798
- /* @__PURE__ */ jsx6(Header, { lastUpdated, loading }),
799
- error && /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.red(`Error: ${error}`) }) }),
800
- 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: [
801
- /* @__PURE__ */ jsx6(Box6, { marginY: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.dim(
802
- " " + "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)
803
- ) }) }),
804
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { children: chalk6.dim(" " + "\u2500".repeat(COL.name + COL.score + 2 + COL.trend + COL.stat * 4)) }) }),
805
- data.map((analysis, i) => /* @__PURE__ */ jsx6(
806
- EntityRow,
807
- {
808
- analysis,
809
- isActive: i === activeIndex
810
- },
811
- analysis.entity.id
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__ */ jsx6(
922
+ showDetail && activeAnalysis && /* @__PURE__ */ jsx7(
815
923
  DetailPanel,
816
924
  {
817
925
  analysis: activeAnalysis,
818
- windows: timeSeries.find((ts) => ts.entity.id === activeAnalysis.entity.id)?.windows
926
+ windows: activeWindows,
927
+ selectedWindow
819
928
  }
820
929
  ),
821
- /* @__PURE__ */ jsx6(StatusBar, {})
930
+ /* @__PURE__ */ jsx7(StatusBar, {})
822
931
  ] });
823
932
  }
824
933
 
825
934
  // src/cli.tsx
826
- import { jsx as jsx7 } from "react/jsx-runtime";
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__ */ jsx7(App, { apiUrl }), { patchConsole: false });
967
+ var instance = render(/* @__PURE__ */ jsx8(App, { apiUrl }), { patchConsole: false });
859
968
  instance.waitUntilExit().then(() => {
860
969
  process.stdout.write("\x1B[?1049l");
861
970
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pythx-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Real-time sentiment intelligence terminal for prediction markets",
5
5
  "type": "module",
6
6
  "bin": {