pythx-cli 0.0.4 → 0.0.6

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 +146 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
2
7
 
3
8
  // src/cli.tsx
4
9
  import { render } from "ink";
@@ -444,6 +449,11 @@ import { drizzle } from "drizzle-orm/postgres-js";
444
449
  import postgres from "postgres";
445
450
 
446
451
  // ../core/src/db/schema.ts
452
+ var schema_exports = {};
453
+ __export(schema_exports, {
454
+ posts: () => posts,
455
+ snapshots: () => snapshots
456
+ });
447
457
  import {
448
458
  pgTable,
449
459
  serial,
@@ -507,20 +517,106 @@ var posts = pgTable(
507
517
  ]
508
518
  );
509
519
 
520
+ // ../core/src/db/client.ts
521
+ var db = null;
522
+ function getDb() {
523
+ if (db) return db;
524
+ const connectionString = process.env.POSTGRES_URL;
525
+ if (!connectionString) {
526
+ throw new Error("POSTGRES_URL environment variable is required");
527
+ }
528
+ const client = postgres(connectionString, { prepare: false });
529
+ db = drizzle(client, { schema: schema_exports });
530
+ return db;
531
+ }
532
+
510
533
  // ../core/src/db/store.ts
511
534
  import { desc, eq } from "drizzle-orm";
535
+ async function persistAnalysis(entityId, snapshot, posts2, modelId) {
536
+ const db2 = getDb();
537
+ await db2.insert(schema_exports.snapshots).values({
538
+ entityId,
539
+ source: null,
540
+ avgScore: snapshot.averageScore,
541
+ positiveCount: snapshot.distribution.positive,
542
+ negativeCount: snapshot.distribution.negative,
543
+ neutralCount: snapshot.distribution.neutral,
544
+ totalCount: snapshot.distribution.total
545
+ });
546
+ for (const breakdown of snapshot.bySource) {
547
+ await db2.insert(schema_exports.snapshots).values({
548
+ entityId,
549
+ source: breakdown.source,
550
+ avgScore: breakdown.averageScore,
551
+ positiveCount: breakdown.distribution.positive,
552
+ negativeCount: breakdown.distribution.negative,
553
+ neutralCount: breakdown.distribution.neutral,
554
+ totalCount: breakdown.distribution.total
555
+ });
556
+ }
557
+ if (posts2.length > 0) {
558
+ await db2.insert(schema_exports.posts).values(
559
+ posts2.map((post) => ({
560
+ externalId: post.id,
561
+ source: post.source,
562
+ entityId,
563
+ text: post.text,
564
+ authorId: post.authorId,
565
+ authorUsername: post.authorUsername ?? null,
566
+ url: post.url,
567
+ createdAt: new Date(post.createdAt),
568
+ upvotes: post.metrics.upvotes,
569
+ comments: post.metrics.comments,
570
+ shares: post.metrics.shares,
571
+ sentimentLabel: post.sentiment.label,
572
+ sentimentConfidence: post.sentiment.score,
573
+ modelId
574
+ }))
575
+ ).onConflictDoNothing({ target: [schema_exports.posts.externalId, schema_exports.posts.source] });
576
+ }
577
+ }
578
+ async function persistEntityAnalysis(analysis, modelId) {
579
+ await persistAnalysis(
580
+ analysis.entity.id,
581
+ analysis.snapshot,
582
+ analysis.posts,
583
+ modelId
584
+ );
585
+ }
586
+ async function loadCachedPosts(entityId, limit = 50) {
587
+ const db2 = getDb();
588
+ 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) => ({
590
+ id: row.externalId,
591
+ source: row.source,
592
+ text: row.text,
593
+ authorId: row.authorId ?? "",
594
+ authorUsername: row.authorUsername ?? void 0,
595
+ createdAt: row.createdAt.toISOString(),
596
+ url: row.url ?? "",
597
+ metrics: {
598
+ upvotes: row.upvotes ?? 0,
599
+ comments: row.comments ?? 0,
600
+ shares: row.shares ?? 0
601
+ },
602
+ sentiment: {
603
+ label: row.sentimentLabel,
604
+ score: row.sentimentConfidence
605
+ }
606
+ }));
607
+ }
512
608
 
513
609
  // src/hooks/use-live-data.ts
514
610
  var POLL_INTERVAL = 30 * 6e4;
