tn-financial-data 0.1.1 → 0.1.2

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
@@ -26,7 +26,7 @@ This package is the hosted agent-facing surface only. The repo's website, local
26
26
  ## Features
27
27
 
28
28
  - Company facts, financials, prices, news, insider trades, ownership, and global rates through one hosted API
29
- - Agent-friendly CLI with JSON output and self-description via `tn-financial-data describe opencli`
29
+ - Agent-friendly CLI with clean error output, scoped subcommand help, and self-description via `tn-financial-data describe opencli`
30
30
  - Packaged skill bundle for Claude and OpenCode installs
31
31
  - Typed TypeScript client from the root package export
32
32
  - Stable production default at `https://api.truenorth-financial.ai/v1`
@@ -135,6 +135,17 @@ tn-financial-data describe opencli
135
135
  Installing the skill bundle does not provision API access. Agents still need the same runtime credential for live reads.
136
136
  For OpenCode, that means the `opencode` process itself must see `TN_FINANCIAL_DATA_API_KEY` in its environment.
137
137
 
138
+ ## Rate Limits
139
+
140
+ The hosted API returns standard rate-limit headers:
141
+
142
+ - `X-RateLimit-Limit`
143
+ - `X-RateLimit-Remaining`
144
+ - `X-RateLimit-Reset`
145
+ - `Retry-After` on `429`
146
+
147
+ When the CLI is throttled, it prints a clean retry message instead of a stack trace.
148
+
138
149
  ## Agent Integration
139
150
 
140
151
  Agents only need two surfaces:
@@ -155,8 +166,9 @@ const client = new TnFinancialData({
155
166
  apiKey: process.env.TN_FINANCIAL_DATA_API_KEY!,
156
167
  });
157
168
 
158
- const company = await client.getCompanyFacts({ ticker: "AAPL" });
169
+ const company = await client.getCompanyFacts("AAPL");
159
170
  console.log(company.company_facts.name);
