sentisense 0.61.0 → 0.62.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
@@ -68,7 +68,7 @@ npx -y sentisense@latest health
68
68
  |---------|--------------|
69
69
  | `auth [key]` | Store a key, show what is configured, or `--remove` it |
70
70
  | `health` | Reachability, key validity, latency, and the resolved base URL |
71
- | `quote <ticker>...` | Price, day range, 52-week range, market cap, P/E. One request per ticker |
71
+ | `quote <ticker>...` | Price, day range, 52-week range, market cap, P/E. One request per ticker; ETFs show AUM, expense ratio and NAV |
72
72
  | `sentiment <ticker>` | SentiSense Score, tone, attention, per-source breakdown, `--days N` history |
73
73
  | `mood` | Composite market sentiment, the signals behind it, and the sector map |
74
74
  | `analysts <ticker>` | Consensus, price target band, recent upgrades and downgrades. `--coverage` for who covers it, by firm |
@@ -238,7 +238,10 @@ client.stocks.getProfile("AAPL") // Company profile
238
238
  client.stocks.getChart("AAPL", { timeframe: "6M" }) // OHLCV chart data
239
239
  client.stocks.getMarketStatus() // Market open/closed
240
240
  client.stocks.getFundamentals("AAPL") // Financial data
241
- client.stocks.getShortInterest("GME") // Short interest
241
+ client.stocks.getShortInterest("GME", { limit: 12 }) // Short interest by settlement date
242
+ client.stocks.getShortVolume("GME") // Daily short volume
243
+ client.stocks.getFloat("GME") // Free float
244
+ client.stocks.getGraph("AAPL", { depth: 1 }) // Company knowledge graph: people, products, peers
242
245
  client.stocks.getOptionsSummary("NVDA") // End-of-day options dossier
243
246
  client.stocks.getOptionsHistory("NVDA", { window: "2y" }) // Daily options aggregates over time
244
247
  client.stocks.getRating("AAPL") // SentiSense Rating: score, letter, percentile, dimensions
@@ -247,6 +250,8 @@ client.stocks.getAISummary("AAPL", { depth: "deep" }) // AI report (PRO)
247
250
 
248
251
  Price fields carry `priceAsOf` (Unix milliseconds) for the age of the market data; read that for freshness rather than `timestamp`, which is when the response was served.
249
252
 
253
+ Short interest and short volume come back newest first, and an unknown ticker returns an empty `dataPoints` array rather than an error. Every identifier in `getGraph` is a slug, the same handle the metric and document endpoints take. An ETF symbol is refused by `getQuote` with an `APIError` whose `code` is `"ticker_is_etf"`; use `etfs.quote()` for funds.
254
+
250
255
  ### Documents & news
251
256
 
252
257
  ```typescript
@@ -459,6 +464,7 @@ Composition data is public; the holdings-weighted aggregate views follow the sam
459
464
 
460
465
  ```typescript
461
466
  client.etfs.list() // Every ETF tracked
467
+ client.etfs.quote("SPY") // Price, ranges, AUM, expense ratio, NAV
462
468
  client.etfs.holdings("QQQ") // Full composition + freshness metadata
463
469
  client.etfs.analystAggregate("QQQ") // Holdings-weighted analyst consensus
464
470
  client.etfs.insiderAggregate("ARKK", { lookbackDays: 90 }) // Holdings-weighted Form 4 net flow
package/dist/cli.cjs CHANGED
@@ -502,10 +502,22 @@ async function verifyTickerOnEmpty(api, ticker) {
502
502
  await api.stocks.getQuote(ticker);
503
503
  return void 0;
504
504
  } catch (error) {
505
+ if (isEtfRedirect(error)) return void 0;
505
506
  if (error instanceof NotFoundError) throw new UnknownTickerError(ticker);
506
507
  return `could not verify ${ticker}, so the empty result is unconfirmed`;
507
508
  }
508
509
  }
510
+ function isEtfRedirect(error) {
511
+ return error instanceof APIError && error.code === "ticker_is_etf";
512
+ }
513
+ async function quoteAnyTicker(api, ticker) {
514
+ try {
515
+ return { kind: "stock", quote: await api.stocks.getQuote(ticker) };
516
+ } catch (error) {
517
+ if (!isEtfRedirect(error)) throw error;
518
+ return { kind: "etf", quote: await api.etfs.quote(ticker) };
519
+ }
520
+ }
509
521
  var EMPTY_VERIFY_NOTE = "An empty result verifies the ticker before reporting no data, so a typo exits 4 rather";
