pythx-cli 0.0.5 → 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 +126 -60
  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,20 +633,33 @@ 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
- function useLiveData(apiUrl) {
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);
616
659
  const fetchViaApi = useCallback(async () => {
617
- if (!apiUrl) return;
660
+ if (!apiUrl2) return;
618
661
  try {
619
- const res = await fetch(`${apiUrl}/api/compare`, {
662
+ const res = await fetch(`${apiUrl2}/api/compare`, {
620
663
  method: "POST",
621
664
  headers: { "Content-Type": "application/json" },
622
665
  body: JSON.stringify({
@@ -629,6 +672,16 @@ function useLiveData(apiUrl) {
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
  }
@@ -637,7 +690,7 @@ function useLiveData(apiUrl) {
637
690
  } finally {
638
691
  setLoading(false);
639
692
  }
640
- }, [apiUrl]);
693
+ }, [apiUrl2]);
641
694
  const loadFromCache = useCallback(async () => {
642
695
  const results = [];
643
696
  for (const entity of DEFAULT_ENTITIES) {
@@ -696,19 +749,19 @@ function useLiveData(apiUrl) {
696
749
  setLoading(false);
697
750
  }
698
751
  }, [loadFromCache]);
699
- const fetchData = apiUrl ? fetchViaApi : fetchDirect;
752
+ const fetchData = apiUrl2 ? fetchViaApi : fetchDirect;
700
753
  useEffect2(() => {
701
754
  fetchData();
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";
710
- function App({ apiUrl }) {
711
- const { data, loading, error, lastUpdated, refresh } = useLiveData(apiUrl);
762
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
763
+ function App({ apiUrl: 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 }) {
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,38 +800,51 @@ function App({ apiUrl }) {
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";
816
+ var DEFAULT_API_URL = "https://pythx.vercel.app";
757
817
  var cli = meow(
758
818
  `
759
819
  Usage
760
820
  $ pythx
761
821
 
762
822
  Options
763
- --api-url URL of the Pythx web API (default: direct mode, no server needed)
823
+ --api-url URL of the Pythx web API (default: ${DEFAULT_API_URL})
824
+ --direct Use direct mode (requires HF_API_TOKEN and POSTGRES_URL env vars)
764
825
 
765
826
  Examples
766
827
  $ pythx
767
828
  $ pythx --api-url http://localhost:3000
768
- $ pythx --api-url https://pythx.vercel.app
829
+ $ pythx --direct
769
830
  `,
770
831
  {
771
832
  importMeta: import.meta,
772
833
  flags: {
773
834
  apiUrl: {
774
835
  type: "string"
836
+ },
837
+ direct: {
838
+ type: "boolean",
839
+ default: false
775
840
  }
776
841
  }
777
842
  }
778
843
  );
844
+ var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
779
845
  process.stdout.write("\x1B[?1049h");
780
846
  process.stdout.write("\x1B[H");
781
- var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl: cli.flags.apiUrl }), { patchConsole: false });
847
+ var instance = render(/* @__PURE__ */ jsx7(App, { apiUrl }), { patchConsole: false });
782
848
  instance.waitUntilExit().then(() => {
783
849
  process.stdout.write("\x1B[?1049l");
784
850
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pythx-cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Real-time sentiment intelligence terminal for prediction markets",
5
5
  "type": "module",
6
6
  "bin": {