pythx-cli 0.0.6 → 0.0.7

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 +111 -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,109 @@ 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 WindowBar({ windows }) {
118
+ const ordered = [...windows].reverse();
119
+ return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, flexDirection: "column", children: [
120
+ /* @__PURE__ */ jsx3(Text3, { children: chalk3.dim(" SENTIMENT OVER TIME") }),
121
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
122
+ " ",
123
+ ordered.map((w) => `${chalk3.dim(w.label + ":")}${colorScore(w.snapshot.averageScore)}`).join(" ")
124
+ ] }) })
125
+ ] });
126
+ }
127
+
128
+ // src/components/detail-panel.tsx
129
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
106
130
  function formatPost(post) {
107
131
  const { sentiment } = post;
108
132
  const labelMap = {
109
- positive: chalk3.green,
110
- negative: chalk3.red,
111
- neutral: chalk3.gray
133
+ positive: chalk4.green,
134
+ negative: chalk4.red,
135
+ neutral: chalk4.gray
112
136
  };
113
137
  const colorFn = labelMap[sentiment.label];
114
138
  const tag = colorFn(
115
139
  `[${sentiment.label.substring(0, 3).toUpperCase()} ${(sentiment.score * 100).toFixed(0)}%]`
116
140
  );
117
- const author = post.authorUsername ? chalk3.dim(`@${post.authorUsername}`) : chalk3.dim("@unknown");
141
+ const author = post.authorUsername ? chalk4.dim(`@${post.authorUsername}`) : chalk4.dim("@unknown");
118
142
  const text2 = post.text.replace(/\n/g, " ").substring(0, 70).trim();
119
- const ellipsis = post.text.length > 70 ? chalk3.dim("...") : "";
143
+ const ellipsis = post.text.length > 70 ? chalk4.dim("...") : "";
120
144
  return ` ${tag} ${author}: "${text2}${ellipsis}"`;
121
145
  }
122
- function DetailPanel({ analysis }) {
146
+ function DetailPanel({ analysis, windows }) {
123
147
  const { entity, snapshot } = analysis;
124
- const scoreColor = snapshot.averageScore > 0.05 ? chalk3.green : snapshot.averageScore < -0.05 ? chalk3.red : chalk3.gray;
148
+ const scoreColor = snapshot.averageScore > 0.05 ? chalk4.green : snapshot.averageScore < -0.05 ? chalk4.red : chalk4.gray;
125
149
  const allPosts = [
126
150
  ...snapshot.topPositive,
127
151
  ...snapshot.topNegative
128
152
  ].sort((a, b) => b.sentiment.score - a.sentiment.score);
129
- return /* @__PURE__ */ jsxs3(
130
- Box3,
153
+ return /* @__PURE__ */ jsxs4(
154
+ Box4,
131
155
  {
132
156
  flexDirection: "column",
133
157
  borderStyle: "single",
134
158
  borderColor: "gray",
135
159
  paddingX: 1,
136
160
  children: [
137
- /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", children: [
138
- /* @__PURE__ */ jsxs3(Text3, { children: [
139
- chalk3.green("\u25B6"),
161
+ /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
162
+ /* @__PURE__ */ jsxs4(Text4, { children: [
163
+ chalk4.green("\u25B6"),
140
164
  " ",
141
- chalk3.bold(entity.name.toUpperCase()),
142
- chalk3.dim(" \u2014 Detail")
165
+ chalk4.bold(entity.name.toUpperCase()),
166
+ chalk4.dim(" \u2014 Detail")
143
167
  ] }),
144
- /* @__PURE__ */ jsxs3(Text3, { children: [
145
- chalk3.dim("avg: "),
168
+ /* @__PURE__ */ jsxs4(Text4, { children: [
169
+ chalk4.dim("avg: "),
146
170
  scoreColor(
147
171
  `${snapshot.averageScore >= 0 ? "+" : ""}${snapshot.averageScore.toFixed(3)}`
148
172
  )
149
173
  ] })
150
174
  ] }),
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)) })
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)) })
152
177
  ]
153
178
  }
154
179
  );
155
180
  }
156
181
 
157
182
  // 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";
183
+ import { Box as Box5, Text as Text5 } from "ink";
184
+ import chalk5 from "chalk";
185
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
161
186
  function StatusBar() {
162
- return /* @__PURE__ */ jsx4(
163
- Box4,
187
+ return /* @__PURE__ */ jsx5(
188
+ Box5,
164
189
  {
165
190
  borderStyle: "single",
166
191
  borderColor: "gray",
167
192
  paddingX: 1,
168
193
  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")
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")
179
206
  ] })
180
207
  }
181
208
  );
@@ -586,7 +613,10 @@ async function persistEntityAnalysis(analysis, modelId) {
586
613
  async function loadCachedPosts(entityId, limit = 50) {
587
614
  const db2 = getDb();
588
615
  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) => ({
616
+ return rows.map(rowToPost);
617
+ }
618
+ function rowToPost(row) {
619
+ return {
590
620
  id: row.externalId,
591
621
  source: row.source,
592
622
  text: row.text,
@@ -603,13 +633,26 @@ async function loadCachedPosts(entityId, limit = 50) {
603
633
  label: row.sentimentLabel,
604
634
  score: row.sentimentConfidence
605
635
  }
606
- }));
636
+ };
607
637
  }