510
522
  var EMPTY_VERIFY_NOTE_2 = "than looking like a company with nothing to report.";
511
523
 
@@ -518,11 +530,13 @@ function actionTone(actionType) {
518
530
  return void 0;
519
531
  }
520
532
  function primaryAnalyst(row) {
521
- const named = row.latestNote?.analyst;
522
- const match = (named ? row.analysts.find((analyst) => analyst.name === named) : void 0) ?? row.analysts[0];
523
- const name = match?.name ?? named ?? "";
533
+ const named = row.latestNote?.analyst ?? null;
534
+ const match = (named ? row.analysts.find(
535
+ (analyst) => named.slug !== null && analyst.slug === named.slug || analyst.name === named.name
536
+ ) : void 0) ?? row.analysts[0];
537
+ const name = match?.name ?? named?.name ?? "";
524
538
  const extra = row.analysts.length > 1 ? ` +${row.analysts.length - 1}` : "";
525
- return { name: name ? `${name}${extra}` : "", slug: match?.slug ?? "" };
539
+ return { name: name ? `${name}${extra}` : "", slug: match?.slug ?? named?.slug ?? "" };
526
540
  }
527
541
  var analystsCommand = {
528
542
  name: "analysts",
@@ -1331,7 +1345,7 @@ var flowsCommand = {
1331
1345
  };
1332
1346
 
1333
1347
  // src/version.ts
1334
- var VERSION = "0.61.0";
1348
+ var VERSION = "0.62.0";
1335
1349
 
1336
1350
  // src/resources/analyst.ts
1337
1351
  var Analyst = class {
@@ -1726,6 +1740,19 @@ var Etfs = class {
1726
1740
  async list() {
1727
1741
  return this.client.get("/api/v1/etfs");
1728
1742
  }
1743
+ /**
1744
+ * Get the quote snapshot for an ETF: price, day and 52-week range, volume, dividend
1745
+ * yield, AUM, expense ratio, NAV and inception date.
1746
+ *
1747
+ * Fails with a 400 `APIError` whose `code` is `"ticker_is_not_etf"` when the symbol is a
1748
+ * stock (use `stocks.getQuote()` for those), and a `NotFoundError` when there is no
1749
+ * price data for the fund.
1750
+ */
1751
+ async quote(ticker) {
1752
+ return this.client.get(
1753
+ `/api/v1/etfs/${encodeURIComponent(ticker.toUpperCase())}/quote`
1754
+ );
1755
+ }
1729
1756
  /**
1730
1757
  * Get the full holdings composition for an ETF, including per-holding weights
1731
1758
  * and freshness metadata. Returns 404 for unknown ETFs or commodity-only funds.
@@ -2320,6 +2347,21 @@ var Stocks = class {
2320
2347
  async getEntities(ticker) {
2321
2348
  return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/entities`);
2322
2349
  }
2350
+ /**
2351
+ * Get the company knowledge graph around a stock: the people, products, peers and other
2352
+ * entities linked to the company, and the typed relationships between them.
2353
+ *
2354
+ * Every identifier in the response is a slug, the same handle the metric and document
2355
+ * endpoints take, so a node can be passed straight to them. `depth` is 1 (default) or 2,
2356
+ * `cap` limits the node count (1 to 200, default 75); out-of-range values are rejected
2357
+ * with a 400. A ticker with no graph returns a `NotFoundError`.
2358
+ */
2359
+ async getGraph(ticker, options) {
2360
+ return this.client.get(
2361
+ `/api/v1/stocks/${encodeURIComponent(ticker.toUpperCase())}/graph`,
2362
+ options
2363
+ );
2364
+ }
2323
2365
  /**
2324
2366
  * Get AI-generated stock analysis report. Requires PRO tier.
2325
2367
  *
@@ -2382,17 +2424,23 @@ var Stocks = class {
2382
2424
  async getHistoricalRevenue(ticker) {
2383
2425
  return this.client.get("/api/v1/stocks/fundamentals/historical/revenue", { ticker });
2384
2426
  }
2385
- /** Get short interest metrics (FINRA). */
2386
- async getShortInterest(ticker) {
2387
- return this.client.get("/api/v1/stocks/short-interest", { ticker });
2427
+ /**
2428
+ * Get short interest history (FINRA), newest first: shares short, days to cover and
2429
+ * average daily volume per settlement date. `limit` defaults to 24 settlement dates.
2430
+ */
2431
+ async getShortInterest(ticker, options) {
2432
+ return this.client.get("/api/v1/stocks/short-interest", { ticker, ...options });
2388
2433
  }
2389
2434
  /** Get float information. */
2390
2435
  async getFloat(ticker) {
2391
2436
  return this.client.get("/api/v1/stocks/float", { ticker });
2392
2437
  }
2393
- /** Get short volume trading data. */
2394
- async getShortVolume(ticker) {
2395
- return this.client.get("/api/v1/stocks/short-volume", { ticker });
2438
+ /**
2439
+ * Get daily short volume, newest first: shares sold short against total volume on the
2440
+ * reporting venues. `limit` defaults to 90 trading days.
2441
+ */
2442
+ async getShortVolume(ticker, options) {
2443
+ return this.client.get("/api/v1/stocks/short-volume", { ticker, ...options });
2396
2444
  }
2397
2445
  /**
2398
2446
  * Get company-specific KPI time-series for a ticker. Returns curated GAAP and
@@ -3641,6 +3689,37 @@ var optionsCommand = {
3641
3689
  };
3642
3690
 
3643
3691
  // src/cli/commands/quote.ts
3692
+ function valuation(row) {
3693
+ if (row.kind === "etf") {
3694
+ const etf = row.quote;
3695
+ return {
3696
+ kind: "facts",
3697
+ items: fields(
3698
+ field("AUM", humanize(etf.aum)),
3699
+ // Both ratios, like `dividendYield` on the stock quote.
3700
+ field("Expense ratio", ratioPercent(etf.expenseRatio)),
3701
+ field("NAV", fixed(etf.nav)),
3702
+ field("Div yield", ratioPercent(etf.dividendYield)),
3703
+ field("52w", `${fixed(etf.week52Low)} to ${fixed(etf.week52High)}`)
3704
+ )
3705
+ };
3706
+ }
3707
+ const quote = row.quote;
3708
+ return {
3709
+ kind: "facts",
3710
+ items: fields(
3711
+ field("Mkt cap", humanize(quote.marketCap)),
3712
+ field("P/E", fixed(quote.peRatio)),
3713
+ field("EPS TTM", fixed(quote.epsTTM)),
3714
+ // A ratio, unlike `changePercent` and the other percentages in the same payload.
3715
+ field("Div yield", ratioPercent(quote.dividendYield)),
3716
+ field(
3717
+ "52w",
3718
+ `${fixed(quote.week52Low)} to ${fixed(quote.week52High)}`
3719
+ )
3720
+ )
3721
+ };
3722
+ }
3644
3723
  function single(row, full) {
3645
3724
  const { quote, profile } = row;
3646
3725
  const tone = direction(quote.changePercent);
@@ -3668,20 +3747,7 @@ function single(row, full) {
3668
3747
  field("Prev close", fixed(quote.previousClose))
3669
3748
  )
3670
3749
  },
3671
- {
3672
- kind: "facts",
3673
- items: fields(
3674
- field("Mkt cap", humanize(quote.marketCap)),
3675
- field("P/E", fixed(quote.peRatio)),
3676
- field("EPS TTM", fixed(quote.epsTTM)),
3677
- // A ratio, unlike `changePercent` and the other percentages in the same payload.
3678
- field("Div yield", ratioPercent(quote.dividendYield)),
3679
- field(
3680
- "52w",
3681
- `${fixed(quote.week52Low)} to ${fixed(quote.week52High)}`
3682
- )
3683
- )
3684
- }
3750
+ valuation(row)
3685
3751
  ];
3686
3752
  if (quote.extendedHours) {
3687
3753
  const ext = quote.extendedHours;
@@ -3700,25 +3766,33 @@ function single(row, full) {
3700
3766
  )
3701
3767
  });
3702
3768
  }
3703
- if (full) {
3769
+ if (full && row.kind === "etf") {
3770
+ blocks.push({
3771
+ kind: "kv",
3772
+ items: fields(
3773
+ row.quote.inceptionDate ? field("inception", row.quote.inceptionDate) : void 0
3774
+ )
3775
+ });
3776
+ } else if (full && row.kind === "stock") {
3704
3777
  blocks.push({
3705
3778
  kind: "kv",
3706
3779
  items: fields(
3707
- field("200d average", fixed(quote.movingAverage200Day)),
3708
- quote.reportedCurrency ? field("reported currency", quote.reportedCurrency) : void 0,
3780
+ field("200d average", fixed(row.quote.movingAverage200Day)),
3781
+ row.quote.reportedCurrency ? field("reported currency", row.quote.reportedCurrency) : void 0,
3709
3782
  profile?.sector ? field("sector", String(profile.sector)) : void 0,
3710
3783
  profile?.industry ? field("industry", String(profile.industry)) : void 0,
3711
3784
  profile?.ceo ? field("ceo", String(profile.ceo)) : void 0
3712
3785
  )
3713
3786
  });
3714
3787
  }
3715
- if (quote.listingStatus === "DELISTED") {
3788
+ const listingStatus = row.kind === "stock" ? row.quote.listingStatus : void 0;
3789
+ if (row.kind === "stock" && listingStatus === "DELISTED") {
3716
3790
  blocks.push({
3717
3791
  kind: "text",
3718
- text: `Delisted on ${quote.delistedDate ?? "an unrecorded date"}. Every figure above is frozen at the last trade, not a live market move.`,
3792
+ text: `Delisted on ${row.quote.delistedDate ?? "an unrecorded date"}. Every figure above is frozen at the last trade, not a live market move.`,
3719
3793
  tone: "down"
3720
3794
  });
3721
- } else if (quote.listingStatus === "PENDING_DELISTING") {
3795
+ } else if (listingStatus === "PENDING_DELISTING") {
3722
3796
  blocks.push({
3723
3797
  kind: "text",
3724
3798
  text: "A merger or take-private is scheduled. The stock still trades, so these figures are current.",
@@ -3745,7 +3819,8 @@ function table(rows, full) {
3745
3819
  cell(signed(row.quote.change), tone),
3746
3820
  cell(signedPercent(row.quote.changePercent), tone),
3747
3821
  cell(humanize(row.quote.volume, 1)),
3748
- cell(humanize(row.quote.marketCap))
3822
+ // An ETF has no market cap; its AUM is a different number and stays out of this column.
3823
+ cell(humanize(row.kind === "stock" ? row.quote.marketCap : void 0))
3749
3824
  ];
3750
3825
  return full ? [cell(row.ticker), cell(row.profile?.name ?? ""), ...tail] : [cell(row.ticker), ...tail];
3751
3826
  })
@@ -3767,7 +3842,9 @@ var quoteCommand = {
3767
3842
  "Use canonical symbols, for example GOOGL rather than GOOG and BRK.B rather than BRK-B.",
3768
3843
  "One request per ticker. The company name costs a second request, so it is fetched only",
3769
3844
  "for the terminal layout: piped and --json output carry the quote alone.",
3770
- "JSON is the exact quote response for one ticker, and an object keyed by ticker for more."
3845
+ "JSON is the exact quote response for one ticker, and an object keyed by ticker for more.",
3846
+ "An ETF symbol answers with the ETF quote: AUM, expense ratio and NAV in place of market",
3847
+ "cap, P/E and EPS. That costs one extra request per ETF symbol."
3771
3848
  ],
3772
3849
  flags: {},
3773
3850
  async run({ args, client, full, mode }) {
@@ -3779,12 +3856,12 @@ var quoteCommand = {
3779
3856
  const wantName = mode === "pretty";
3780
3857
  const rows = await Promise.all(
3781
3858
  tickers.map(async (ticker) => {
3782
- const [quote, profile] = await Promise.all([
3783
- api.stocks.getQuote(ticker),
3859
+ const [answer, profile] = await Promise.all([
3860
+ quoteAnyTicker(api, ticker),
3784
3861
  // A name is a nicety, not the answer. A ticker with no profile still gets a quote.
3785
3862
  wantName ? api.stocks.getProfile(ticker).catch(() => null) : Promise.resolve(null)
3786
3863
  ]);
3787
- return { ticker, quote, profile };
3864
+ return { ...answer, ticker, profile };
3788
3865
  })
3789
3866
  );
3790
3867
  if (rows.length === 1) {
package/dist/index.cjs CHANGED
@@ -475,6 +475,19 @@ var Etfs = class {
475
475
  async list() {
476
476
  return this.client.get("/api/v1/etfs");
477
477
  }
478
+ /**
479
+ * Get the quote snapshot for an ETF: price, day and 52-week range, volume, dividend
480
+ * yield, AUM, expense ratio, NAV and inception date.
481
+ *
482
+ * Fails with a 400 `APIError` whose `code` is `"ticker_is_not_etf"` when the symbol is a
483
+ * stock (use `stocks.getQuote()` for those), and a `NotFoundError` when there is no
484
+ * price data for the fund.
485
+ */
486
+ async quote(ticker) {
487
+ return this.client.get(
488
+ `/api/v1/etfs/${encodeURIComponent(ticker.toUpperCase())}/quote`
489
+ );
490
+ }
478
491
  /**
479
492
  * Get the full holdings composition for an ETF, including per-holding weights
480
493
  * and freshness metadata. Returns 404 for unknown ETFs or commodity-only funds.
@@ -1069,6 +1082,21 @@ var Stocks = class {
1069
1082
  async getEntities(ticker) {
1070
1083
  return this.client.get(`/api/v1/stocks/${encodeURIComponent(ticker)}/entities`);
1071
1084
  }
1085
+ /**
1086
+ * Get the company knowledge graph around a stock: the people, products, peers and other
1087
+ * entities linked to the company, and the typed relationships between them.
1088
+ *
1089
+ * Every identifier in the response is a slug, the same handle the metric and document
1090
+ * endpoints take, so a node can be passed straight to them. `depth` is 1 (default) or 2,
1091
+ * `cap` limits the node count (1 to 200, default 75); out-of-range values are rejected
1092
+ * with a 400. A ticker with no graph returns a `NotFoundError`.
1093
+ */
1094
+ async getGraph(ticker, options) {
1095
+ return this.client.get(
1096
+ `/api/v1/stocks/${encodeURIComponent(ticker.toUpperCase())}/graph`,
1097
+ options
1098
+ );
1099
+ }
1072
1100
  /**
1073
1101
  * Get AI-generated stock analysis report. Requires PRO tier.
1074
1102
  *
@@ -1131,17 +1159,23 @@ var Stocks = class {
1131
1159
  async getHistoricalRevenue(ticker) {
1132
1160
  return this.client.get("/api/v1/stocks/fundamentals/historical/revenue", { ticker });
1133
1161
  }
1134
- /** Get short interest metrics (FINRA). */
1135
- async getShortInterest(ticker) {
1136
- return this.client.get("/api/v1/stocks/short-interest", { ticker });
1162
+ /**
1163
+ * Get short interest history (FINRA), newest first: shares short, days to cover and
1164
+ * average daily volume per settlement date. `limit` defaults to 24 settlement dates.
1165
+ */
1166
+ async getShortInterest(ticker, options) {
1167
+ return this.client.get("/api/v1/stocks/short-interest", { ticker, ...options });
1137
1168
  }
1138
1169
  /** Get float information. */
1139
1170
  async getFloat(ticker) {
1140
1171
  return this.client.get("/api/v1/stocks/float", { ticker });
1141
1172
  }
1142
- /** Get short volume trading data. */
1143
- async getShortVolume(ticker) {
1144
- return this.client.get("/api/v1/stocks/short-volume", { ticker });
1173
+ /**
1174
+ * Get daily short volume, newest first: shares sold short against total volume on the
1175
+ * reporting venues. `limit` defaults to 90 trading days.
1176
+ */
1177
+ async getShortVolume(ticker, options) {
1178
+ return this.client.get("/api/v1/stocks/short-volume", { ticker, ...options });
1145
1179
  }
1146
1180
  /**
1147
1181
  * Get company-specific KPI time-series for a ticker. Returns curated GAAP and
@@ -1353,7 +1387,7 @@ var Trackers = class {
1353
1387
  };
1354
1388
 
1355
1389
  // src/version.ts
1356
- var VERSION = "0.61.0";
1390
+ var VERSION = "0.62.0";
1357
1391
 
1358
1392
  // src/client.ts
1359
1393
  var DEFAULT_BASE_URL = "https://app.sentisense.ai";