sentisense 0.42.0 → 0.43.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/README.md CHANGED
@@ -56,7 +56,7 @@ Get a key at [app.sentisense.ai/get-api-key](https://app.sentisense.ai/get-api-k
56
56
  | `sentiment <ticker>` | SentiSense Score, tone, attention, per-source breakdown, `--days N` history |
57
57
  | `mood` | Composite market sentiment, the signals behind it, and the sector map |
58
58
  | `analysts <ticker>` | Consensus, price target band, recent upgrades and downgrades |
59
- | `earnings [ticker]` | Forward calendar with no ticker, per-quarter analysis with one |
59
+ | `earnings [ticker]` | Forward calendar with no ticker, per-quarter analysis with one (`earnings AAPL`) |
60
60
  | `insiders <ticker>` | Filed Form 4 transactions, including whether they were pre-planned |
61
61
  | `insights <ticker>` | Generated signals, filterable by `--urgency` and `--type` |
62
62
  | `congress [ticker]` | Congressional disclosures, market-wide or for one symbol |
package/dist/cli.cjs CHANGED
@@ -70,6 +70,13 @@ var CliUsageError = class extends Error {
70
70
  this.hint = hint;
71
71
  }
72
72
  };
73
+ var UnknownTickerError = class extends Error {
74
+ constructor(ticker) {
75
+ super(`unknown ticker "${ticker}".`);
76
+ this.name = "UnknownTickerError";
77
+ this.ticker = ticker;
78
+ }
79
+ };
73
80
  var MissingKeyError = class extends Error {
74
81
  constructor() {
75
82
  super("no API key configured.");
@@ -93,6 +100,15 @@ function classify(error) {
93
100
  ]
94
101
  };
95
102
  }
