pythx-cli 0.0.6 → 0.1.0

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 +122 -52
  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 Box5, Text as Text5, useApp, useInput } from "ink";
15
- import chalk5 from "chalk";
14
+ import { Box as Box6, Text as Text6, useApp, useInput } from "ink";
15
+ import chalk6 from "chalk";
16
16
 
17
17
  // src/components/header.tsx
18
18
  import { useState, useEffect } from "react";
@@ -85,11 +85,11 @@ function EntityRow({ analysis, isActive }) {
85
85
  const neg = String(distribution.negative).padStart(COL.stat);
86
86
  const tot = String(distribution.total).padStart(COL.stat);
87
87
  const colorName = isActive ? chalk2.green.bold(name) : chalk2.white(name);
88
- const colorScore = snapshot.averageScore > 0.05 ? chalk2.green(score) : snapshot.averageScore < -0.05 ? chalk2.red(score) : chalk2.gray(score);
88
+ const colorScore2 = snapshot.averageScore > 0.05 ? chalk2.green(score) : snapshot.averageScore < -0.05 ? chalk2.red(score) : chalk2.gray(score);
89
89
  return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { children: [
90
90
  cursor,
91
91
  colorName,
92
- colorScore,
92
+ colorScore2,
93
93
  " ",
94
94
  trend,
95
95
  chalk2.green(pos),
@@ -100,82 +100,120 @@ function EntityRow({ analysis, isActive }) {
100
100
  }
101
101
 
102
102
  // src/components/detail-panel.tsx
103
+ import { Box as Box4, Text as Text4 } from "ink";
104
+ import chalk4 from "chalk";
105
+
106
+ // src/components/window-bar.tsx
103
107
  import { Box as Box3, Text as Text3 } from "ink";
104
108
  import chalk3 from "chalk";
105
109
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
110
+ function colorScore(score) {
111
+ const prefix = score >= 0 ? "+" : "";
112
+ const formatted = `${prefix}${score.toFixed(2)}`;
113
+ if (score > 0.05) return chalk3.green(formatted);
114
+ if (score < -0.05) return chalk3.red(formatted);
115
+ return chalk3.gray(formatted);
116
+ }
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 }) {
124
+ const ordered = [...windows].reverse();
125
+ return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, flexDirection: "column", children: [
126
+ /* @__PURE__ */ jsx3(Text3, { children: chalk3.dim(" SENTIMENT OVER TIME") }),
127
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
128
+ " ",
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
+ return `${arrow}${chalk3.dim(w.label + ":")}${score}`;
136
+ }).join(" ")
137
+ ] }) })
138
+ ] });
139
+ }
140
+
141
+ // src/components/detail-panel.tsx
142
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
106
143
  function formatPost(post) {
107
144
  const { sentiment } = post;
108
145
  const labelMap = {
109
- positive: chalk3.green,
110
- negative: chalk3.red,
111
- neutral: chalk3.gray
146
+ positive: chalk4.green,
147
+ negative: chalk4.red,
148
+ neutral: chalk4.gray
112
149
  };
113
150
  const colorFn = labelMap[sentiment.label];
114
151
  const tag = colorFn(
115
152
  `[${sentiment.label.substring(0, 3).toUpperCase()} ${(sentiment.score * 100).toFixed(0)}%]`
116
153
  );
117
- const author = post.authorUsername ? chalk3.dim(`@${post.authorUsername}`) : chalk3.dim("@unknown");
154
+ const author = post.authorUsername ? chalk4.dim(`@${post.authorUsername}`) : chalk4.dim("@unknown");
118
155
  const text2 = post.text.replace(/\n/g, " ").substring(0, 70).trim();
119
- const ellipsis = post.text.length > 70 ? chalk3.dim("...") : "";
156
+ const ellipsis = post.text.length > 70 ? chalk4.dim("...") : "";
120
157
  return ` ${tag} ${author}: "${text2}${ellipsis}"`;
121
158
  }