515
- function useLiveData(apiUrl) {
611
+ function useLiveData(apiUrl2) {
516
612
  const [data, setData] = useState2([]);
517
613
  const [loading, setLoading] = useState2(true);
518
614
  const [error, setError] = useState2(null);
519
615
  const [lastUpdated, setLastUpdated] = useState2(null);
520
616
  const fetchViaApi = useCallback(async () => {
521
- if (!apiUrl) return;
617
+ if (!apiUrl2) return;
522
618
  try {
523
- const res = await fetch(`${apiUrl}/api/compare`, {
619
+ const res = await fetch(`${apiUrl2}/api/compare`, {
524
620
  method: "POST",
525
621
  headers: { "Content-Type": "application/json" },
526
622
  body: JSON.stringify({
@@ -541,7 +637,22 @@ function useLiveData(apiUrl) {
541
637
  } finally {
542
638
  setLoading(false);
543
639
  }
544
- }, [apiUrl]);
640
+ }, [apiUrl2]);
641
+ const loadFromCache = useCallback(async () => {
642
+ const results = [];
643
+ for (const entity of DEFAULT_ENTITIES) {
644
+ const cached = await loadCachedPosts(entity.id, 50);
645
+ if (cached.length === 0) continue;
646
+ const snapshot = aggregate(cached);
647
+ results.push({
648
+ entity,
649
+ snapshot,
650
+ posts: cached,
651
+ fetchedAt: cached[0]?.sentiment ? (/* @__PURE__ */ new Date()).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
652
+ });
653
+ }
654
+ return results;
655
+ }, []);
545
656
  const fetchDirect = useCallback(async () => {
546
657
  try {
547
658
  const results = [];
@@ -554,23 +665,38 @@ function useLiveData(apiUrl) {
554
665
  allClassified.push(...classified);
555
666
  }
556
667
  const snapshot = aggregate(allClassified);
557
- results.push({
668
+ const analysis = {
558
669
  entity,
559
670
  snapshot,
560
671
  posts: allClassified,
561
672
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
562
- });
673
+ };
674
+ results.push(analysis);
675
+ try {
676
+ await persistEntityAnalysis(analysis, "hf-default");
677
+ } catch {
678
+ }
563
679
  }
564
680
  setData(results);
565
681
  setLastUpdated(/* @__PURE__ */ new Date());
566
682
  setError(null);
567
683
  } catch (err) {
684
+ try {
685
+ const cached = await loadFromCache();
686
+ if (cached.length > 0) {
687
+ setData(cached);
688
+ setLastUpdated(/* @__PURE__ */ new Date());
689
+ setError("Live fetch failed \u2014 showing cached data");
690
+ return;
691
+ }
692
+ } catch {
693
+ }
568
694
  setError(err instanceof Error ? err.message : "Analysis failed");
569
695
  } finally {
570
696
  setLoading(false);
571
697
  }
572
- }, []);
573
- const fetchData = apiUrl ? fetchViaApi : fetchDirect;
698
+ }, [loadFromCache]);
699
+ const fetchData = apiUrl2 ? fetchViaApi : fetchDirect;
574
700
  useEffect2(() => {
575
701
  fetchData();
576
702
  const interval = setInterval(fetchData, POLL_INTERVAL);
@@ -581,8 +707,8 @@ function useLiveData(apiUrl) {
581
707
 
582
708
  // src/app.tsx
583
709
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
584
- function App({ apiUrl }) {
585
- const { data, loading, error, lastUpdated, refresh } = useLiveData(apiUrl);
710
+ function App({ apiUrl: apiUrl2 }) {
711
+ const { data, loading, error, lastUpdated, refresh } = useLiveData(apiUrl2);
586
712
  const [activeIndex, setActiveIndex] = useState3(0);
587
713
  const [showDetail, setShowDetail] = useState3(true);
588
714
  const { exit } = useApp();
@@ -628,31 +754,38 @@ function App({ apiUrl }) {
628
754
 
629
755
  // src/cli.tsx
630
756
  import { jsx as jsx6 } from "react/jsx-runtime";
757
+ var DEFAULT_API_URL = "https://pythx.vercel.app";
631
758
  var cli = meow(
632
759
  `
633
760
  Usage
634
761
  $ pythx
635
762
 
636
763
  Options
637
- --api-url URL of the Pythx web API (default: direct mode, no server needed)
764
+ --api-url URL of the Pythx web API (default: ${DEFAULT_API_URL})
765
+ --direct Use direct mode (requires HF_API_TOKEN and POSTGRES_URL env vars)
638
766
 
639
767
  Examples
640
768
  $ pythx
641
769
  $ pythx --api-url http://localhost:3000
642
- $ pythx --api-url https://pythx.vercel.app
770
+ $ pythx --direct
643
771
  `,
644
772
  {
645
773
  importMeta: import.meta,
646
774
  flags: {
647
775
  apiUrl: {
648
776
  type: "string"
777
+ },
778
+ direct: {
779
+ type: "boolean",
780
+ default: false
649
781
  }
650
782
  }
651
783
  }
652
784
  );
785
+ var apiUrl = cli.flags.direct ? void 0 : cli.flags.apiUrl ?? DEFAULT_API_URL;
653
786
  process.stdout.write("\x1B[?1049h");
654
787
  process.stdout.write("\x1B[H");
655
- var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl: cli.flags.apiUrl }), { patchConsole: false });
788
+ var instance = render(/* @__PURE__ */ jsx6(App, { apiUrl }), { patchConsole: false });
656
789
  instance.waitUntilExit().then(() => {
657
790
  process.stdout.write("\x1B[?1049l");
658
791
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pythx-cli",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Real-time sentiment intelligence terminal for prediction markets",
5
5
  "type": "module",
6
6
  "bin": {