103
+ if (error instanceof UnknownTickerError) {
104
+ return {
105
+ exitCode: EXIT.NOT_FOUND,
106
+ lines: [
107
+ `error: ${error.message}`,
108
+ "next: check the symbol. Use canonical tickers, for example GOOGL rather than GOOG and BRK.B rather than BRK-B."
109
+ ]
110
+ };
111
+ }
96
112
  if (error instanceof CliUsageError) {
97
113
  return {
98
114
  exitCode: EXIT.USAGE,
@@ -158,6 +174,48 @@ function classify(error) {
158
174
  };
159
175
  }
160
176
 
177
+ // src/cli/ticker.ts
178
+ function rejectSurplus(args, name, allowed) {
179
+ if (args.positionals.length <= allowed) return;
180
+ throw new CliUsageError(
181
+ `${name} takes one ticker, and got ${args.positionals.length}.`,
182
+ "run it once per ticker. Only quote accepts more than one."
183
+ );
184
+ }
185
+ function oneTicker(args, name) {
186
+ rejectSurplus(args, name, 1);
187
+ const ticker = args.positionals[0];
188
+ if (!ticker) {
189
+ throw new CliUsageError(
190
+ `${name} needs a ticker.`,
191
+ `for example: sentisense ${name} NVDA`
192
+ );
193
+ }
194
+ return ticker.toUpperCase();
195
+ }
196
+ function optionalTicker(args, name) {
197
+ rejectSurplus(args, name, 1);
198
+ return args.positionals[0]?.toUpperCase();
199
+ }
200
+ function rejectPositionals(args, name) {
201
+ if (args.positionals.length === 0) return;
202
+ throw new CliUsageError(
203
+ `${name} takes no ticker, and got ${args.positionals.length}.`,
204
+ `run "sentisense help ${name}" for what it accepts.`
205
+ );
206
+ }
207
+ async function verifyTickerOnEmpty(api, ticker) {
208
+ try {
209
+ await api.stocks.getQuote(ticker);
210
+ return void 0;
211
+ } catch (error) {
212
+ if (error instanceof NotFoundError) throw new UnknownTickerError(ticker);
213
+ return `could not verify ${ticker}, so the empty result is unconfirmed`;
214
+ }
215
+ }
216
+ var EMPTY_VERIFY_NOTE = "An empty result verifies the ticker before reporting no data, so a typo exits 4 rather";
217
+ var EMPTY_VERIFY_NOTE_2 = "than looking like a company with nothing to report.";
218
+
161
219
  // src/cli/render/doc.ts
162
220
  function cell(text, tone) {
163
221
  return tone ? { text, tone } : { text };
@@ -269,13 +327,7 @@ var analystsCommand = {
269
327
  days: { type: "number", placeholder: "N", describe: "Days of rating history (default 90)" }
270
328
  },
271
329
  async run({ args, client, full }) {
272
- const ticker = args.positionals[0]?.toUpperCase();
273
- if (!ticker) {
274
- throw new CliUsageError(
275
- "analysts needs a ticker.",
276
- "for example: sentisense analysts NVDA"
277
- );
278
- }
330
+ const ticker = oneTicker(args, "analysts");
279
331
  const lookbackDays = typeof args.flags.days === "number" ? args.flags.days : void 0;
280
332
  const api = client();
281
333
  const notes = [];
@@ -519,7 +571,9 @@ var congressCommand = {
519
571
  "Disclosures are filed after the fact, so the gap between the trade date and the",
520
572
  "disclosure date is part of the picture. The delay column carries it in days.",
521
573
  "Amounts are the filed ranges, never exact figures: that is how the filings work.",
522
- "A free key sees a short preview of either feed."
574
+ "A free key sees a short preview of either feed.",
575
+ EMPTY_VERIFY_NOTE,
576
+ EMPTY_VERIFY_NOTE_2
523
577
  ],
524
578
  flags: {
525
579
  days: { type: "number", placeholder: "N", describe: "Look-back window, 1 to 365 (default 90)" },
@@ -527,7 +581,8 @@ var congressCommand = {
527
581
  },
528
582
  async run({ args, client, full }) {
529
583
  const api = client();
530
- const ticker = args.positionals[0]?.toUpperCase();
584
+ const ticker = optionalTicker(args, "congress");
585
+ const notes = [];
531
586
  const lookbackDays = typeof args.flags.days === "number" ? args.flags.days : void 0;
532
587
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : void 0;
533
588
  let envelope;
@@ -546,6 +601,10 @@ var congressCommand = {
546
601
  );
547
602
  }
548
603
  const trades = envelope.data ?? [];
604
+ if (ticker && trades.length === 0) {
605
+ const note = await verifyTickerOnEmpty(api, ticker);
606
+ if (note) notes.push(note);
607
+ }
549
608
  const shown = full ? trades : trades.slice(0, 20);
550
609
  const blocks = [
551
610
  {
@@ -594,7 +653,7 @@ var congressCommand = {
594
653
  tone: "dim"
595
654
  });
596
655
  }
597
- return { json: envelope, doc: doc(...blocks) };
656
+ return { json: envelope, doc: doc(...blocks), notes };
598
657
  }
599
658
  };
600
659
 
@@ -612,14 +671,18 @@ var earningsCommand = {
612
671
  examples: [
613
672
  "sentisense earnings",
614
673
  "sentisense earnings --week next",
615
- "sentisense earnings NVDA",
616
- "sentisense earnings NVDA --limit 4 --full"
674
+ "sentisense earnings AAPL",
675
+ "sentisense earnings AAPL --limit 4 --full"
617
676
  ],
618
677
  notes: [
619
678
  "With no ticker this is the forward calendar: who reports, when, and the consensus EPS.",
620
679
  "With a ticker it is the backward-looking analysis: one entry per reported quarter with",
621
680
  "the editorial headline, and on a PRO key the written summary and guidance language.",
622
- "A free key sees the current week of the calendar and the latest quarter of the analysis."
681
+ "A free key sees the current week of the calendar and the latest quarter of the analysis.",
682
+ "Coverage of the per-ticker analysis is not the whole market, so a tracked company can",
683
+ "have no stored quarter yet.",
684
+ EMPTY_VERIFY_NOTE,
685
+ EMPTY_VERIFY_NOTE_2
623
686
  ],
624
687
  flags: {
625
688
  week: { type: "string", placeholder: "this|next", describe: "Calendar window shorthand" },
@@ -630,7 +693,8 @@ var earningsCommand = {
630
693
  },
631
694
  async run({ args, client, full }) {
632
695
  const api = client();
633
- const ticker = args.positionals[0]?.toUpperCase();
696
+ const ticker = optionalTicker(args, "earnings");
697
+ const notes = [];
634
698
  if (ticker) {
635
699
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : void 0;
636
700
  const envelope2 = await api.earnings.getSummaries(
@@ -638,6 +702,10 @@ var earningsCommand = {
638
702
  limit === void 0 ? void 0 : { limit }
639
703
  );
640
704
  const quarters = envelope2.data ?? [];
705
+ if (quarters.length === 0) {
706
+ const note = await verifyTickerOnEmpty(api, ticker);
707
+ if (note) notes.push(note);
708
+ }
641
709
  const blocks2 = [
642
710
  {
643
711
  kind: "head",
@@ -699,7 +767,7 @@ var earningsCommand = {
699
767
  tone: "dim"
700
768
  });
701
769
  }
702
- return { json: envelope2, doc: doc(...blocks2) };
770
+ return { json: envelope2, doc: doc(...blocks2), notes };
703
771
  }
704
772
  const week = typeof args.flags.week === "string" ? args.flags.week : void 0;
705
773
  if (week && week !== "this" && week !== "next") {
@@ -791,7 +859,9 @@ var flowsCommand = {
791
859
  "market. With a ticker it is that stock's institutional ownership and the quarter's",
792
860
  "notable position changes, since flows are only published market-wide.",
793
861
  "13F filings land up to 45 days after quarter end, so a still-open quarter shows only",
794
- "early filers and says so."
862
+ "early filers and says so, and a ticker reads the newest quarter that has closed.",
863
+ EMPTY_VERIFY_NOTE,
864
+ EMPTY_VERIFY_NOTE_2
795
865
  ],
796
866
  flags: {
797
867
  limit: { type: "number", placeholder: "N", describe: `Rows per side (default ${DEFAULT_ROWS})` },
@@ -799,7 +869,8 @@ var flowsCommand = {
799
869
  },
800
870
  async run({ args, client, full }) {
801
871
  const api = client();
802
- const ticker = args.positionals[0]?.toUpperCase();
872
+ const ticker = optionalTicker(args, "flows");
873
+ const notes = [];
803
874
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : DEFAULT_ROWS;
804
875
  const quarter = typeof args.flags.quarter === "string" ? args.flags.quarter : void 0;
805
876
  if (ticker) {
@@ -820,6 +891,10 @@ var flowsCommand = {
820
891
  });
821
892
  const data2 = envelope2.data;
822
893
  const holders = data2?.holders ?? [];
894
+ if (holders.length === 0) {
895
+ const note = await verifyTickerOnEmpty(api, ticker);
896
+ if (note) notes.push(note);
897
+ }
823
898
  const blocks2 = [
824
899
  {
825
900
  kind: "head",
@@ -878,7 +953,7 @@ var flowsCommand = {
878
953
  tone: "dim"
879
954
  });
880
955
  }
881
- return { json: envelope2, doc: doc(...blocks2) };
956
+ return { json: envelope2, doc: doc(...blocks2), notes };
882
957
  }
883
958
  const envelope = await api.institutional.getFlows(quarter, { limit });
884
959
  const data = envelope.data;
@@ -933,7 +1008,7 @@ var flowsCommand = {
933
1008
  };
934
1009
 
935
1010
  // src/version.ts
936
- var VERSION = "0.42.0";
1011
+ var VERSION = "0.43.0";
937
1012
 
938
1013
  // src/resources/analyst.ts
939
1014
  var Analyst = class {
@@ -2312,7 +2387,8 @@ var healthCommand = {
2312
2387
  "Exits 3 when the key is missing or rejected, 6 when the host cannot be reached."
2313
2388
  ],
2314
2389
  flags: {},
2315
- async run({ context }) {
2390
+ async run({ args, context }) {
2391
+ rejectPositionals(args, "health");
2316
2392
  const baseUrl = effectiveBaseUrl(context);
2317
2393
  const anonymous = createClient(context, { anonymous: true });
2318
2394
  const reach = await probe(() => anonymous.stocks.getMarketStatus());
@@ -2382,25 +2458,27 @@ var insidersCommand = {
2382
2458
  "Rows are individual filed transactions, newest first, not a net total.",
2383
2459
  "The plan column says whether the trade was under a confirmed pre-arranged 10b5-1 plan,",
2384
2460
  "which is the difference between a scheduled sale and a discretionary one.",
2385
- "A free key sees the top few transactions; a PRO key sees the window you asked for."
2461
+ "A free key sees the top few transactions; a PRO key sees the window you asked for.",
2462
+ EMPTY_VERIFY_NOTE,
2463
+ EMPTY_VERIFY_NOTE_2
2386
2464
  ],
2387
2465
  flags: {
2388
2466
  days: { type: "number", placeholder: "N", describe: "Look-back window, 1 to 365 (default 90)" }
2389
2467
  },
2390
2468
  async run({ args, client, full }) {
2391
- const ticker = args.positionals[0]?.toUpperCase();
2392
- if (!ticker) {
2393
- throw new CliUsageError(
2394
- "insiders needs a ticker.",
2395
- "for example: sentisense insiders NVDA"
2396
- );
2397
- }
2469
+ const ticker = oneTicker(args, "insiders");
2398
2470
  const lookbackDays = typeof args.flags.days === "number" ? args.flags.days : void 0;
2399
- const envelope = await client().insider.getTrades(
2471
+ const api = client();
2472
+ const notes = [];
2473
+ const envelope = await api.insider.getTrades(
2400
2474
  ticker,
2401
2475
  lookbackDays === void 0 ? void 0 : { lookbackDays }
2402
2476
  );
2403
2477
  const trades = envelope.data ?? [];
2478
+ if (trades.length === 0) {
2479
+ const note = await verifyTickerOnEmpty(api, ticker);
2480
+ if (note) notes.push(note);
2481
+ }
2404
2482
  const shown = full ? trades : trades.slice(0, 15);
2405
2483
  const buys = trades.filter((trade) => trade.transactionType === "BUY");
2406
2484
  const sells = trades.filter((trade) => trade.transactionType === "SELL");
@@ -2451,7 +2529,7 @@ var insidersCommand = {
2451
2529
  tone: "dim"
2452
2530
  });
2453
2531
  }
2454
- return { json: envelope, doc: doc(...blocks) };
2532
+ return { json: envelope, doc: doc(...blocks), notes };
2455
2533
  }
2456
2534
  };
2457
2535
 
@@ -2468,25 +2546,23 @@ var insightsCommand = {
2468
2546
  examples: [
2469
2547
  "sentisense insights NVDA",
2470
2548
  "sentisense insights NVDA --urgency high",
2471
- "sentisense insights NVDA --type insider_buy_signal --full"
2549
+ "sentisense insights NVDA --type institutional_position_change --full"
2472
2550
  ],
2473
2551
  notes: [
2474
2552
  "Signals are generated observations about filings, flows, and attention, ordered by",
2475
2553
  "urgency then confidence. They describe what the data shows, not what to do about it.",
2476
- "A free key sees the top three; a PRO key sees the whole list."
2554
+ "A free key sees the top three; a PRO key sees the whole list.",
2555
+ "Signal types vary by ticker and over time, so take --type from what a plain run reports",
2556
+ "rather than guessing a name.",
2557
+ EMPTY_VERIFY_NOTE,
2558
+ EMPTY_VERIFY_NOTE_2
2477
2559
  ],
2478
2560
  flags: {
2479
2561
  urgency: { type: "string", placeholder: "level", describe: "Filter to low, medium, or high" },
2480
2562
  type: { type: "string", placeholder: "name", describe: "Filter to one signal type" }
2481
2563
  },
2482
2564
  async run({ args, client, full }) {
2483
- const ticker = args.positionals[0]?.toUpperCase();
2484
- if (!ticker) {
2485
- throw new CliUsageError(
2486
- "insights needs a ticker.",
2487
- "for example: sentisense insights NVDA"
2488
- );
2489
- }
2565
+ const ticker = oneTicker(args, "insights");
2490
2566
  const urgency = typeof args.flags.urgency === "string" ? args.flags.urgency : void 0;
2491
2567
  if (urgency && !["low", "medium", "high"].includes(urgency)) {
2492
2568
  throw new CliUsageError(
@@ -2498,11 +2574,17 @@ var insightsCommand = {
2498
2574
  ...urgency ? { urgency } : {},
2499
2575
  ...typeof args.flags.type === "string" ? { insightType: args.flags.type } : {}
2500
2576
  };
2501
- const envelope = await client().insights.stock(
2577
+ const api = client();
2578
+ const notes = [];
2579
+ const envelope = await api.insights.stock(
2502
2580
  ticker,
2503
2581
  Object.keys(options).length > 0 ? options : void 0
2504
2582
  );
2505
2583
  const insights = envelope.data ?? [];
2584
+ if (insights.length === 0) {
2585
+ const note = await verifyTickerOnEmpty(api, ticker);
2586
+ if (note) notes.push(note);
2587
+ }
2506
2588
  const shown = full ? insights : insights.slice(0, 8);
2507
2589
  const blocks = [
2508
2590
  {
@@ -2555,7 +2637,7 @@ var insightsCommand = {
2555
2637
  tone: "dim"
2556
2638
  });
2557
2639
  }
2558
- return { json: envelope, doc: doc(...blocks) };
2640
+ return { json: envelope, doc: doc(...blocks), notes };
2559
2641
  }
2560
2642
  };
2561
2643
 
@@ -2584,7 +2666,8 @@ var moodCommand = {
2584
2666
  "46-55 neutral, 56-70 optimism, 71-85 greed, 86-100 extreme greed."
2585
2667
  ],
2586
2668
  flags: {},
2587
- async run({ client, full }) {
2669
+ async run({ args, client, full }) {
2670
+ rejectPositionals(args, "mood");
2588
2671
  const payload = await client().marketMood.get();
2589
2672
  const market = readMarket(payload);
2590
2673
  const sectors = readSectors(payload);
@@ -2669,7 +2752,9 @@ var newsCommand = {
2669
2752
  "A story is a cluster of articles covering the same event, not a single headline, so",
2670
2753
  "the size column is how many sources picked it up and impact ranks how much it moved.",
2671
2754
  "Tone is the average sentiment across the cluster, between -1 and 1.",
2672
- "--days only applies to the market-wide feed."
2755
+ "--days only applies to the market-wide feed.",
2756
+ EMPTY_VERIFY_NOTE,
2757
+ EMPTY_VERIFY_NOTE_2
2673
2758
  ],
2674
2759
  flags: {
2675
2760
  limit: { type: "number", placeholder: "N", describe: `Stories to return (default ${DEFAULT_LIMIT})` },
@@ -2677,10 +2762,15 @@ var newsCommand = {
2677
2762
  },
2678
2763
  async run({ args, client, full }) {
2679
2764
  const api = client();
2680
- const ticker = args.positionals[0]?.toUpperCase();
2765
+ const ticker = optionalTicker(args, "news");
2766
+ const notes = [];
2681
2767
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : DEFAULT_LIMIT;
2682
2768
  const days = typeof args.flags.days === "number" ? args.flags.days : void 0;
2683
2769
  const stories = ticker ? await api.documents.getStoriesByTicker(ticker, { limit }) : await api.documents.getStories({ limit, ...days === void 0 ? {} : { days } });
2770
+ if (ticker && stories.length === 0) {
2771
+ const note = await verifyTickerOnEmpty(api, ticker);
2772
+ if (note) notes.push(note);
2773
+ }
2684
2774
  const blocks = [
2685
2775
  {
2686
2776
  kind: "head",
@@ -2738,7 +2828,7 @@ var newsCommand = {
2738
2828
  tone: "dim"
2739
2829
  });
2740
2830
  }
2741
- return { json: stories, doc: doc(...blocks) };
2831
+ return { json: stories, doc: doc(...blocks), notes };
2742
2832
  }
2743
2833
  };
2744
2834
 
@@ -2766,23 +2856,25 @@ var optionsCommand = {
2766
2856
  "End of day, not live: readings describe the latest completed session and refresh the",
2767
2857
  "following morning. Percentiles are against that ticker's own trailing history, so they",
2768
2858
  "compare a stock to its past self, never to another stock.",
2769
- "Coverage is the most actively optioned names plus the tracked ETFs. A ticker outside",
2770
- "that set reports no coverage and exits 0, the same as any other empty result.",
2859
+ "Coverage is the most actively optioned names plus the tracked ETFs. A real ticker",
2860
+ "outside that set reports no coverage and exits 0, the same as any other empty result.",
2861
+ EMPTY_VERIFY_NOTE,
2862
+ EMPTY_VERIFY_NOTE_2,
2771
2863
  "A free key gets the full dossier for the first ten calls each month, then a headline",
2772
2864
  "preview. Calls that return no dossier do not count against that."
2773
2865
  ],
2774
2866
  flags: {},
2775
2867
  async run({ args, client, full }) {
2776
- const ticker = args.positionals[0]?.toUpperCase();
2777
- if (!ticker) {
2778
- throw new CliUsageError("options needs a ticker.", "for example: sentisense options NVDA");
2779
- }
2780
- const envelope = await client().stocks.getOptionsSummary(ticker);
2868
+ const ticker = oneTicker(args, "options");
2869
+ const api = client();
2870
+ const envelope = await api.stocks.getOptionsSummary(ticker);
2781
2871
  const data = envelope.data;
2782
2872
  if (!data) {
2873
+ const note = await verifyTickerOnEmpty(api, ticker);
2783
2874
  return {
2784
2875
  json: envelope,
2785
- doc: doc({ kind: "text", text: `No options coverage for ${ticker}.` })
2876
+ doc: doc({ kind: "text", text: `No options coverage for ${ticker}.` }),
2877
+ notes: note ? [note] : void 0
2786
2878
  };
2787
2879
  }
2788
2880
  const latest = data.latest ?? {};
@@ -3109,6 +3201,7 @@ var screenCommand = {
3109
3201
  etf: { type: "boolean", describe: "Screen the ETF universe" }
3110
3202
  },
3111
3203
  async run({ args, client, full }) {
3204
+ rejectPositionals(args, "screen");
3112
3205
  const api = client();
3113
3206
  const etf = args.flags.etf === true;
3114
3207
  if (args.flags.fields === true) {
@@ -3314,13 +3407,7 @@ var sentimentCommand = {
3314
3407
  days: { type: "number", placeholder: "N", describe: `Days of Score history (default ${DEFAULT_DAYS})` }
3315
3408
  },
3316
3409
  async run({ args, client, full }) {
3317
- const ticker = args.positionals[0]?.toUpperCase();
3318
- if (!ticker) {
3319
- throw new CliUsageError(
3320
- "sentiment needs a ticker.",
3321
- "for example: sentisense sentiment NVDA"
3322
- );
3323
- }
3410
+ const ticker = oneTicker(args, "sentiment");
3324
3411
  const days = typeof args.flags.days === "number" ? args.flags.days : DEFAULT_DAYS;
3325
3412
  if (days < 1) {
3326
3413
  throw new CliUsageError("--days must be at least 1.", "for example: --days 30");
package/dist/index.cjs CHANGED
@@ -1036,7 +1036,7 @@ var Trackers = class {
1036
1036
  };
1037
1037
 
1038
1038
  // src/version.ts
1039
- var VERSION = "0.42.0";
1039
+ var VERSION = "0.43.0";
1040
1040
 
1041
1041
  // src/client.ts
1042
1042
  var DEFAULT_BASE_URL = "https://app.sentisense.ai";