sentisense 0.43.0 → 0.45.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 +19 -0
- package/dist/cli.cjs +82 -23
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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"],
|
|
@@ -83,14 +83,14 @@ var MissingKeyError = class extends Error {
|
|
|
83
83
|
this.name = "MissingKeyError";
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
|
-
function describeError(error, debug) {
|
|
87
|
-
const report = classify(error);
|
|
86
|
+
function describeError(error, debug, command) {
|
|
87
|
+
const report = classify(error, command);
|
|
88
88
|
if (debug && error instanceof Error && error.stack) {
|
|
89
89
|
report.lines.push(error.stack);
|
|
90
90
|
}
|
|
91
91
|
return report;
|
|
92
92
|
}
|
|
93
|
-
function classify(error) {
|
|
93
|
+
function classify(error, command) {
|
|
94
94
|
if (error instanceof MissingKeyError) {
|
|
95
95
|
return {
|
|
96
96
|
exitCode: EXIT.AUTH,
|
|
@@ -156,6 +156,16 @@ function classify(error) {
|
|
|
156
156
|
]
|
|
157
157
|
};
|
|
158
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
|
+
}
|
|
159
169
|
return {
|
|
160
170
|
exitCode: EXIT.ERROR,
|
|
161
171
|
lines: [
|
|
@@ -268,6 +278,16 @@ function signedPercent(value, decimals = 2) {
|
|
|
268
278
|
function percent(value, decimals = 1) {
|
|
269
279
|
return isNum(value) ? `${value.toFixed(decimals)}%` : ABSENT;
|
|
270
280
|
}
|
|
281
|
+
var MAX_RATIO_DECIMALS = 6;
|
|
282
|
+
function ratioPercent(value, decimals = 2) {
|
|
283
|
+
if (!isNum(value)) return ABSENT;
|
|
284
|
+
const scaled = value * 100;
|
|
285
|
+
let places = decimals;
|
|
286
|
+
while (scaled !== 0 && places < MAX_RATIO_DECIMALS && Number(scaled.toFixed(places)) === 0) {
|
|
287
|
+
places += 1;
|
|
288
|
+
}
|
|
289
|
+
return `${scaled.toFixed(places)}%`;
|
|
290
|
+
}
|
|
271
291
|
function money(value, decimals = 2) {
|
|
272
292
|
return isNum(value) ? `$${value.toFixed(decimals)}` : ABSENT;
|
|
273
293
|
}
|
|
@@ -435,6 +455,7 @@ function readConfig(dir) {
|
|
|
435
455
|
const config = {};
|
|
436
456
|
if (typeof raw.apiKey === "string") config.apiKey = raw.apiKey;
|
|
437
457
|
if (typeof raw.agentName === "string") config.agentName = raw.agentName;
|
|
458
|
+
if (typeof raw.skill === "string") config.skill = raw.skill;
|
|
438
459
|
if (typeof raw.baseUrl === "string") config.baseUrl = raw.baseUrl;
|
|
439
460
|
return config;
|
|
440
461
|
} catch {
|
|
@@ -464,10 +485,11 @@ function maskKey(key) {
|
|
|
464
485
|
var authCommand = {
|
|
465
486
|
name: "auth",
|
|
466
487
|
summary: "Store an API key, show what is configured, or remove it",
|
|
467
|
-
usage: "sentisense auth [<key>] [--agent <name>] [--remove]",
|
|
488
|
+
usage: "sentisense auth [<key>] [--agent <name>] [--skill <slug>] [--remove]",
|
|
468
489
|
examples: [
|
|
469
490
|
"sentisense auth $SENTISENSE_API_KEY",
|
|
470
491
|
"sentisense auth --agent research-desk",
|
|
492
|
+
"sentisense auth --skill stock-analysis --agent research-desk",
|
|
471
493
|
"sentisense auth",
|
|
472
494
|
"sentisense auth --remove"
|
|
473
495
|
],
|
|
@@ -475,8 +497,10 @@ var authCommand = {
|
|
|
475
497
|
"Settings live in config.json under $SENTISENSE_CONFIG_DIR, $XDG_CONFIG_HOME/sentisense,",
|
|
476
498
|
"or ~/.config/sentisense, written owner-readable only (0600).",
|
|
477
499
|
"The key is never printed back in full, and never has to be pasted into a command again.",
|
|
478
|
-
"
|
|
479
|
-
"
|
|
500
|
+
"Two optional labels say who is calling: --agent is what your agent calls itself, and",
|
|
501
|
+
"--skill is the slug of the skill driving it. When set, they ride along in the",
|
|
502
|
+
"User-Agent, so usage can be understood and the tools improved. Both are voluntary and",
|
|
503
|
+
"nothing needs them to work."
|
|
480
504
|
],
|
|
481
505
|
flags: {
|
|
482
506
|
remove: { type: "boolean", describe: "Delete the stored settings" }
|
|
@@ -495,13 +519,15 @@ var authCommand = {
|
|
|
495
519
|
}
|
|
496
520
|
const key = args.positionals[0];
|
|
497
521
|
const agent = typeof args.flags.agent === "string" ? args.flags.agent : void 0;
|
|
522
|
+
const skill = typeof args.flags.skill === "string" ? args.flags.skill : void 0;
|
|
498
523
|
const baseUrl = typeof args.flags["base-url"] === "string" ? args.flags["base-url"] : void 0;
|
|
499
|
-
if (key || agent || baseUrl) {
|
|
524
|
+
if (key || agent || skill || baseUrl) {
|
|
500
525
|
const stored2 = readConfig(dir);
|
|
501
526
|
const next = {
|
|
502
527
|
...stored2,
|
|
503
528
|
...key ? { apiKey: key } : {},
|
|
504
529
|
...agent ? { agentName: agent } : {},
|
|
530
|
+
...skill ? { skill } : {},
|
|
505
531
|
...baseUrl ? { baseUrl } : {}
|
|
506
532
|
};
|
|
507
533
|
writeConfig(dir, next);
|
|
@@ -510,6 +536,7 @@ var authCommand = {
|
|
|
510
536
|
path,
|
|
511
537
|
apiKey: next.apiKey ? maskKey(next.apiKey) : null,
|
|
512
538
|
agentName: next.agentName ?? null,
|
|
539
|
+
skill: next.skill ?? null,
|
|
513
540
|
baseUrl: next.baseUrl ?? null
|
|
514
541
|
},
|
|
515
542
|
doc: doc(
|
|
@@ -519,6 +546,7 @@ var authCommand = {
|
|
|
519
546
|
items: fields(
|
|
520
547
|
next.apiKey ? field("api key", maskKey(next.apiKey)) : void 0,
|
|
521
548
|
next.agentName ? field("agent", next.agentName) : void 0,
|
|
549
|
+
next.skill ? field("skill", next.skill) : void 0,
|
|
522
550
|
next.baseUrl ? field("base url", next.baseUrl) : void 0
|
|
523
551
|
)
|
|
524
552
|
}
|
|
@@ -534,6 +562,7 @@ var authCommand = {
|
|
|
534
562
|
apiKey: resolved ? maskKey(resolved) : null,
|
|
535
563
|
apiKeySource: context.apiKeySource,
|
|
536
564
|
agentName: context.agentName ?? null,
|
|
565
|
+
skill: context.skill ?? null,
|
|
537
566
|
baseUrl: context.baseUrl ?? null
|
|
538
567
|
},
|
|
539
568
|
doc: doc(
|
|
@@ -544,6 +573,7 @@ var authCommand = {
|
|
|
544
573
|
field("source", resolved ? context.apiKeySource : "none"),
|
|
545
574
|
field("config", path),
|
|
546
575
|
context.agentName ? field("agent", context.agentName) : void 0,
|
|
576
|
+
context.skill ? field("skill", context.skill) : void 0,
|
|
547
577
|
context.baseUrl ? field("base url", context.baseUrl) : void 0
|
|
548
578
|
)
|
|
549
579
|
},
|
|
@@ -1008,7 +1038,7 @@ var flowsCommand = {
|
|
|
1008
1038
|
};
|
|
1009
1039
|
|
|
1010
1040
|
// src/version.ts
|
|
1011
|
-
var VERSION = "0.
|
|
1041
|
+
var VERSION = "0.45.0";
|
|
1012
1042
|
|
|
1013
1043
|
// src/resources/analyst.ts
|
|
1014
1044
|
var Analyst = class {
|
|
@@ -2190,7 +2220,12 @@ var GLOBAL_FLAGS = {
|
|
|
2190
2220
|
agent: {
|
|
2191
2221
|
type: "string",
|
|
2192
2222
|
placeholder: "name",
|
|
2193
|
-
describe: "
|
|
2223
|
+
describe: "Name your agent in the User-Agent, if you want to"
|
|
2224
|
+
},
|
|
2225
|
+
skill: {
|
|
2226
|
+
type: "string",
|
|
2227
|
+
placeholder: "slug",
|
|
2228
|
+
describe: "Name the skill driving this call, if you want to"
|
|
2194
2229
|
}
|
|
2195
2230
|
};
|
|
2196
2231
|
var SHORT_FLAGS = {
|
|
@@ -2325,6 +2360,7 @@ function resolveContext({ flags, env, configDir }) {
|
|
|
2325
2360
|
const key = pick(flagString(flags, "api-key"), env.SENTISENSE_API_KEY, stored.apiKey);
|
|
2326
2361
|
const base = pick(flagString(flags, "base-url"), env.SENTISENSE_BASE_URL, stored.baseUrl);
|
|
2327
2362
|
const agent = pick(flagString(flags, "agent"), env.SENTISENSE_AGENT_NAME, stored.agentName);
|
|
2363
|
+
const skill = pick(flagString(flags, "skill"), env.SENTISENSE_SKILL, stored.skill);
|
|
2328
2364
|
return {
|
|
2329
2365
|
configDir: dir,
|
|
2330
2366
|
apiKey: key.value,
|
|
@@ -2332,21 +2368,27 @@ function resolveContext({ flags, env, configDir }) {
|
|
|
2332
2368
|
baseUrl: base.value,
|
|
2333
2369
|
baseUrlSource: base.source,
|
|
2334
2370
|
agentName: agent.value,
|
|
2335
|
-
agentSource: agent.source
|
|
2371
|
+
agentSource: agent.source,
|
|
2372
|
+
skill: skill.value,
|
|
2373
|
+
skillSource: skill.source
|
|
2336
2374
|
};
|
|
2337
2375
|
}
|
|
2338
2376
|
var DEFAULT_BASE_URL2 = "https://app.sentisense.ai";
|
|
2339
2377
|
function effectiveBaseUrl(context) {
|
|
2340
2378
|
return context.baseUrl ?? DEFAULT_BASE_URL2;
|
|
2341
2379
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2380
|
+
var MAX_IDENTITY = 32;
|
|
2381
|
+
function sanitizeIdentity(value) {
|
|
2382
|
+
return value.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "").replace(/-{2,}/g, "-").slice(0, MAX_IDENTITY).replace(/^-+|-+$/g, "");
|
|
2344
2383
|
}
|
|
2345
2384
|
function userAgentSuffix(context) {
|
|
2346
|
-
const
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
|
|
2385
|
+
const product = `sentisense-cli/${VERSION}`;
|
|
2386
|
+
const slug = sanitizeIdentity(context.skill ?? "");
|
|
2387
|
+
const agent = sanitizeIdentity(context.agentName ?? "");
|
|
2388
|
+
const comment = [];
|
|
2389
|
+
if (slug) comment.push(slug);
|
|
2390
|
+
if (agent) comment.push(`agent/${agent}`);
|
|
2391
|
+
return comment.length === 0 ? product : `${product} (${comment.join("; ")})`;
|
|
2350
2392
|
}
|
|
2351
2393
|
function createClient(context, options = {}) {
|
|
2352
2394
|
if (!options.anonymous && !context.apiKey) throw new MissingKeyError();
|
|
@@ -2752,13 +2794,20 @@ var newsCommand = {
|
|
|
2752
2794
|
"A story is a cluster of articles covering the same event, not a single headline, so",
|
|
2753
2795
|
"the size column is how many sources picked it up and impact ranks how much it moved.",
|
|
2754
2796
|
"Tone is the average sentiment across the cluster, between -1 and 1.",
|
|
2755
|
-
"--days only applies to the market-wide feed
|
|
2797
|
+
"--days only applies to the market-wide feed, and counts from when a story STARTED",
|
|
2798
|
+
"breaking, not from its latest article. A running story with fresh coverage but an",
|
|
2799
|
+
"older start falls out of short windows, so an empty window can be correct. It also",
|
|
2800
|
+
"switches ordering to curation score instead of the day-bucketed default.",
|
|
2756
2801
|
EMPTY_VERIFY_NOTE,
|
|
2757
2802
|
EMPTY_VERIFY_NOTE_2
|
|
2758
2803
|
],
|
|
2759
2804
|
flags: {
|
|
2760
2805
|
limit: { type: "number", placeholder: "N", describe: `Stories to return (default ${DEFAULT_LIMIT})` },
|
|
2761
|
-
days: {
|
|
2806
|
+
days: {
|
|
2807
|
+
type: "number",
|
|
2808
|
+
placeholder: "N",
|
|
2809
|
+
describe: "Look-back window in days, market-wide feed only"
|
|
2810
|
+
}
|
|
2762
2811
|
},
|
|
2763
2812
|
async run({ args, client, full }) {
|
|
2764
2813
|
const api = client();
|
|
@@ -2766,7 +2815,10 @@ var newsCommand = {
|
|
|
2766
2815
|
const notes = [];
|
|
2767
2816
|
const limit = typeof args.flags.limit === "number" ? args.flags.limit : DEFAULT_LIMIT;
|
|
2768
2817
|
const days = typeof args.flags.days === "number" ? args.flags.days : void 0;
|
|
2769
|
-
const stories = ticker ? await api.documents.getStoriesByTicker(ticker, { limit }) : await api.documents.getStories({
|
|
2818
|
+
const stories = ticker ? await api.documents.getStoriesByTicker(ticker, { limit }) : await api.documents.getStories({
|
|
2819
|
+
limit,
|
|
2820
|
+
...days === void 0 ? {} : { filterHours: days * 24 }
|
|
2821
|
+
});
|
|
2770
2822
|
if (ticker && stories.length === 0) {
|
|
2771
2823
|
const note = await verifyTickerOnEmpty(api, ticker);
|
|
2772
2824
|
if (note) notes.push(note);
|
|
@@ -3000,7 +3052,8 @@ function single(row, full) {
|
|
|
3000
3052
|
field("Mkt cap", humanize(quote.marketCap)),
|
|
3001
3053
|
field("P/E", fixed(quote.peRatio)),
|
|
3002
3054
|
field("EPS TTM", fixed(quote.epsTTM)),
|
|
3003
|
-
|
|
3055
|
+
// A ratio, unlike `changePercent` and the other percentages in the same payload.
|
|
3056
|
+
field("Div yield", ratioPercent(quote.dividendYield)),
|
|
3004
3057
|
field(
|
|
3005
3058
|
"52w",
|
|
3006
3059
|
`${fixed(quote.week52Low)} to ${fixed(quote.week52High)}`
|
|
@@ -3604,6 +3657,12 @@ function mainHelp() {
|
|
|
3604
3657
|
"",
|
|
3605
3658
|
'Run "sentisense help <command>" for flags, examples, and exit codes.',
|
|
3606
3659
|
"",
|
|
3660
|
+
"Saying who is calling (optional):",
|
|
3661
|
+
" SENTISENSE_AGENT_NAME=<name> what your agent calls itself",
|
|
3662
|
+
" SENTISENSE_SKILL=<slug> the skill driving it",
|
|
3663
|
+
" Set either and requests carry that identity, so usage can be understood and the",
|
|
3664
|
+
" tools improved. Nothing needs them, and nothing is inferred when they are absent.",
|
|
3665
|
+
"",
|
|
3607
3666
|
"Research data, not investment advice."
|
|
3608
3667
|
];
|
|
3609
3668
|
return `${lines.join("\n")}
|
|
@@ -3872,7 +3931,7 @@ async function runCli(argv, io) {
|
|
|
3872
3931
|
return result.exitCode ?? EXIT.OK;
|
|
3873
3932
|
} catch (error) {
|
|
3874
3933
|
const debug = argv.includes("--debug");
|
|
3875
|
-
const report = describeError(error, debug);
|
|
3934
|
+
const report = describeError(error, debug, name);
|
|
3876
3935
|
for (const line of report.lines) io.stderr(`${line}
|
|
3877
3936
|
`);
|
|
3878
3937
|
return report.exitCode;
|