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 +20 -1
- package/dist/cli.cjs +203 -78
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
59
|
-
[EXIT.USAGE, "bad usage
|
|
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
|
|
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
|
-
"
|
|
427
|
-
"
|
|
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
|
|
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
|
|
616
|
-
"sentisense earnings
|
|
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
|
|
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
|
|
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.
|
|
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: "
|
|
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
|
-
|
|
2268
|
-
|
|
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
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2777
|
-
|
|
2778
|
-
|
|
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
|
|
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;
|