171
+ console.log(company._rateLimit);
160
172
  ```
161
173
 
162
174
  ## Package Scope
package/dist/cli.js CHANGED
@@ -7,6 +7,43 @@ import { pathToFileURL } from "url";
7
7
 
8
8
  // src/client/index.ts
9
9
  var DEFAULT_API_BASE_URL = "https://api.truenorth-financial.ai/v1";
10
+ function parseOptionalInt(value) {
11
+ if (!value) {
12
+ return null;
13
+ }
14
+ const parsed = Number.parseInt(value, 10);
15
+ return Number.isFinite(parsed) ? parsed : null;
16
+ }
17
+ function parseOptionalUnixTimestamp(value) {
18
+ const parsed = parseOptionalInt(value);
19
+ if (parsed === null) {
20
+ return null;
21
+ }
22
+ return new Date(parsed * 1e3).toISOString();
23
+ }
24
+ function parseRateLimitInfo(headers) {
25
+ const info = {
26
+ limit: parseOptionalInt(headers.get("X-RateLimit-Limit")),
27
+ remaining: parseOptionalInt(headers.get("X-RateLimit-Remaining")),
28
+ resetAt: parseOptionalUnixTimestamp(headers.get("X-RateLimit-Reset")),
29
+ retryAfterSeconds: parseOptionalInt(headers.get("Retry-After"))
30
+ };
31
+ return Object.values(info).some((value) => value !== null) ? info : null;
32
+ }
33
+ var ApiError = class extends Error {
34
+ status;
35
+ code;
36
+ body;
37
+ rateLimit;
38
+ constructor(options) {
39
+ super(`API error ${options.status}: ${options.message}`);
40
+ this.name = "ApiError";
41
+ this.status = options.status;
42
+ this.code = options.code;
43
+ this.body = options.body;
44
+ this.rateLimit = options.rateLimit;
45
+ }
46
+ };
10
47
  var TnFinancialData = class {
11
48
  apiKey;
12
49
  baseUrl;
@@ -27,21 +64,44 @@ var TnFinancialData = class {
27
64
  headers
28
65
  });
29
66
  if (!res.ok) {
30
- const errorMessage = await this.extractErrorMessage(res);
31
- throw new Error(`API error ${res.status}: ${errorMessage}`);
67
+ const errorBody = await this.extractErrorBody(res);
68
+ throw new ApiError({
69
+ status: res.status,
70
+ code: errorBody.code,
71
+ message: errorBody.message,
72
+ body: errorBody.rawBody,
73
+ rateLimit: parseRateLimitInfo(res.headers)
74
+ });
32
75
  }
33
- return res.json();
76
+ const payload = await res.json();
77
+ Object.defineProperty(payload, "_rateLimit", {
78
+ value: parseRateLimitInfo(res.headers),
79
+ enumerable: false,
80
+ configurable: true,
81
+ writable: false
82
+ });
83
+ return payload;
34
84
  }
35
- async extractErrorMessage(res) {
85
+ async extractErrorBody(res) {
36
86
  const rawBody = await res.text().catch(() => "");
37
87
  const trimmed = rawBody.trim();
38
- if (trimmed.length === 0) return "Unknown error";
88
+ if (trimmed.length === 0) {
89
+ return { message: "Unknown error", code: null, rawBody: "" };
90
+ }
39
91
  try {
40
92
  const parsed = JSON.parse(trimmed);
41
93
  const code = parsed.code ? `[${parsed.code}] ` : "";
42
- return `${code}${parsed.error?.trim() || trimmed}`;
94
+ return {
95
+ message: `${code}${parsed.error?.trim() || trimmed}`,
96
+ code: parsed.code?.trim() || null,
97
+ rawBody: trimmed
98
+ };
43
99
  } catch {
44
- return trimmed;
100
+ return {
101
+ message: trimmed,
102
+ code: null,
103
+ rawBody: trimmed
104
+ };
45
105
  }
46
106
  }
47
107
  appendLimitParam(params, limit) {
@@ -1124,6 +1184,273 @@ function buildOpenCliDocument() {
1124
1184
  };
1125
1185
  }
1126
1186
 
1187
+ // src/cli/query-subcommand-help.ts
1188
+ function renderHelp(command, description, options, example) {
1189
+ return [
1190
+ `tn-financial-data query ${command}`,
1191
+ "",
1192
+ ...description,
1193
+ "",
1194
+ "Options:",
1195
+ ...options.map((line) => ` ${line}`),
1196
+ "",
1197
+ "Example:",
1198
+ ` ${example}`
1199
+ ].join("\n");
1200
+ }
1201
+ var QUERY_SUBCOMMAND_HELP = {
1202
+ "available-tickers": renderHelp(
1203
+ "available-tickers",
1204
+ ["List tickers currently supported by the hosted read API."],
1205
+ [
1206
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1207
+ "--base-url <url> API base URL override."
1208
+ ],
1209
+ "tn-financial-data query available-tickers"
1210
+ ),
1211
+ company: renderHelp(
1212
+ "company",
1213
+ ["Fetch the basic company identity record for a single ticker."],
1214
+ [
1215
+ "--ticker <symbol> Required. Stock ticker symbol.",
1216
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1217
+ "--base-url <url> API base URL override."
1218
+ ],
1219
+ "tn-financial-data query company --ticker AAPL"
1220
+ ),
1221
+ "general-overview": renderHelp(
1222
+ "general-overview",
1223
+ ["Fetch the bundled company, financials, price snapshot, and news overview for one ticker."],
1224
+ [
1225
+ "--ticker <symbol> Required. Stock ticker symbol.",
1226
+ "--period <period> annual | quarterly | ttm. Default: annual.",
1227
+ "--financial-limit <n> Number of financial periods to return.",
1228
+ "--news-limit <n> Number of news items to return.",
1229
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1230
+ "--base-url <url> API base URL override."
1231
+ ],
1232
+ "tn-financial-data query general-overview --ticker AAPL --period quarterly --financial-limit 4 --news-limit 5"
1233
+ ),
1234
+ financials: renderHelp(
1235
+ "financials",
1236
+ [
1237
+ "Fetch income statements, balance sheets, cash flow statements, or key statistics for a single ticker."
1238
+ ],
1239
+ [
1240
+ "--ticker <symbol> Required. Stock ticker symbol.",
1241
+ "--period <period> annual | quarterly | ttm. Required unless --statement key-stats.",
1242
+ "--statement <type> all | income | balance | cash-flow | key-stats. Default: all.",
1243
+ "--limit <n> Number of periods to return.",
1244
+ "--report-period <date> Exact report period (YYYY-MM-DD).",
1245
+ "--report-period-gte <date> Report period >= date.",
1246
+ "--report-period-lte <date> Report period <= date.",
1247
+ "--report-period-gt <date> Report period > date.",
1248
+ "--report-period-lt <date> Report period < date.",
1249
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1250
+ "--base-url <url> API base URL override."
1251
+ ],
1252
+ "tn-financial-data query financials --ticker AAPL --period annual --statement income --limit 4"
1253
+ ),
1254
+ "financial-metrics": renderHelp(
1255
+ "financial-metrics",
1256
+ ["Fetch derived profitability, leverage, liquidity, and growth metrics for a single ticker."],
1257
+ [
1258
+ "--ticker <symbol> Required. Stock ticker symbol.",
1259
+ "--period <period> annual | quarterly | ttm. Default: annual.",
1260
+ "--limit <n> Number of periods to return.",
1261
+ "--report-period <date> Exact report period (YYYY-MM-DD).",
1262
+ "--report-period-gte <date> Report period >= date.",
1263
+ "--report-period-lte <date> Report period <= date.",
1264
+ "--report-period-gt <date> Report period > date.",
1265
+ "--report-period-lt <date> Report period < date.",
1266
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1267
+ "--base-url <url> API base URL override."
1268
+ ],
1269
+ "tn-financial-data query financial-metrics --ticker AAPL --period quarterly --limit 8"
1270
+ ),
1271
+ "financial-metrics-snapshot": renderHelp(
1272
+ "financial-metrics-snapshot",
1273
+ ["Fetch the latest metrics bundle, price snapshot, and key statistics for one ticker."],
1274
+ [
1275
+ "--ticker <symbol> Required. Stock ticker symbol.",
1276
+ "--period <period> annual | quarterly | ttm. Default: annual.",
1277
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1278
+ "--base-url <url> API base URL override."
1279
+ ],
1280
+ "tn-financial-data query financial-metrics-snapshot --ticker AAPL --period quarterly"
1281
+ ),
1282
+ earnings: renderHelp(
1283
+ "earnings",
1284
+ ["Fetch reported earnings rows and consensus surprise fields for a single ticker."],
1285
+ [
1286
+ "--ticker <symbol> Required. Stock ticker symbol.",
1287
+ "--period <period> annual | quarterly | ttm. Default: quarterly.",
1288
+ "--limit <n> Number of periods to return.",
1289
+ "--report-period <date> Exact report period (YYYY-MM-DD).",
1290
+ "--report-period-gte <date> Report period >= date.",
1291
+ "--report-period-lte <date> Report period <= date.",
1292
+ "--report-period-gt <date> Report period > date.",
1293
+ "--report-period-lt <date> Report period < date.",
1294
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1295
+ "--base-url <url> API base URL override."
1296
+ ],
1297
+ "tn-financial-data query earnings --ticker AAPL --period quarterly --limit 8"
1298
+ ),
1299
+ "quarterly-highlights": renderHelp(
1300
+ "quarterly-highlights",
1301
+ [
1302
+ "Fetch issuer-reported supplemental quarterly highlights such as segment mix and selected KPIs."
1303
+ ],
1304
+ [
1305
+ "--ticker <symbol> Required. Stock ticker symbol.",
1306
+ "--report-period <date> Exact report period (YYYY-MM-DD).",
1307
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1308
+ "--base-url <url> API base URL override."
1309
+ ],
1310
+ "tn-financial-data query quarterly-highlights --ticker AAPL"
1311
+ ),
1312
+ "analyst-estimates": renderHelp(
1313
+ "analyst-estimates",
1314
+ ["Fetch analyst EPS/revenue estimate rows and the current price-target snapshot for a ticker."],
1315
+ [
1316
+ "--ticker <symbol> Required. Stock ticker symbol.",
1317
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1318
+ "--base-url <url> API base URL override."
1319
+ ],
1320
+ "tn-financial-data query analyst-estimates --ticker AAPL"
1321
+ ),
1322
+ prices: renderHelp(
1323
+ "prices",
1324
+ ["Fetch historical price bars for a single ticker."],
1325
+ [
1326
+ "--ticker <symbol> Required. Stock ticker symbol.",
1327
+ "--interval <period> day | 1h.",
1328
+ "--start-date <date> Required. Start date (YYYY-MM-DD).",
1329
+ "--end-date <date> Required. End date (YYYY-MM-DD).",
1330
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1331
+ "--base-url <url> API base URL override."
1332
+ ],
1333
+ "tn-financial-data query prices --ticker AAPL --interval day --start-date 2025-01-01 --end-date 2025-01-31"
1334
+ ),
1335
+ snapshot: renderHelp(
1336
+ "snapshot",
1337
+ ["Fetch the latest price snapshot for a single ticker."],
1338
+ [
1339
+ "--ticker <symbol> Required. Stock ticker symbol.",
1340
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1341
+ "--base-url <url> API base URL override."
1342
+ ],
1343
+ "tn-financial-data query snapshot --ticker AAPL"
1344
+ ),
1345
+ news: renderHelp(
1346
+ "news",
1347
+ ["Fetch curated issuer news for a single ticker."],
1348
+ [
1349
+ "--ticker <symbol> Required. Stock ticker symbol.",
1350
+ "--start-date <date> Optional start date (YYYY-MM-DD).",
1351
+ "--end-date <date> Optional end date (YYYY-MM-DD).",
1352
+ "--publisher <name> Optional publisher filter.",
1353
+ "--limit <n> Number of articles to return.",
1354
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1355
+ "--base-url <url> API base URL override."
1356
+ ],
1357
+ "tn-financial-data query news --ticker AAPL --limit 10"
1358
+ ),
1359
+ "insider-trades": renderHelp(
1360
+ "insider-trades",
1361
+ ["Fetch SEC Form 4 insider-trade rows for a single issuer."],
1362
+ [
1363
+ "--ticker <symbol> Required. Stock ticker symbol.",
1364
+ "--start-date <date> Optional start date (YYYY-MM-DD).",
1365
+ "--end-date <date> Optional end date (YYYY-MM-DD).",
1366
+ "--reporting-owner-cik <cik> Optional reporting owner filter.",
1367
+ "--transaction-code <csv> Optional comma-separated transaction codes.",
1368
+ "--security-type <type> all | non_derivative | derivative.",
1369
+ "--limit <n> Number of rows to return.",
1370
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1371
+ "--base-url <url> API base URL override."
1372
+ ],
1373
+ "tn-financial-data query insider-trades --ticker AAPL --transaction-code P,S --limit 25"
1374
+ ),
1375
+ "institutional-ownership": renderHelp(
1376
+ "institutional-ownership",
1377
+ ["Fetch issuer-centric institutional holder rows for a single ticker."],
1378
+ [
1379
+ "--ticker <symbol> Required. Stock ticker symbol.",
1380
+ "--limit <n> Number of holder rows to return.",
1381
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1382
+ "--base-url <url> API base URL override."
1383
+ ],
1384
+ "tn-financial-data query institutional-ownership --ticker AAPL --limit 20"
1385
+ ),
1386
+ "investor-portfolio": renderHelp(
1387
+ "investor-portfolio",
1388
+ ["Fetch investor-centric SEC 13F holdings by filer CIK or known investor alias."],
1389
+ [
1390
+ "--cik <cik> SEC filer CIK.",
1391
+ "--investor <id> Known investor alias such as vanguard.",
1392
+ "--limit <n> Number of holdings to return.",
1393
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1394
+ "--base-url <url> API base URL override."
1395
+ ],
1396
+ "tn-financial-data query investor-portfolio --investor vanguard --limit 20"
1397
+ ),
1398
+ "global-rates": renderHelp(
1399
+ "global-rates",
1400
+ ["Fetch curated global policy and money-market rate series."],
1401
+ [
1402
+ "--series <csv> Optional comma-separated series keys.",
1403
+ "--start-date <date> Optional start date (YYYY-MM-DD).",
1404
+ "--end-date <date> Optional end date (YYYY-MM-DD).",
1405
+ "--limit <n> Number of observations to return per series.",
1406
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1407
+ "--base-url <url> API base URL override."
1408
+ ],
1409
+ "tn-financial-data query global-rates --series ecb_refi,boe_sonia --limit 10"
1410
+ ),
1411
+ "segmented-revenues": renderHelp(
1412
+ "segmented-revenues",
1413
+ ["Fetch reported segment revenue rows for a single issuer."],
1414
+ [
1415
+ "--ticker <symbol> Required. Stock ticker symbol.",
1416
+ "--period <period> annual | quarterly | ttm.",
1417
+ "--limit <n> Number of periods to return.",
1418
+ "--segment-type <type> Optional segment type filter.",
1419
+ "--report-period <date> Exact report period (YYYY-MM-DD).",
1420
+ "--report-period-gte <date> Report period >= date.",
1421
+ "--report-period-lte <date> Report period <= date.",
1422
+ "--report-period-gt <date> Report period > date.",
1423
+ "--report-period-lt <date> Report period < date.",
1424
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1425
+ "--base-url <url> API base URL override."
1426
+ ],
1427
+ "tn-financial-data query segmented-revenues --ticker AAPL --period quarterly --limit 4"
1428
+ ),
1429
+ screen: renderHelp(
1430
+ "screen",
1431
+ ["Run the hosted equity screener with one or more filter expressions."],
1432
+ [
1433
+ '--filter "field operator value" Required. Repeat for multiple filters.',
1434
+ "--limit <n> Number of rows to return.",
1435
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1436
+ "--base-url <url> API base URL override."
1437
+ ],
1438
+ 'tn-financial-data query screen --filter "trailing_pe < 25" --filter "recommendation_key = buy" --limit 10'
1439
+ ),
1440
+ "screen-fields": renderHelp(
1441
+ "screen-fields",
1442
+ ["List the supported screener fields and operator metadata."],
1443
+ [
1444
+ "--api-key <key> API key. Overrides TN_FINANCIAL_DATA_API_KEY.",
1445
+ "--base-url <url> API base URL override."
1446
+ ],
1447
+ "tn-financial-data query screen-fields"
1448
+ )
1449
+ };
1450
+ function getQuerySubcommandHelp(resource) {
1451
+ return QUERY_SUBCOMMAND_HELP[resource] ?? null;
1452
+ }
1453
+
1127
1454
  // src/cli/skill.ts
1128
1455
  import { cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, rmSync } from "fs";
1129
1456
  import { homedir as defaultHomedir } from "os";
@@ -1224,6 +1551,23 @@ function installSkill(options = {}) {
1224
1551
  // src/cli/index.ts
1225
1552
  var CliUsageError = class extends Error {
1226
1553
  };
1554
+ function isCliDebugEnabled(runtime) {
1555
+ const debug = runtime.env.DEBUG?.trim().toLowerCase();
1556
+ return debug === "1" || debug === "true" || debug === "tn-financial-data";
1557
+ }
1558
+ function formatRateLimitError(error) {
1559
+ const retryAfterSeconds = error.rateLimit?.retryAfterSeconds;
1560
+ const remaining = error.rateLimit?.remaining;
1561
+ const limit = error.rateLimit?.limit;
1562
+ const segments = ["Rate limit exceeded."];
1563
+ if (remaining !== null && remaining !== void 0 && limit !== null && limit !== void 0) {
1564
+ segments.push(`${remaining}/${limit} requests remaining.`);
1565
+ }
1566
+ if (retryAfterSeconds !== null && retryAfterSeconds !== void 0) {
1567
+ segments.push(`Retry after ${retryAfterSeconds} seconds.`);
1568
+ }
1569
+ return segments.join(" ");
1570
+ }
1227
1571
  function logCliErrorDetails(runtime, error, prefix = "") {
1228
1572
  runtime.stderr(`${prefix}${error.message}`);
1229
1573
  const pgLikeError = error;
@@ -1252,12 +1596,29 @@ function logCliError(runtime, error) {
1252
1596
  runtime.stderr("Unknown CLI error.");
1253
1597
  return;
1254
1598
  }
1255
- if (error instanceof CliUsageError) {
1599
+ if (!isCliDebugEnabled(runtime)) {
1600
+ if (error instanceof CliUsageError) {
1601
+ runtime.stderr(error.message);
1602
+ return;
1603
+ }
1604
+ if (error instanceof ApiError && error.status === 429) {
1605
+ runtime.stderr(formatRateLimitError(error));
1606
+ return;
1607
+ }
1256
1608
  runtime.stderr(error.message);
1257
1609
  return;
1258
1610
  }
1259
1611
  logCliErrorDetails(runtime, error);
1260
1612
  }
1613
+ function getCliExitCode(error) {
1614
+ if (error instanceof CliUsageError) {
1615
+ return 2;
1616
+ }
1617
+ if (error instanceof ApiError && error.status === 400) {
1618
+ return 2;
1619
+ }
1620
+ return 1;
1621
+ }
1261
1622
  function createRuntime(overrides = {}) {
1262
1623
  return {
1263
1624
  stdout: overrides.stdout ?? ((line) => process.stdout.write(`${line}
