sentisense 0.42.0 → 0.44.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 |
@@ -88,6 +88,25 @@ something supplementary does not come back, such as the Score history behind a s
88
88
  command still prints its answer and exits 0 with a `note:` line on stderr, so stdout stays
89
89
  clean for a pipe and the gap is never silent.
90
90
 
91
+ ### Saying who is calling
92
+
93
+ If you set `SENTISENSE_AGENT_NAME` (what your agent is called) and `SENTISENSE_SKILL` (the
94
+ slug of the skill driving it), requests carry that identity, so usage can be understood and
95
+ the tools improved. Both are optional, never required, and nothing is inferred when they are
96
+ absent.
97
+
98
+ ```bash
99
+ export SENTISENSE_AGENT_NAME=research-desk
100
+ export SENTISENSE_SKILL=stock-analysis
101
+ npx -y sentisense@latest quote NVDA
102
+ # User-Agent: sentisense-node/0.44.0 sentisense-cli/0.44.0 (stock-analysis; agent/research-desk)
103
+ ```
104
+
105
+ Either can also be a flag (`--agent`, `--skill`) or a stored setting
106
+ (`sentisense auth --agent research-desk --skill stock-analysis`), resolved flag first, then
107
+ environment, then config. Values are reduced to letters, digits, dot, underscore and hyphen,
108
+ and capped at 32 characters, so nothing you set can reshape the header.
109
+
91
110
  ### Exit codes
92
111
 
93
112
  Failures print two lines to stderr, what went wrong and what to do about it, and exit with a
package/dist/cli.cjs CHANGED
@@ -55,8 +55,8 @@ var EXIT = {
55
55
  };
56
56
  var EXIT_TABLE = [
57
57
  [EXIT.OK, "success"],
58
- [EXIT.ERROR, "API error or unexpected failure"],
59
- [EXIT.USAGE, "bad usage: unknown command, flag, or missing argument"],
58
+ [EXIT.ERROR, "API error, including a request the API rejected as invalid"],
59
+ [EXIT.USAGE, "bad usage, caught before any request was sent"],
60
60
  [EXIT.AUTH, "missing or rejected API key"],
61
61
  [EXIT.NOT_FOUND, "no data for that symbol or identifier"],
62
62
  [EXIT.RATE_LIMIT, "rate limited"],
@@ -70,20 +70,27 @@ 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.");
76
83
  this.name = "MissingKeyError";
77
84
  }
78
85
  };
