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.
- package/dist/cli.js +122 -52
- 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
|
|
15
|
-
import
|
|
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
|
|
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
|
-
|
|
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:
|
|
110
|
-
negative:
|
|
111
|
-
neutral:
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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__ */
|
|
130
|
-
|
|
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__ */
|
|
138
|
-
/* @__PURE__ */
|
|
139
|
-
|
|
174
|
+
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
175
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
176
|
+
chalk4.green("\u25B6"),
|
|
140
177
|
" ",
|
|
141
|
-
|
|
142
|
-
|
|
178
|
+
chalk4.bold(entity.name.toUpperCase()),
|
|
179
|
+
chalk4.dim(" \u2014 Detail")
|
|
143
180
|
] }),
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
|
|
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
|
-
|
|
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
|
|
159
|
-
import
|
|
160
|
-
import { jsx as
|
|
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__ */
|
|
163
|
-
|
|
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__ */
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
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
|
|
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__ */
|
|
734
|
-
/* @__PURE__ */
|
|
735
|
-
error && /* @__PURE__ */
|
|
736
|
-
loading && data.length === 0 ? /* @__PURE__ */
|
|
737
|
-
/* @__PURE__ */
|
|
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__ */
|
|
741
|
-
data.map((analysis, i) => /* @__PURE__ */
|
|
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__ */
|
|
751
|
-
|
|
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
|
|
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__ */
|
|
858
|
+
var instance = render(/* @__PURE__ */ jsx7(App, { apiUrl }), { patchConsole: false });
|
|
789
859
|
instance.waitUntilExit().then(() => {
|
|
790
860
|
process.stdout.write("\x1B[?1049l");
|
|
791
861
|
});
|