608
638
 
639
+ // ../core/src/windows.ts
640
+ import { and, eq as eq2, gte } from "drizzle-orm";
641
+ var WINDOW_DEFS = {
642
+ now: { label: "Now", ms: 6 * 60 * 60 * 1e3 },
643
+ "1d": { label: "1D", ms: 24 * 60 * 60 * 1e3 },
644
+ "1w": { label: "1W", ms: 7 * 24 * 60 * 60 * 1e3 },
645
+ "1m": { label: "1M", ms: 30 * 24 * 60 * 60 * 1e3 },
646
+ "90d": { label: "90D", ms: 90 * 24 * 60 * 60 * 1e3 },
647
+ "6m": { label: "6M", ms: 180 * 24 * 60 * 60 * 1e3 },
648
+ "12m": { label: "12M", ms: 365 * 24 * 60 * 60 * 1e3 }
649
+ };
650
+
609
651
  // src/hooks/use-live-data.ts
610
652
  var POLL_INTERVAL = 30 * 6e4;
611
653
  function useLiveData(apiUrl2) {
612
654
  const [data, setData] = useState2([]);
655
+ const [timeSeries, setTimeSeries] = useState2([]);
613
656
  const [loading, setLoading] = useState2(true);
614
657
  const [error, setError] = useState2(null);
615
658
  const [lastUpdated, setLastUpdated] = useState2(null);
@@ -629,6 +672,16 @@ function useLiveData(apiUrl2) {
629
672
  setData(json.data);
630
673
  setLastUpdated(/* @__PURE__ */ new Date());
631
674
  setError(null);
675
+ try {
676
+ const tsRes = await fetch(
677
+ `${apiUrl2}/api/timeseries?entities=${DEFAULT_ENTITIES.map((e) => e.id).join(",")}`
678
+ );
679
+ const tsJson = await tsRes.json();
680
+ if (tsJson.success && tsJson.data) {
681
+ setTimeSeries(tsJson.data);
682
+ }
683
+ } catch {
684
+ }
632
685
  } else {
633
686
  setError(json.error ?? "API error");
634
687
  }
@@ -702,13 +755,13 @@ function useLiveData(apiUrl2) {
702
755
  const interval = setInterval(fetchData, POLL_INTERVAL);
703
756
  return () => clearInterval(interval);
704
757
  }, [fetchData]);
705
- return { data, loading, error, lastUpdated, refresh: fetchData };
758
+ return { data, timeSeries, loading, error, lastUpdated, refresh: fetchData };
706
759
  }
707
760
 
708
761
  // src/app.tsx
709
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
762
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
710
763
  function App({ apiUrl: apiUrl2 }) {
711
- const { data, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
764
+ const { data, timeSeries, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
712
765
  const [activeIndex, setActiveIndex] = useState3(0);
713
766
  const [showDetail, setShowDetail] = useState3(true);
714
767
  const { exit } = useApp();
@@ -730,15 +783,15 @@ function App({ apiUrl: apiUrl2 }) {
730
783
  }
731
784
  });
732
785
  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(
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(
738
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)
739
792
  ) }) }),
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(
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(
742
795
  EntityRow,
743
796
  {
744
797
  analysis,
@@ -747,13 +800,19 @@ function App({ apiUrl: apiUrl2 }) {
747
800
  analysis.entity.id
748
801
  ))
749
802
  ] }),
750
- showDetail && activeAnalysis && /* @__PURE__ */ jsx5(DetailPanel, { analysis: activeAnalysis }),
751
- /* @__PURE__ */ jsx5(StatusBar, {})
803
+ showDetail && activeAnalysis && /* @__PURE__ */ jsx6(
804
+ DetailPanel,
805
+ {
806
+ analysis: activeAnalysis,
807
+ windows: timeSeries.find((ts) => ts.entity.id === activeAnalysis.entity.id)?.windows
808
+ }
809
+ ),
810
+ /* @__PURE__ */ jsx6(StatusBar, {})
752
811
  ] });
753
812
  }
754
813
 
755
814
  // src/cli.tsx
756
- import { jsx as jsx6 } from "react/jsx-runtime";
815
+ import { jsx as jsx7 } from "react/jsx-runtime";
757
816
  var DEFAULT_API_URL = "https://pythx.vercel.app";
758
817
  var cli = meow(
759
818
  `
@@ -785,7 +844,7 @@ var cli = meow(
785
844
  var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
786
845
  process.stdout.write("\x1B[?1049h");
787
846
  process.stdout.write("\x1B[H");
788
- var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl }), { patchConsole: false });
847
+ var instance = render(/* @__PURE__ */ jsx7(App, { apiUrl }), { patchConsole: false });
789
848
  instance.waitUntilExit().then(() => {
790
849
  process.stdout.write("\x1B[?1049l");
791
850
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pythx-cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Real-time sentiment intelligence terminal for prediction markets",
5
5
  "type": "module",
6
6
  "bin": {