79
- function describeError(error, debug) {
80
- const report = classify(error);
86
+ function describeError(error, debug, command) {
87
+ const report = classify(error, command);
81
88
  if (debug && error instanceof Error && error.stack) {
82
89
  report.lines.push(error.stack);
83
90
  }
84
91
  return report;
85
92
  }
86
- function classify(error) {
93
+ function classify(error, command) {
87
94
  if (error instanceof MissingKeyError) {
88
95
  return {
89
96
  exitCode: EXIT.AUTH,
@@ -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,
@@ -140,6 +156,16 @@ function classify(error) {
140
156
  ]
141
157
  };
142
158
  }
159
+ if (error.status !== void 0 && error.status >= 400 && error.status < 500) {
160
+ const help = command ? `sentisense help ${command}` : "sentisense --help";
161
+ return {
162
+ exitCode: EXIT.ERROR,
163
+ lines: [
164
+ `error: the API rejected the request (${error.status}): ${error.message}`,
165
+ `next: check the flags and values you passed. Run "${help}" for the accepted fields and examples.`
166
+ ]
167
+ };
168
+ }
143
169
  return {
144
170
  exitCode: EXIT.ERROR,
145
171
  lines: [
@@ -158,6 +184,48 @@ function classify(error) {
158
184
  };
159
185
  }
160
186
 
187
+ // src/cli/ticker.ts
188
+ function rejectSurplus(args, name, allowed) {
189
+ if (args.positionals.length <= allowed) return;
190
+ throw new CliUsageError(
191
+ `${name} takes one ticker, and got ${args.positionals.length}.`,
192
+ "run it once per ticker. Only quote accepts more than one."
193
+ );
194
+ }
195
+ function oneTicker(args, name) {
196
+ rejectSurplus(args, name, 1);
197
+ const ticker = args.positionals[0];
198
+ if (!ticker) {
199
+ throw new CliUsageError(
200
+ `${name} needs a ticker.`,
201
+ `for example: sentisense ${name} NVDA`
202
+ );
203
+ }
204
+ return ticker.toUpperCase();
205
+ }
206
+ function optionalTicker(args, name) {
207
+ rejectSurplus(args, name, 1);
208
+ return args.positionals[0]?.toUpperCase();
209
+ }
210
+ function rejectPositionals(args, name) {
211
+ if (args.positionals.length === 0) return;
212
+ throw new CliUsageError(
213
+ `${name} takes no ticker, and got ${args.positionals.length}.`,
214
+ `run "sentisense help ${name}" for what it accepts.`
215
+ );
216
+ }
217
+ async function verifyTickerOnEmpty(api, ticker) {
218
+ try {
219
+ await api.stocks.getQuote(ticker);
220
+ return void 0;
221
+ } catch (error) {
222
+ if (error instanceof NotFoundError) throw new UnknownTickerError(ticker);
223
+ return `could not verify ${ticker}, so the empty result is unconfirmed`;
224
+ }
225
+ }
226
+ var EMPTY_VERIFY_NOTE = "An empty result verifies the ticker before reporting no data, so a typo exits 4 rather";
227
+ var EMPTY_VERIFY_NOTE_2 = "than looking like a company with nothing to report.";
228
+
161
229
  // src/cli/render/doc.ts
162
230
  function cell(text, tone) {
163
231
  return tone ? { text, tone } : { text };
@@ -269,13 +337,7 @@ var analystsCommand = {
269
337
  days: { type: "number", placeholder: "N", describe: "Days of rating history (default 90)" }
270
338
  },
271
339
  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
- }
340
+ const ticker = oneTicker(args, "analysts");
279
341
  const lookbackDays = typeof args.flags.days === "number" ? args.flags.days : void 0;
280
342
  const api = client();
281
343
  const notes = [];
@@ -383,6 +445,7 @@ function readConfig(dir) {
383
445
  const config = {};
384
446
  if (typeof raw.apiKey === "string") config.apiKey = raw.apiKey;
385
447
  if (typeof raw.agentName === "string") config.agentName = raw.agentName;
448
+ if (typeof raw.skill === "string") config.skill = raw.skill;
386
449
  if (typeof raw.baseUrl === "string") config.baseUrl = raw.baseUrl;
387
450
  return config;
388
451
  } catch {
@@ -412,10 +475,11 @@ function maskKey(key) {
412
475
  var authCommand = {
413
476
  name: "auth",
414
477
  summary: "Store an API key, show what is configured, or remove it",
415
- usage: "sentisense auth [<key>] [--agent <name>] [--remove]",
478
+ usage: "sentisense auth [<key>] [--agent <name>] [--skill <slug>] [--remove]",
416
479
  examples: [
417
480
  "sentisense auth $SENTISENSE_API_KEY",
418
481
  "sentisense auth --agent research-desk",
482
+ "sentisense auth --skill stock-analysis --agent research-desk",
419
483
  "sentisense auth",
420
484
  "sentisense auth --remove"
421
485
  ],
@@ -423,8 +487,10 @@ var authCommand = {
423
487
  "Settings live in config.json under $SENTISENSE_CONFIG_DIR, $XDG_CONFIG_HOME/sentisense,",
424
488
  "or ~/.config/sentisense, written owner-readable only (0600).",
425
489
  "The key is never printed back in full, and never has to be pasted into a command again.",
426
- "An agent name is optional. When set, it rides along in the User-Agent so your own",
427
- "traffic is easy to tell apart from everything else calling the API."
490
+ "Two optional labels say who is calling: --agent is what your agent calls itself, and",
491
+ "--skill is the slug of the skill driving it. When set, they ride along in the",
492
+ "User-Agent, so usage can be understood and the tools improved. Both are voluntary and",
493
+ "nothing needs them to work."
428
494
  ],
429
495
  flags: {
430
496
  remove: { type: "boolean", describe: "Delete the stored settings" }
@@ -443,13 +509,15 @@ var authCommand = {
443
509
  }
444
510
  const key = args.positionals[0];
445
511
  const agent = typeof args.flags.agent === "string" ? args.flags.agent : void 0;
512
+ const skill = typeof args.flags.skill === "string" ? args.flags.skill : void 0;
446
513
  const baseUrl = typeof args.flags["base-url"] === "string" ? args.flags["base-url"] : void 0;
447
- if (key || agent || baseUrl) {
514
+ if (key || agent || skill || baseUrl) {
448
515
  const stored2 = readConfig(dir);
449
516
  const next = {
450
517
  ...stored2,
451
518
  ...key ? { apiKey: key } : {},
452
519
  ...agent ? { agentName: agent } : {},
520
+ ...skill ? { skill } : {},
453
521
  ...baseUrl ? { baseUrl } : {}
454
522
  };
455
523
  writeConfig(dir, next);
@@ -458,6 +526,7 @@ var authCommand = {
458
526
  path,
459
527
  apiKey: next.apiKey ? maskKey(next.apiKey) : null,
460
528
  agentName: next.agentName ?? null,
529
+ skill: next.skill ?? null,
461
530
  baseUrl: next.baseUrl ?? null
462
531
  },
463
532
  doc: doc(
@@ -467,6 +536,7 @@ var authCommand = {
467
536
  items: fields(
468
537
  next.apiKey ? field("api key", maskKey(next.apiKey)) : void 0,
469
538
  next.agentName ? field("agent", next.agentName) : void 0,
539
+ next.skill ? field("skill", next.skill) : void 0,
470
540
  next.baseUrl ? field("base url", next.baseUrl) : void 0
471
541
  )
472
542
  }
@@ -482,6 +552,7 @@ var authCommand = {
482
552
  apiKey: resolved ? maskKey(resolved) : null,
483
553
  apiKeySource: context.apiKeySource,
484
554
  agentName: context.agentName ?? null,
555
+ skill: context.skill ?? null,
485
556
  baseUrl: context.baseUrl ?? null
486
557
  },
487
558
  doc: doc(
@@ -492,6 +563,7 @@ var authCommand = {
492
563
  field("source", resolved ? context.apiKeySource : "none"),
493
564
  field("config", path),
494
565
  context.agentName ? field("agent", context.agentName) : void 0,
566
+ context.skill ? field("skill", context.skill) : void 0,
495
567
  context.baseUrl ? field("base url", context.baseUrl) : void 0
496
568
  )
497
569
  },
@@ -519,7 +591,9 @@ var congressCommand = {
519
591
  "Disclosures are filed after the fact, so the gap between the trade date and the",
520
592
  "disclosure date is part of the picture. The delay column carries it in days.",
521
593
  "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."
594
+ "A free key sees a short preview of either feed.",
595
+ EMPTY_VERIFY_NOTE,
596
+ EMPTY_VERIFY_NOTE_2
523
597
  ],
524
598
  flags: {
525
599
  days: { type: "number", placeholder: "N", describe: "Look-back window, 1 to 365 (default 90)" },
@@ -527,7 +601,8 @@ var congressCommand = {
527
601
  },
528
602
  async run({ args, client, full }) {
529
603
  const api = client();
530
- const ticker = args.positionals[0]?.toUpperCase();
604
+ const ticker = optionalTicker(args, "congress");
605
+ const notes = [];
531
606
  const lookbackDays = typeof args.flags.days === "number" ? args.flags.days : void 0;
532
607
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : void 0;
533
608
  let envelope;
@@ -546,6 +621,10 @@ var congressCommand = {
546
621
  );
547
622
  }
548
623
  const trades = envelope.data ?? [];
624
+ if (ticker && trades.length === 0) {
625
+ const note = await verifyTickerOnEmpty(api, ticker);
626
+ if (note) notes.push(note);
627
+ }
549
628
  const shown = full ? trades : trades.slice(0, 20);
550
629
  const blocks = [
551
630
  {
@@ -594,7 +673,7 @@ var congressCommand = {
594
673
  tone: "dim"
595
674
  });
596
675
  }
597
- return { json: envelope, doc: doc(...blocks) };
676
+ return { json: envelope, doc: doc(...blocks), notes };
598
677
  }
599
678
  };
600
679
 
@@ -612,14 +691,18 @@ var earningsCommand = {
612
691
  examples: [
613
692
  "sentisense earnings",
614
693
  "sentisense earnings --week next",
615
- "sentisense earnings NVDA",
616
- "sentisense earnings NVDA --limit 4 --full"
694
+ "sentisense earnings AAPL",
695
+ "sentisense earnings AAPL --limit 4 --full"
617
696
  ],
618
697
  notes: [
619
698
  "With no ticker this is the forward calendar: who reports, when, and the consensus EPS.",
620
699
  "With a ticker it is the backward-looking analysis: one entry per reported quarter with",
621
700
  "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."
701
+ "A free key sees the current week of the calendar and the latest quarter of the analysis.",
702
+ "Coverage of the per-ticker analysis is not the whole market, so a tracked company can",
703
+ "have no stored quarter yet.",
704
+ EMPTY_VERIFY_NOTE,
705
+ EMPTY_VERIFY_NOTE_2
623
706
  ],
624
707
  flags: {
625
708
  week: { type: "string", placeholder: "this|next", describe: "Calendar window shorthand" },
@@ -630,7 +713,8 @@ var earningsCommand = {
630
713
  },
631
714
  async run({ args, client, full }) {
632
715
  const api = client();
633
- const ticker = args.positionals[0]?.toUpperCase();
716
+ const ticker = optionalTicker(args, "earnings");
717
+ const notes = [];
634
718
  if (ticker) {
635
719
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : void 0;
636
720
  const envelope2 = await api.earnings.getSummaries(
@@ -638,6 +722,10 @@ var earningsCommand = {
638
722
  limit === void 0 ? void 0 : { limit }
639
723
  );
640
724
  const quarters = envelope2.data ?? [];
725
+ if (quarters.length === 0) {
726
+ const note = await verifyTickerOnEmpty(api, ticker);
727
+ if (note) notes.push(note);
728
+ }
641
729
  const blocks2 = [
642
730
  {
643
731
  kind: "head",
@@ -699,7 +787,7 @@ var earningsCommand = {
699
787
  tone: "dim"
700
788
  });
701
789
  }
702
- return { json: envelope2, doc: doc(...blocks2) };
790
+ return { json: envelope2, doc: doc(...blocks2), notes };
703
791
  }
704
792
  const week = typeof args.flags.week === "string" ? args.flags.week : void 0;
705
793
  if (week && week !== "this" && week !== "next") {
@@ -791,7 +879,9 @@ var flowsCommand = {
791
879
  "market. With a ticker it is that stock's institutional ownership and the quarter's",
792
880
  "notable position changes, since flows are only published market-wide.",
793
881
  "13F filings land up to 45 days after quarter end, so a still-open quarter shows only",
794
- "early filers and says so."
882
+ "early filers and says so, and a ticker reads the newest quarter that has closed.",
883
+ EMPTY_VERIFY_NOTE,
884
+ EMPTY_VERIFY_NOTE_2
795
885
  ],
796
886
  flags: {
797
887
  limit: { type: "number", placeholder: "N", describe: `Rows per side (default ${DEFAULT_ROWS})` },
@@ -799,7 +889,8 @@ var flowsCommand = {
799
889
  },
800
890
  async run({ args, client, full }) {
801
891
  const api = client();
802
- const ticker = args.positionals[0]?.toUpperCase();
892
+ const ticker = optionalTicker(args, "flows");
893
+ const notes = [];
803
894
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : DEFAULT_ROWS;
804
895
  const quarter = typeof args.flags.quarter === "string" ? args.flags.quarter : void 0;
805
896
  if (ticker) {
@@ -820,6 +911,10 @@ var flowsCommand = {
820
911
  });
821
912
  const data2 = envelope2.data;
822
913
  const holders = data2?.holders ?? [];
914
+ if (holders.length === 0) {
915
+ const note = await verifyTickerOnEmpty(api, ticker);
916
+ if (note) notes.push(note);
917
+ }
823
918
  const blocks2 = [
824
919
  {
825
920
  kind: "head",
@@ -878,7 +973,7 @@ var flowsCommand = {
878
973
  tone: "dim"
879
974
  });
880
975
  }
881
- return { json: envelope2, doc: doc(...blocks2) };
976
+ return { json: envelope2, doc: doc(...blocks2), notes };
882
977
  }
883
978
  const envelope = await api.institutional.getFlows(quarter, { limit });
884
979
  const data = envelope.data;
@@ -933,7 +1028,7 @@ var flowsCommand = {
933
1028
  };
934
1029
 
935
1030
  // src/version.ts
936
- var VERSION = "0.42.0";
1031
+ var VERSION = "0.44.0";
937
1032
 
938
1033
  // src/resources/analyst.ts
939
1034
  var Analyst = class {
@@ -2115,7 +2210,12 @@ var GLOBAL_FLAGS = {
2115
2210
  agent: {
2116
2211
  type: "string",
2117
2212
  placeholder: "name",
2118
- describe: "Label this call in the User-Agent"
2213
+ describe: "Name your agent in the User-Agent, if you want to"
2214
+ },
2215
+ skill: {
2216
+ type: "string",
2217
+ placeholder: "slug",
2218
+ describe: "Name the skill driving this call, if you want to"
2119
2219
  }
2120
2220
  };
2121
2221
  var SHORT_FLAGS = {
@@ -2250,6 +2350,7 @@ function resolveContext({ flags, env, configDir }) {
2250
2350
  const key = pick(flagString(flags, "api-key"), env.SENTISENSE_API_KEY, stored.apiKey);
2251
2351
  const base = pick(flagString(flags, "base-url"), env.SENTISENSE_BASE_URL, stored.baseUrl);
2252
2352
  const agent = pick(flagString(flags, "agent"), env.SENTISENSE_AGENT_NAME, stored.agentName);
2353
+ const skill = pick(flagString(flags, "skill"), env.SENTISENSE_SKILL, stored.skill);
2253
2354
  return {
2254
2355
  configDir: dir,
2255
2356
  apiKey: key.value,
@@ -2257,21 +2358,27 @@ function resolveContext({ flags, env, configDir }) {
2257
2358
  baseUrl: base.value,
2258
2359
  baseUrlSource: base.source,
2259
2360
  agentName: agent.value,
2260
- agentSource: agent.source
2361
+ agentSource: agent.source,
2362
+ skill: skill.value,
2363
+ skillSource: skill.source
2261
2364
  };
2262
2365
  }
2263
2366
  var DEFAULT_BASE_URL2 = "https://app.sentisense.ai";
2264
2367
  function effectiveBaseUrl(context) {
2265
2368
  return context.baseUrl ?? DEFAULT_BASE_URL2;
2266
2369
  }
2267
- function sanitizeAgent(name) {
2268
- return name.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "");
2370
+ var MAX_IDENTITY = 32;
2371
+ function sanitizeIdentity(value) {
2372
+ return value.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "").replace(/-{2,}/g, "-").slice(0, MAX_IDENTITY).replace(/^-+|-+$/g, "");
2269
2373
  }
2270
2374
  function userAgentSuffix(context) {
2271
- const parts = [`sentisense-cli/${VERSION}`];
2272
- const agent = context.agentName ? sanitizeAgent(context.agentName) : "";
2273
- if (agent) parts.push(`agent/${agent}`);
2274
- return parts.join(" ");
2375
+ const product = `sentisense-cli/${VERSION}`;
2376
+ const slug = sanitizeIdentity(context.skill ?? "");
2377
+ const agent = sanitizeIdentity(context.agentName ?? "");
2378
+ const comment = [];
2379
+ if (slug) comment.push(slug);
2380
+ if (agent) comment.push(`agent/${agent}`);
2381
+ return comment.length === 0 ? product : `${product} (${comment.join("; ")})`;
2275
2382
  }
2276
2383
  function createClient(context, options = {}) {
2277
2384
  if (!options.anonymous && !context.apiKey) throw new MissingKeyError();
@@ -2312,7 +2419,8 @@ var healthCommand = {
2312
2419
  "Exits 3 when the key is missing or rejected, 6 when the host cannot be reached."
2313
2420
  ],
2314
2421
  flags: {},
2315
- async run({ context }) {
2422
+ async run({ args, context }) {
2423
+ rejectPositionals(args, "health");
2316
2424
  const baseUrl = effectiveBaseUrl(context);
2317
2425
  const anonymous = createClient(context, { anonymous: true });
2318
2426
  const reach = await probe(() => anonymous.stocks.getMarketStatus());
@@ -2382,25 +2490,27 @@ var insidersCommand = {
2382
2490
  "Rows are individual filed transactions, newest first, not a net total.",
2383
2491
  "The plan column says whether the trade was under a confirmed pre-arranged 10b5-1 plan,",
2384
2492
  "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."
2493
+ "A free key sees the top few transactions; a PRO key sees the window you asked for.",
2494
+ EMPTY_VERIFY_NOTE,
2495
+ EMPTY_VERIFY_NOTE_2
2386
2496
  ],
2387
2497
  flags: {
2388
2498
  days: { type: "number", placeholder: "N", describe: "Look-back window, 1 to 365 (default 90)" }
2389
2499
  },
2390
2500
  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
- }
2501
+ const ticker = oneTicker(args, "insiders");
2398
2502
  const lookbackDays = typeof args.flags.days === "number" ? args.flags.days : void 0;
2399
- const envelope = await client().insider.getTrades(
2503
+ const api = client();
2504
+ const notes = [];
2505
+ const envelope = await api.insider.getTrades(
2400
2506
  ticker,
2401
2507
  lookbackDays === void 0 ? void 0 : { lookbackDays }
2402
2508
  );
2403
2509
  const trades = envelope.data ?? [];
2510
+ if (trades.length === 0) {
2511
+ const note = await verifyTickerOnEmpty(api, ticker);
2512
+ if (note) notes.push(note);
2513
+ }
2404
2514
  const shown = full ? trades : trades.slice(0, 15);
2405
2515
  const buys = trades.filter((trade) => trade.transactionType === "BUY");
2406
2516
  const sells = trades.filter((trade) => trade.transactionType === "SELL");
@@ -2451,7 +2561,7 @@ var insidersCommand = {
2451
2561
  tone: "dim"
2452
2562
  });
2453
2563
  }
2454
- return { json: envelope, doc: doc(...blocks) };
2564
+ return { json: envelope, doc: doc(...blocks), notes };
2455
2565
  }
2456
2566
  };
2457
2567
 
@@ -2468,25 +2578,23 @@ var insightsCommand = {
2468
2578
  examples: [
2469
2579
  "sentisense insights NVDA",
2470
2580
  "sentisense insights NVDA --urgency high",
2471
- "sentisense insights NVDA --type insider_buy_signal --full"
2581
+ "sentisense insights NVDA --type institutional_position_change --full"
2472
2582
  ],
2473
2583
  notes: [
2474
2584
  "Signals are generated observations about filings, flows, and attention, ordered by",
2475
2585
  "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."
2586
+ "A free key sees the top three; a PRO key sees the whole list.",
2587
+ "Signal types vary by ticker and over time, so take --type from what a plain run reports",
2588
+ "rather than guessing a name.",
2589
+ EMPTY_VERIFY_NOTE,
2590
+ EMPTY_VERIFY_NOTE_2
2477
2591
  ],
2478
2592
  flags: {
2479
2593
  urgency: { type: "string", placeholder: "level", describe: "Filter to low, medium, or high" },
2480
2594
  type: { type: "string", placeholder: "name", describe: "Filter to one signal type" }
2481
2595
  },
2482
2596
  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
- }
2597
+ const ticker = oneTicker(args, "insights");
2490
2598
  const urgency = typeof args.flags.urgency === "string" ? args.flags.urgency : void 0;
2491
2599
  if (urgency && !["low", "medium", "high"].includes(urgency)) {
2492
2600
  throw new CliUsageError(
@@ -2498,11 +2606,17 @@ var insightsCommand = {
2498
2606
  ...urgency ? { urgency } : {},
2499
2607
  ...typeof args.flags.type === "string" ? { insightType: args.flags.type } : {}
2500
2608
  };
2501
- const envelope = await client().insights.stock(
2609
+ const api = client();
2610
+ const notes = [];
2611
+ const envelope = await api.insights.stock(
2502
2612
  ticker,
2503
2613
  Object.keys(options).length > 0 ? options : void 0
2504
2614
  );
2505
2615
  const insights = envelope.data ?? [];
2616
+ if (insights.length === 0) {
2617
+ const note = await verifyTickerOnEmpty(api, ticker);
2618
+ if (note) notes.push(note);
2619
+ }
2506
2620
  const shown = full ? insights : insights.slice(0, 8);
2507
2621
  const blocks = [
2508
2622
  {
@@ -2555,7 +2669,7 @@ var insightsCommand = {
2555
2669
  tone: "dim"
2556
2670
  });
2557
2671
  }
2558
- return { json: envelope, doc: doc(...blocks) };
2672
+ return { json: envelope, doc: doc(...blocks), notes };
2559
2673
  }
2560
2674
  };
2561
2675
 
@@ -2584,7 +2698,8 @@ var moodCommand = {
2584
2698
  "46-55 neutral, 56-70 optimism, 71-85 greed, 86-100 extreme greed."
2585
2699
  ],
2586
2700
  flags: {},
2587
- async run({ client, full }) {
2701
+ async run({ args, client, full }) {
2702
+ rejectPositionals(args, "mood");
2588
2703
  const payload = await client().marketMood.get();
2589
2704
  const market = readMarket(payload);
2590
2705
  const sectors = readSectors(payload);
@@ -2669,7 +2784,9 @@ var newsCommand = {
2669
2784
  "A story is a cluster of articles covering the same event, not a single headline, so",
2670
2785
  "the size column is how many sources picked it up and impact ranks how much it moved.",
2671
2786
  "Tone is the average sentiment across the cluster, between -1 and 1.",
2672
- "--days only applies to the market-wide feed."
2787
+ "--days only applies to the market-wide feed.",
2788
+ EMPTY_VERIFY_NOTE,
2789
+ EMPTY_VERIFY_NOTE_2
2673
2790
  ],
2674
2791
  flags: {
2675
2792
  limit: { type: "number", placeholder: "N", describe: `Stories to return (default ${DEFAULT_LIMIT})` },
@@ -2677,10 +2794,15 @@ var newsCommand = {
2677
2794
  },
2678
2795
  async run({ args, client, full }) {
2679
2796
  const api = client();
2680
- const ticker = args.positionals[0]?.toUpperCase();
2797
+ const ticker = optionalTicker(args, "news");
2798
+ const notes = [];
2681
2799
  const limit = typeof args.flags.limit === "number" ? args.flags.limit : DEFAULT_LIMIT;
2682
2800
  const days = typeof args.flags.days === "number" ? args.flags.days : void 0;
2683
2801
  const stories = ticker ? await api.documents.getStoriesByTicker(ticker, { limit }) : await api.documents.getStories({ limit, ...days === void 0 ? {} : { days } });
2802
+ if (ticker && stories.length === 0) {
2803
+ const note = await verifyTickerOnEmpty(api, ticker);
2804
+ if (note) notes.push(note);
2805
+ }
2684
2806
  const blocks = [
2685
2807
  {
2686
2808
  kind: "head",
@@ -2738,7 +2860,7 @@ var newsCommand = {
2738
2860
  tone: "dim"
2739
2861
  });
2740
2862
  }
2741
- return { json: stories, doc: doc(...blocks) };
2863
+ return { json: stories, doc: doc(...blocks), notes };
2742
2864
  }
2743
2865
  };
2744
2866
 
@@ -2766,23 +2888,25 @@ var optionsCommand = {
2766
2888
  "End of day, not live: readings describe the latest completed session and refresh the",
2767
2889
  "following morning. Percentiles are against that ticker's own trailing history, so they",
2768
2890
  "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.",
2891
+ "Coverage is the most actively optioned names plus the tracked ETFs. A real ticker",
2892
+ "outside that set reports no coverage and exits 0, the same as any other empty result.",
2893
+ EMPTY_VERIFY_NOTE,
2894
+ EMPTY_VERIFY_NOTE_2,
2771
2895
  "A free key gets the full dossier for the first ten calls each month, then a headline",
2772
2896
  "preview. Calls that return no dossier do not count against that."
2773
2897
  ],
2774
2898
  flags: {},
2775
2899
  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);
2900
+ const ticker = oneTicker(args, "options");
2901
+ const api = client();
2902
+ const envelope = await api.stocks.getOptionsSummary(ticker);
2781
2903
  const data = envelope.data;
2782
2904
  if (!data) {
2905
+ const note = await verifyTickerOnEmpty(api, ticker);
2783
2906
  return {
2784
2907
  json: envelope,
2785
- doc: doc({ kind: "text", text: `No options coverage for ${ticker}.` })
2908
+ doc: doc({ kind: "text", text: `No options coverage for ${ticker}.` }),
2909
+ notes: note ? [note] : void 0
2786
2910
  };
2787
2911
  }
2788
2912
  const latest = data.latest ?? {};
@@ -3109,6 +3233,7 @@ var screenCommand = {
3109
3233
  etf: { type: "boolean", describe: "Screen the ETF universe" }
3110
3234
  },
3111
3235
  async run({ args, client, full }) {
3236
+ rejectPositionals(args, "screen");
3112
3237
  const api = client();
3113
3238
  const etf = args.flags.etf === true;
3114
3239
  if (args.flags.fields === true) {
@@ -3314,13 +3439,7 @@ var sentimentCommand = {
3314
3439
  days: { type: "number", placeholder: "N", describe: `Days of Score history (default ${DEFAULT_DAYS})` }
3315
3440
  },
3316
3441
  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
- }
3442
+ const ticker = oneTicker(args, "sentiment");
3324
3443
  const days = typeof args.flags.days === "number" ? args.flags.days : DEFAULT_DAYS;
3325
3444
  if (days < 1) {
3326
3445
  throw new CliUsageError("--days must be at least 1.", "for example: --days 30");
@@ -3517,6 +3636,12 @@ function mainHelp() {
3517
3636
  "",
3518
3637
  'Run "sentisense help <command>" for flags, examples, and exit codes.',
3519
3638
  "",
3639
+ "Saying who is calling (optional):",
3640
+ " SENTISENSE_AGENT_NAME=<name> what your agent calls itself",
3641
+ " SENTISENSE_SKILL=<slug> the skill driving it",
3642
+ " Set either and requests carry that identity, so usage can be understood and the",
3643
+ " tools improved. Nothing needs them, and nothing is inferred when they are absent.",
3644
+ "",
3520
3645
  "Research data, not investment advice."
3521
3646
  ];
3522
3647
  return `${lines.join("\n")}
@@ -3785,7 +3910,7 @@ async function runCli(argv, io) {
3785
3910
  return result.exitCode ?? EXIT.OK;
3786
3911
  } catch (error) {
3787
3912
  const debug = argv.includes("--debug");
3788
- const report = describeError(error, debug);
3913
+ const report = describeError(error, debug, name);
3789
3914
  for (const line of report.lines) io.stderr(`${line}
3790
3915
  `);
3791
3916
  return report.exitCode;