@@ -1613,10 +1974,14 @@ function buildFinancialCliOptions(parsed, statement) {
1613
1974
  }
1614
1975
  async function handleQuery(parsed, runtime) {
1615
1976
  const resource = parsed.positionals[1];
1616
- if (!resource || hasOption(parsed, "help")) {
1977
+ if (!resource) {
1617
1978
  runtime.stdout(queryHelp());
1618
1979
  return 0;
1619
1980
  }
1981
+ if (hasOption(parsed, "help")) {
1982
+ runtime.stdout(getQuerySubcommandHelp(resource) ?? queryHelp());
1983
+ return 0;
1984
+ }
1620
1985
  const client = createClient(parsed, runtime);
1621
1986
  switch (resource) {
1622
1987
  case "available-tickers":
@@ -2029,7 +2394,7 @@ async function runCli(argv, runtimeOverrides = {}) {
2029
2394
  }
2030
2395
  } catch (error) {
2031
2396
  logCliError(runtime, error);
2032
- return error instanceof CliUsageError ? 2 : 1;
2397
+ return getCliExitCode(error);
2033
2398
  }
2034
2399
  }
2035
2400
  function isMainModule() {
@@ -250,6 +250,12 @@ interface ClientOptions {
250
250
  baseUrl?: string;
251
251
  fetch?: typeof fetch;
252
252
  }
253
+ interface RateLimitInfo {
254
+ limit: number | null;
255
+ remaining: number | null;
256
+ resetAt: string | null;
257
+ retryAfterSeconds: number | null;
258
+ }
253
259
  type SnakeCase<T extends string> = T extends `${infer Head}${infer Tail}` ? Tail extends Uncapitalize<Tail> ? `${Lowercase<Head>}${SnakeCase<Tail>}` : `${Lowercase<Head>}_${SnakeCase<Tail>}` : T;
254
260
  type SnakeCasedProperties<T> = {
255
261
  [Key in keyof T as Key extends string ? SnakeCase<Key> : Key]: T[Key];
@@ -701,14 +707,31 @@ interface ScreenerResponse {
701
707
  filters_applied: ScreenerFilter[];
702
708
  results: ScreenerResultRow[];
703
709
  }
710
+ interface RateLimitAnnotatedResponse {
711
+ _rateLimit?: RateLimitInfo | null;
712
+ }
713
+ type ApiResponse<T extends object> = T & RateLimitAnnotatedResponse;
704
714
  declare const DEFAULT_API_BASE_URL = "https://api.truenorth-financial.ai/v1";
715
+ declare class ApiError extends Error {
716
+ readonly status: number;
717
+ readonly code: string | null;
718
+ readonly body: string;
719
+ readonly rateLimit: RateLimitInfo | null;
720
+ constructor(options: {
721
+ status: number;
722
+ code: string | null;
723
+ message: string;
724
+ body: string;
725
+ rateLimit: RateLimitInfo | null;
726
+ });
727
+ }
705
728
  declare class TnFinancialData {
706
729
  private apiKey;
707
730
  private baseUrl;
708
731
  private fetchFn;
709
732
  constructor(opts: ClientOptions);
710
733
  private request;
711
- private extractErrorMessage;
734
+ private extractErrorBody;
712
735
  private appendLimitParam;
713
736
  private appendReportPeriodParams;
714
737
  private buildFinancialParams;
@@ -717,31 +740,31 @@ declare class TnFinancialData {
717
740
  private buildSegmentedRevenueParams;
718
741
  private buildQuarterlyHighlightsParams;
719
742
  private buildInsiderTradeParams;
720
- getIncomeStatements(ticker: string, opts: FinancialOpts): Promise<IncomeStatementsResponse>;
721
- getBalanceSheets(ticker: string, opts: FinancialOpts): Promise<BalanceSheetsResponse>;
722
- getCashFlowStatements(ticker: string, opts: FinancialOpts): Promise<CashFlowStatementsResponse>;
723
- getFinancials(ticker: string, opts: FinancialOpts): Promise<FinancialsResponse>;
724
- getFinancialMetrics(ticker: string, opts?: FinancialMetricsOpts): Promise<FinancialMetricsResponse>;
725
- getFinancialMetricsSnapshot(ticker: string, opts?: Pick<FinancialMetricsOpts, "period">): Promise<FinancialMetricsSnapshotResponse>;
726
- getEarnings(ticker: string, opts?: EarningsOpts): Promise<EarningsResponse>;
727
- getGeneralOverview(ticker: string, opts?: GeneralOverviewOpts): Promise<GeneralOverviewResponse>;
728
- getKeyStatistics(ticker: string): Promise<KeyStatisticsResponse>;
729
- getAvailableTickers(): Promise<AvailableTickersResponse>;
730
- getPrices(ticker: string, opts: PriceOpts): Promise<PriceHistoryResponse>;
731
- getPriceSnapshot(ticker: string): Promise<PriceSnapshotResponse>;
732
- getNews(ticker: string, opts?: NewsOpts): Promise<NewsResponse>;
733
- getInsiderTrades(ticker: string, opts?: InsiderTradesOpts): Promise<InsiderTradesResponse>;
734
- getInstitutionalOwnership(ticker: string, opts?: InstitutionalOwnershipOpts): Promise<InstitutionalOwnershipResponse>;
735
- getInvestorPortfolio(opts: InvestorPortfolioOpts): Promise<InvestorPortfolioResponse>;
736
- getGlobalInterestRates(opts?: GlobalInterestRatesOpts): Promise<GlobalInterestRatesResponse>;
737
- getAnalystEstimates(ticker: string): Promise<AnalystEstimatesResponse>;
738
- getCompanyFacts(ticker: string): Promise<{
743
+ getIncomeStatements(ticker: string, opts: FinancialOpts): Promise<ApiResponse<IncomeStatementsResponse>>;
744
+ getBalanceSheets(ticker: string, opts: FinancialOpts): Promise<ApiResponse<BalanceSheetsResponse>>;
745
+ getCashFlowStatements(ticker: string, opts: FinancialOpts): Promise<ApiResponse<CashFlowStatementsResponse>>;
746
+ getFinancials(ticker: string, opts: FinancialOpts): Promise<ApiResponse<FinancialsResponse>>;
747
+ getFinancialMetrics(ticker: string, opts?: FinancialMetricsOpts): Promise<ApiResponse<FinancialMetricsResponse>>;
748
+ getFinancialMetricsSnapshot(ticker: string, opts?: Pick<FinancialMetricsOpts, "period">): Promise<ApiResponse<FinancialMetricsSnapshotResponse>>;
749
+ getEarnings(ticker: string, opts?: EarningsOpts): Promise<ApiResponse<EarningsResponse>>;
750
+ getGeneralOverview(ticker: string, opts?: GeneralOverviewOpts): Promise<ApiResponse<GeneralOverviewResponse>>;
751
+ getKeyStatistics(ticker: string): Promise<ApiResponse<KeyStatisticsResponse>>;
752
+ getAvailableTickers(): Promise<ApiResponse<AvailableTickersResponse>>;
753
+ getPrices(ticker: string, opts: PriceOpts): Promise<ApiResponse<PriceHistoryResponse>>;
754
+ getPriceSnapshot(ticker: string): Promise<ApiResponse<PriceSnapshotResponse>>;
755
+ getNews(ticker: string, opts?: NewsOpts): Promise<ApiResponse<NewsResponse>>;
756
+ getInsiderTrades(ticker: string, opts?: InsiderTradesOpts): Promise<ApiResponse<InsiderTradesResponse>>;
757
+ getInstitutionalOwnership(ticker: string, opts?: InstitutionalOwnershipOpts): Promise<ApiResponse<InstitutionalOwnershipResponse>>;
758
+ getInvestorPortfolio(opts: InvestorPortfolioOpts): Promise<ApiResponse<InvestorPortfolioResponse>>;
759
+ getGlobalInterestRates(opts?: GlobalInterestRatesOpts): Promise<ApiResponse<GlobalInterestRatesResponse>>;
760
+ getAnalystEstimates(ticker: string): Promise<ApiResponse<AnalystEstimatesResponse>>;
761
+ getCompanyFacts(ticker: string): Promise<ApiResponse<{
739
762
  company_facts: CompanyFacts;
740
- }>;
741
- getSegmentedRevenues(ticker: string, opts: SegmentedRevenuesOpts): Promise<SegmentedRevenuesResponse>;
742
- getQuarterlyHighlights(ticker: string, opts?: QuarterlyHighlightsOpts): Promise<QuarterlyHighlightsResponse>;
743
- getScreenerFields(): Promise<ScreenerFieldsResponse>;
744
- screen(request: ScreenerRequest): Promise<ScreenerResponse>;
763
+ }>>;
764
+ getSegmentedRevenues(ticker: string, opts: SegmentedRevenuesOpts): Promise<ApiResponse<SegmentedRevenuesResponse>>;
765
+ getQuarterlyHighlights(ticker: string, opts?: QuarterlyHighlightsOpts): Promise<ApiResponse<QuarterlyHighlightsResponse>>;
766
+ getScreenerFields(): Promise<ApiResponse<ScreenerFieldsResponse>>;
767
+ screen(request: ScreenerRequest): Promise<ApiResponse<ScreenerResponse>>;
745
768
  }
746
769
 
747
- export { type AnalystEstimate, type AnalystEstimatesResponse, type AnalystPriceTarget, type AvailableTicker, type AvailableTickersResponse, type BalanceSheet, type BalanceSheetsResponse, type CashFlowStatement, type CashFlowStatementsResponse, type ClientOptions, type CompanyFacts, type ConsensusSurprise, DEFAULT_API_BASE_URL, type DataAvailability, type EarningsOpts, type EarningsResponse, type EarningsRow, type FinancialMetric, type FinancialMetricsOpts, type FinancialMetricsResponse, type FinancialMetricsSnapshotResponse, type FinancialOpts, type FinancialsResponse, type GeneralOverviewFinancials, type GeneralOverviewNews, type GeneralOverviewOpts, type GeneralOverviewPriceSnapshot, type GeneralOverviewResponse, type GlobalInterestRateObservation, type GlobalInterestRateSeries, type GlobalInterestRatesOpts, type GlobalInterestRatesResponse, type IncomeStatement, type IncomeStatementsResponse, type InsiderTrade, type InsiderTradesOpts, type InsiderTradesResponse, type InstitutionalOwnershipEntry, type InstitutionalOwnershipOpts, type InstitutionalOwnershipResponse, type InvestorPortfolioHolding, type InvestorPortfolioInvestor, type InvestorPortfolioOpts, type InvestorPortfolioResponse, type InvestorPortfolioSummary, type KeyStatistics, type KeyStatisticsResponse, type NewsArticle, type NewsOpts, type NewsResponse, type OptionalFinancialQueryOpts, type PriceBar, type PriceHistoryResponse, type PriceOpts, type PriceSnapshot, type PriceSnapshotResponse, type QuarterlyHighlightsMetric, type QuarterlyHighlightsOpts, type QuarterlyHighlightsPayload, type QuarterlyHighlightsResponse, type ReadMetadata, type ReportPeriodFilterOpts, type ScreenerFieldMetadata, type ScreenerFieldsResponse, type ScreenerRequest, type ScreenerResponse, type ScreenerResultRow, type SegmentedRevenueItem, type SegmentedRevenuePeriod, type SegmentedRevenuesOpts, type SegmentedRevenuesResponse, type SnakeCase, TnFinancialData };
770
+ export { type AnalystEstimate, type AnalystEstimatesResponse, type AnalystPriceTarget, ApiError, type ApiResponse, type AvailableTicker, type AvailableTickersResponse, type BalanceSheet, type BalanceSheetsResponse, type CashFlowStatement, type CashFlowStatementsResponse, type ClientOptions, type CompanyFacts, type ConsensusSurprise, DEFAULT_API_BASE_URL, type DataAvailability, type EarningsOpts, type EarningsResponse, type EarningsRow, type FinancialMetric, type FinancialMetricsOpts, type FinancialMetricsResponse, type FinancialMetricsSnapshotResponse, type FinancialOpts, type FinancialsResponse, type GeneralOverviewFinancials, type GeneralOverviewNews, type GeneralOverviewOpts, type GeneralOverviewPriceSnapshot, type GeneralOverviewResponse, type GlobalInterestRateObservation, type GlobalInterestRateSeries, type GlobalInterestRatesOpts, type GlobalInterestRatesResponse, type IncomeStatement, type IncomeStatementsResponse, type InsiderTrade, type InsiderTradesOpts, type InsiderTradesResponse, type InstitutionalOwnershipEntry, type InstitutionalOwnershipOpts, type InstitutionalOwnershipResponse, type InvestorPortfolioHolding, type InvestorPortfolioInvestor, type InvestorPortfolioOpts, type InvestorPortfolioResponse, type InvestorPortfolioSummary, type KeyStatistics, type KeyStatisticsResponse, type NewsArticle, type NewsOpts, type NewsResponse, type OptionalFinancialQueryOpts, type PriceBar, type PriceHistoryResponse, type PriceOpts, type PriceSnapshot, type PriceSnapshotResponse, type QuarterlyHighlightsMetric, type QuarterlyHighlightsOpts, type QuarterlyHighlightsPayload, type QuarterlyHighlightsResponse, type RateLimitAnnotatedResponse, type RateLimitInfo, type ReadMetadata, type ReportPeriodFilterOpts, type ScreenerFieldMetadata, type ScreenerFieldsResponse, type ScreenerRequest, type ScreenerResponse, type ScreenerResultRow, type SegmentedRevenueItem, type SegmentedRevenuePeriod, type SegmentedRevenuesOpts, type SegmentedRevenuesResponse, type SnakeCase, TnFinancialData };
@@ -1,5 +1,42 @@
1
1
  // src/client/index.ts
2
2
  var DEFAULT_API_BASE_URL = "https://api.truenorth-financial.ai/v1";
3
+ function parseOptionalInt(value) {
4
+ if (!value) {
5
+ return null;
6
+ }
7
+ const parsed = Number.parseInt(value, 10);
8
+ return Number.isFinite(parsed) ? parsed : null;
9
+ }
10
+ function parseOptionalUnixTimestamp(value) {
11
+ const parsed = parseOptionalInt(value);
12
+ if (parsed === null) {
13
+ return null;
14
+ }
15
+ return new Date(parsed * 1e3).toISOString();
16
+ }
17
+ function parseRateLimitInfo(headers) {
18
+ const info = {
19
+ limit: parseOptionalInt(headers.get("X-RateLimit-Limit")),
20
+ remaining: parseOptionalInt(headers.get("X-RateLimit-Remaining")),
21
+ resetAt: parseOptionalUnixTimestamp(headers.get("X-RateLimit-Reset")),
22
+ retryAfterSeconds: parseOptionalInt(headers.get("Retry-After"))
23
+ };
24
+ return Object.values(info).some((value) => value !== null) ? info : null;
25
+ }
26
+ var ApiError = class extends Error {
27
+ status;
28
+ code;
29
+ body;
30
+ rateLimit;
31
+ constructor(options) {
32
+ super(`API error ${options.status}: ${options.message}`);
33
+ this.name = "ApiError";
34
+ this.status = options.status;
35
+ this.code = options.code;
36
+ this.body = options.body;
37
+ this.rateLimit = options.rateLimit;
38
+ }
39
+ };
3
40
  var TnFinancialData = class {
4
41
  apiKey;
5
42
  baseUrl;
@@ -20,21 +57,44 @@ var TnFinancialData = class {
20
57
  headers
21
58
  });
22
59
  if (!res.ok) {
23
- const errorMessage = await this.extractErrorMessage(res);
24
- throw new Error(`API error ${res.status}: ${errorMessage}`);
60
+ const errorBody = await this.extractErrorBody(res);
61
+ throw new ApiError({
62
+ status: res.status,
63
+ code: errorBody.code,
64
+ message: errorBody.message,
65
+ body: errorBody.rawBody,
66
+ rateLimit: parseRateLimitInfo(res.headers)
67
+ });
25
68
  }
26
- return res.json();
69
+ const payload = await res.json();
70
+ Object.defineProperty(payload, "_rateLimit", {
71
+ value: parseRateLimitInfo(res.headers),
72
+ enumerable: false,
73
+ configurable: true,
74
+ writable: false
75
+ });
76
+ return payload;
27
77
  }
28
- async extractErrorMessage(res) {
78
+ async extractErrorBody(res) {
29
79
  const rawBody = await res.text().catch(() => "");
30
80
  const trimmed = rawBody.trim();
31
- if (trimmed.length === 0) return "Unknown error";
81
+ if (trimmed.length === 0) {
82
+ return { message: "Unknown error", code: null, rawBody: "" };
83
+ }
32
84
  try {
33
85
  const parsed = JSON.parse(trimmed);
34
86
  const code = parsed.code ? `[${parsed.code}] ` : "";
35
- return `${code}${parsed.error?.trim() || trimmed}`;
87
+ return {
88
+ message: `${code}${parsed.error?.trim() || trimmed}`,
89
+ code: parsed.code?.trim() || null,
90
+ rawBody: trimmed
91
+ };
36
92
  } catch {
37
- return trimmed;
93
+ return {
94
+ message: trimmed,
95
+ code: null,
96
+ rawBody: trimmed
97
+ };
38
98
  }
39
99
  }
40
100
  appendLimitParam(params, limit) {
@@ -223,6 +283,7 @@ var TnFinancialData = class {
223
283
  }
224
284
  };
225
285
  export {
286
+ ApiError,
226
287
  DEFAULT_API_BASE_URL,
227
288
  TnFinancialData
228
289
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tn-financial-data",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Hosted US equity financial data CLI and client for agents",
5
5
  "keywords": [
6
6
  "agent",
@@ -22,7 +22,7 @@ Covered reads include:
22
22
 
23
23
  - company facts and broad one-company overviews
24
24
  - financial statements, derived financial metrics, earnings, and analyst estimates
25
- - issuer-reported supplemental quarterly highlights for supported companies
25
+ - quarterly highlights, including issuer-reported release metrics when available and generic latest-quarter summaries otherwise
26
26
  - segment and geographic revenue mix
27
27
  - price history, latest price snapshot, and stored company news
28
28
  - insider trades, issuer-level institutional ownership, and investor `13F` portfolios
@@ -33,7 +33,7 @@ Covered reads include:
33
33
 
34
34
  - For a broad single-company prompt, start with `general-overview`.
35
35
  - For a narrow ask, prefer the narrower read instead of widening the problem too early.
36
- - For Apple product mix or Greater China, Meta ad KPIs, Amazon `AWS` operating income or segment-profit prompts, and Tesla services-and-other or free-cash-flow prompts, start with `quarterly-highlights` before `segmented-revenues`, `financials`, or `general-overview`.
36
+ - For Apple product mix or Greater China, Meta ad KPIs, Amazon `AWS` operating income or segment-profit prompts, and Tesla services-and-other or free-cash-flow prompts, start with `quarterly-highlights` before `segmented-revenues`, `financials`, or `general-overview`. For other covered issuers, `quarterly-highlights` now serves as a generic latest-quarter summary even when no issuer-specific release parser exists.
37
37
  - Use `segmented-revenues` when the user explicitly wants XBRL dimension tables, additive segment rows, or issuer coverage that is outside the narrower `quarterly-highlights` surface.
38
38
  - For covered-universe filter or ranking questions, use `screen-fields` and `screen`.
39
39
  - Keep issuer-centric ownership (`institutional-ownership`) separate from investor-centric portfolio lookup (`investor-portfolio`).
@@ -5,6 +5,7 @@ Use the CLI for agent access whenever possible.
5
5
  Auth rule:
6
6
  - the published package reads credentials from flags and ambient process env, not from project-local `.env`
7
7
  - if there is any doubt, pass `--api-key` and `--base-url` explicitly
8
+ - throttling surfaces as a clean CLI error; raw HTTP callers should read `X-RateLimit-*` and `Retry-After`
8
9
  - for broad ticker prompts like "what's up with NVDA?", start with `general-overview`
9
10
  - for covered recent-news prompts like "show me recent Nvidia news", start with `news`
10
11
  - for latest-quarter product mix, supplemental KPIs, AWS segment profitability, or Tesla free-cash-flow prompts, start with `quarterly-highlights`