122
- function DetailPanel({ analysis }) {
159
+ function DetailPanel({ analysis, windows }) {
123
160
  const { entity, snapshot } = analysis;
124
- const scoreColor = snapshot.averageScore > 0.05 ? chalk3.green : snapshot.averageScore < -0.05 ? chalk3.red : chalk3.gray;
161
+ const scoreColor = snapshot.averageScore > 0.05 ? chalk4.green : snapshot.averageScore < -0.05 ? chalk4.red : chalk4.gray;
125
162
  const allPosts = [
126
163
  ...snapshot.topPositive,
127
164
  ...snapshot.topNegative
128
165
  ].sort((a, b) => b.sentiment.score - a.sentiment.score);
129
- return /* @__PURE__ */ jsxs3(
130
- Box3,
166
+ return /* @__PURE__ */ jsxs4(
167
+ Box4,
131
168
  {
132
169
  flexDirection: "column",
133
170
  borderStyle: "single",
134
171
  borderColor: "gray",
135
172
  paddingX: 1,
136
173
  children: [
137
- /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", children: [
138
- /* @__PURE__ */ jsxs3(Text3, { children: [
139
- chalk3.green("\u25B6"),
174
+ /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
175
+ /* @__PURE__ */ jsxs4(Text4, { children: [
176
+ chalk4.green("\u25B6"),
140
177
  " ",
141
- chalk3.bold(entity.name.toUpperCase()),
142
- chalk3.dim(" \u2014 Detail")
178
+ chalk4.bold(entity.name.toUpperCase()),
179
+ chalk4.dim(" \u2014 Detail")
143
180
  ] }),
144
- /* @__PURE__ */ jsxs3(Text3, { children: [
145
- chalk3.dim("avg: "),
181
+ /* @__PURE__ */ jsxs4(Text4, { children: [
182
+ chalk4.dim("avg: "),
146
183
  scoreColor(
147
184
  `${snapshot.averageScore >= 0 ? "+" : ""}${snapshot.averageScore.toFixed(3)}`
148
185
  )
149
186
  ] })
150
187
  ] }),
151
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: allPosts.length === 0 ? /* @__PURE__ */ jsx3(Text3, { children: chalk3.dim(" No posts to display") }) : allPosts.slice(0, 8).map((post) => /* @__PURE__ */ jsx3(Text3, { children: formatPost(post) }, post.id)) })
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)) })
152
190
  ]
153
191
  }
154
192
  );
155
193
  }
156
194
 
157
195
  // src/components/status-bar.tsx
