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