158
- import { Box as Box4, Text as Text4 } from "ink";
159
- import chalk4 from "chalk";
160
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
196
+ import { Box as Box5, Text as Text5 } from "ink";
197
+ import chalk5 from "chalk";
198
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
161
199
  function StatusBar() {
162
- return /* @__PURE__ */ jsx4(
163
- Box4,
200
+ return /* @__PURE__ */ jsx5(
201
+ Box5,
164
202
  {
165
203
  borderStyle: "single",
166
204
  borderColor: "gray",
167
205
  paddingX: 1,
168
206
  justifyContent: "center",
169
- children: /* @__PURE__ */ jsxs4(Text4, { children: [
170
- chalk4.dim("\u2591 "),
171
- chalk4.white("q"),
172
- chalk4.dim(":quit "),
173
- chalk4.white("\u2191\u2193"),
174
- chalk4.dim(":navigate "),
175
- chalk4.white("enter"),
176
- chalk4.dim(":expand "),
177
- chalk4.white("r"),
178
- chalk4.dim(":refresh")
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")
179
217
  ] })
180
218
  }
181
219
  );
@@ -586,7 +624,10 @@ async function persistEntityAnalysis(analysis, modelId) {
586
624
  async function loadCachedPosts(entityId, limit = 50) {
587
625
  const db2 = getDb();
588
626
  const rows = await db2.select().from(schema_exports.posts).where(eq(schema_exports.posts.entityId, entityId)).orderBy(desc(schema_exports.posts.createdAt)).limit(limit);
589
- return rows.map((row) => ({
627
+ return rows.map(rowToPost);
628
+ }
629
+ function rowToPost(row) {
630
+ return {
590
631
  id: row.externalId,
591
632
  source: row.source,
592
633
  text: row.text,
@@ -603,13 +644,26 @@ async function loadCachedPosts(entityId, limit = 50) {
603
644
  label: row.sentimentLabel,
604
645
  score: row.sentimentConfidence
605
646
  }
606
- }));
647
+ };
607
648
  }
608
649
 
650
+ // ../core/src/windows.ts
651
+ import { and, eq as eq2, gte } from "drizzle-orm";
652
+ var WINDOW_DEFS = {
653
+ now: { label: "Now", ms: 6 * 60 * 60 * 1e3 },
654
+ "1d": { label: "1D", ms: 24 * 60 * 60 * 1e3 },
655
+ "1w": { label: "1W", ms: 7 * 24 * 60 * 60 * 1e3 },
656
+ "1m": { label: "1M", ms: 30 * 24 * 60 * 60 * 1e3 },
657
+ "90d": { label: "90D", ms: 90 * 24 * 60 * 60 * 1e3 },
658
+ "6m": { label: "6M", ms: 180 * 24 * 60 * 60 * 1e3 },
659
+ "12m": { label: "12M", ms: 365 * 24 * 60 * 60 * 1e3 }
660
+ };
661
+
609
662
  // src/hooks/use-live-data.ts
610
663
  var POLL_INTERVAL = 30 * 6e4;
611
664
  function useLiveData(apiUrl2) {
612
665
  const [data, setData] = useState2([]);
666
+ const [timeSeries, setTimeSeries] = useState2([]);
613
667
  const [loading, setLoading] = useState2(true);
614
668
  const [error, setError] = useState2(null);
615
669
  const [lastUpdated, setLastUpdated] = useState2(null);
@@ -629,6 +683,16 @@ function useLiveData(apiUrl2) {
629
683
  setData(json.data);
630
684
  setLastUpdated(/* @__PURE__ */ new Date());
631
685
  setError(null);
686
+ try {
687
+ const tsRes = await fetch(
688
+ `${apiUrl2}/api/timeseries?entities=${DEFAULT_ENTITIES.map((e) => e.id).join(",")}`
689
+ );
690
+ const tsJson = await tsRes.json();
691
+ if (tsJson.success && tsJson.data) {
692
+ setTimeSeries(tsJson.data);
693
+ }
694
+ } catch {
695
+ }
632
696
  } else {
633
697
  setError(json.error ?? "API error");
634
698
  }
@@ -702,13 +766,13 @@ function useLiveData(apiUrl2) {
702
766
  const interval = setInterval(fetchData, POLL_INTERVAL);
703
767
  return () => clearInterval(interval);
704
768
  }, [fetchData]);
705
- return { data, loading, error, lastUpdated, refresh: fetchData };
769
+ return { data, timeSeries, loading, error, lastUpdated, refresh: fetchData };
706
770
  }
707
771
 
708
772
  // src/app.tsx
709
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
773
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
710
774
  function App({ apiUrl: apiUrl2 }) {
711
- const { data, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
775
+ const { data, timeSeries, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
712
776
  const [activeIndex, setActiveIndex] = useState3(0);
713
777
  const [showDetail, setShowDetail] = useState3(true);
714
778
  const { exit } = useApp();
@@ -730,15 +794,15 @@ function App({ apiUrl: apiUrl2 }) {
730
794
  }
731
795
  });
732
796
  const activeAnalysis = data[activeIndex];
733
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
734
- /* @__PURE__ */ jsx5(Header, { lastUpdated, loading }),
735
- error && /* @__PURE__ */ jsx5(Box5, { paddingX: 1, children: /* @__PURE__ */ jsx5(Text5, { children: chalk5.red(`Error: ${error}`) }) }),
736
- loading && data.length === 0 ? /* @__PURE__ */ jsx5(Box5, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx5(Text5, { children: chalk5.yellow("\u27F3 Fetching sentiment data...") }) }) : /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, children: [
737
- /* @__PURE__ */ jsx5(Box5, { marginY: 1, children: /* @__PURE__ */ jsx5(Text5, { children: chalk5.dim(
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(
738
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)
739
803
  ) }) }),
740
- /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { children: chalk5.dim(" " + "\u2500".repeat(COL.name + COL.score + 2 + COL.trend + COL.stat * 4)) }) }),
741
- data.map((analysis, i) => /* @__PURE__ */ jsx5(
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(
742
806
  EntityRow,
743
807
  {
744
808
  analysis,
@@ -747,13 +811,19 @@ function App({ apiUrl: apiUrl2 }) {
747
811
  analysis.entity.id
748
812
  ))
749
813
  ] }),
750
- showDetail && activeAnalysis && /* @__PURE__ */ jsx5(DetailPanel, { analysis: activeAnalysis }),
751
- /* @__PURE__ */ jsx5(StatusBar, {})
814
+ showDetail && activeAnalysis && /* @__PURE__ */ jsx6(
815
+ DetailPanel,
816
+ {
817
+ analysis: activeAnalysis,
818
+ windows: timeSeries.find((ts) => ts.entity.id === activeAnalysis.entity.id)?.windows
819
+ }
820
+ ),
821
+ /* @__PURE__ */ jsx6(StatusBar, {})
752
822
  ] });
753
823
  }
754
824
 
755
825
  // src/cli.tsx
756
- import { jsx as jsx6 } from "react/jsx-runtime";
826
+ import { jsx as jsx7 } from "react/jsx-runtime";
757
827
  var DEFAULT_API_URL = "https://pythx.vercel.app";
758
828
  var cli = meow(
759
829
  `
@@ -785,7 +855,7 @@ var cli = meow(
785
855
  var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
786
856
  process.stdout.write("\x1B[?1049h");
787
857
  process.stdout.write("\x1B[H");
788
- var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl }), { patchConsole: false });
858
+ var instance = render(/* @__PURE__ */ jsx7(App, { apiUrl }), { patchConsole: false });
789
859
  instance.waitUntilExit().then(() => {
790
860
  process.stdout.write("\x1B[?1049l");
791
861
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pythx-cli",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "description": "Real-time sentiment intelligence terminal for prediction markets",
5
5
  "type": "module",
6
6
  "bin": {