edgar-cli 0.1.4 → 0.2.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.
@@ -1,23 +0,0 @@
1
- export interface RuntimeOptions {
2
- jsonMode: boolean;
3
- humanMode: boolean;
4
- view: 'summary' | 'full';
5
- fields?: string[];
6
- limit?: number;
7
- verbose: boolean;
8
- userAgent?: string;
9
- }
10
- export interface RuntimeInput {
11
- json?: boolean;
12
- human?: boolean;
13
- view?: string;
14
- fields?: string;
15
- limit?: string | number;
16
- verbose?: boolean;
17
- userAgent?: string;
18
- }
19
- export declare function buildRuntimeOptions(input: RuntimeInput, env: NodeJS.ProcessEnv): RuntimeOptions;
20
- export declare function requireUserAgent(userAgent: string | undefined): string;
21
- export declare function parsePositiveInt(value: string, argName: string): number;
22
- export declare function parseNonNegativeInt(value: string, argName: string): number;
23
- export declare function parseDateString(value: string, argName: string): string;
@@ -1,51 +0,0 @@
1
- import { z } from 'zod';
2
- import { CLIError, ErrorCode } from './errors.js';
3
- import { parseFields } from './output-shape.js';
4
- const positiveIntSchema = z.coerce.number().int().min(1);
5
- const nonNegativeIntSchema = z.coerce.number().int().min(0);
6
- const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
7
- export function buildRuntimeOptions(input, env) {
8
- const humanMode = Boolean(input.human);
9
- const jsonMode = !humanMode;
10
- const view = input.view === 'full' ? 'full' : 'summary';
11
- const parsedFields = parseFields(input.fields);
12
- const parsedLimit = input.limit === undefined || input.limit === null
13
- ? undefined
14
- : parsePositiveInt(String(input.limit), '--limit');
15
- return {
16
- jsonMode,
17
- humanMode,
18
- view,
19
- fields: parsedFields,
20
- limit: parsedLimit,
21
- verbose: Boolean(input.verbose),
22
- userAgent: input.userAgent?.trim() || env.EDGAR_USER_AGENT?.trim() || undefined
23
- };
24
- }
25
- export function requireUserAgent(userAgent) {
26
- if (userAgent && userAgent.trim().length > 0) {
27
- return userAgent.trim();
28
- }
29
- throw new CLIError(ErrorCode.IDENTITY_REQUIRED, 'Missing SEC identity. Set --user-agent "Name email@domain.com" or EDGAR_USER_AGENT.');
30
- }
31
- export function parsePositiveInt(value, argName) {
32
- const parsed = positiveIntSchema.safeParse(value);
33
- if (!parsed.success) {
34
- throw new CLIError(ErrorCode.VALIDATION_ERROR, `${argName} must be a positive integer`);
35
- }
36
- return parsed.data;
37
- }
38
- export function parseNonNegativeInt(value, argName) {
39
- const parsed = nonNegativeIntSchema.safeParse(value);
40
- if (!parsed.success) {
41
- throw new CLIError(ErrorCode.VALIDATION_ERROR, `${argName} must be a non-negative integer`);
42
- }
43
- return parsed.data;
44
- }
45
- export function parseDateString(value, argName) {
46
- const parsed = dateSchema.safeParse(value);
47
- if (!parsed.success) {
48
- throw new CLIError(ErrorCode.VALIDATION_ERROR, `${argName} must use YYYY-MM-DD`);
49
- }
50
- return parsed.data;
51
- }
@@ -1,28 +0,0 @@
1
- import { ErrorCode } from './errors.js';
2
- export interface BrokerError {
3
- code: ErrorCode;
4
- message: string;
5
- retriable: boolean;
6
- }
7
- export interface OutputEnvelope {
8
- ok: boolean;
9
- command: string;
10
- provider: 'sec';
11
- data: unknown;
12
- error: BrokerError | null;
13
- meta: Record<string, unknown>;
14
- }
15
- export declare function successEnvelope(params: {
16
- command: string;
17
- data: unknown;
18
- view: string;
19
- metaUpdates?: Record<string, unknown>;
20
- }): OutputEnvelope;
21
- export declare function failureEnvelope(params: {
22
- command: string;
23
- code: ErrorCode;
24
- message: string;
25
- retriable?: boolean;
26
- view: string;
27
- metaUpdates?: Record<string, unknown>;
28
- }): OutputEnvelope;
@@ -1,37 +0,0 @@
1
- function nowIso() {
2
- return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
3
- }
4
- export function successEnvelope(params) {
5
- return {
6
- ok: true,
7
- command: params.command,
8
- provider: 'sec',
9
- data: params.data,
10
- error: null,
11
- meta: {
12
- timestamp: nowIso(),
13
- output_schema: 'v1',
14
- view: params.view,
15
- ...(params.metaUpdates ?? {})
16
- }
17
- };
18
- }
19
- export function failureEnvelope(params) {
20
- return {
21
- ok: false,
22
- command: params.command,
23
- provider: 'sec',
24
- data: null,
25
- error: {
26
- code: params.code,
27
- message: params.message,
28
- retriable: params.retriable ?? false
29
- },
30
- meta: {
31
- timestamp: nowIso(),
32
- output_schema: 'v1',
33
- view: params.view,
34
- ...(params.metaUpdates ?? {})
35
- }
36
- };
37
- }
@@ -1,18 +0,0 @@
1
- export declare enum ErrorCode {
2
- VALIDATION_ERROR = "VALIDATION_ERROR",
3
- DOCS_REQUIRED = "DOCS_REQUIRED",
4
- IDENTITY_REQUIRED = "IDENTITY_REQUIRED",
5
- RATE_LIMITED = "RATE_LIMITED",
6
- NOT_FOUND = "NOT_FOUND",
7
- NETWORK_ERROR = "NETWORK_ERROR",
8
- PARSE_ERROR = "PARSE_ERROR",
9
- INTERNAL_ERROR = "INTERNAL_ERROR"
10
- }
11
- export declare const EXIT_CODE_MAP: Record<ErrorCode, number>;
12
- export declare class CLIError extends Error {
13
- readonly code: ErrorCode;
14
- readonly retriable: boolean;
15
- constructor(code: ErrorCode, message: string, retriable?: boolean);
16
- get exitCode(): number;
17
- }
18
- export declare function isCLIError(err: unknown): err is CLIError;
@@ -1,37 +0,0 @@
1
- export var ErrorCode;
2
- (function (ErrorCode) {
3
- ErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
4
- ErrorCode["DOCS_REQUIRED"] = "DOCS_REQUIRED";
5
- ErrorCode["IDENTITY_REQUIRED"] = "IDENTITY_REQUIRED";
6
- ErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
7
- ErrorCode["NOT_FOUND"] = "NOT_FOUND";
8
- ErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
9
- ErrorCode["PARSE_ERROR"] = "PARSE_ERROR";
10
- ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
11
- })(ErrorCode || (ErrorCode = {}));
12
- export const EXIT_CODE_MAP = {
13
- [ErrorCode.VALIDATION_ERROR]: 2,
14
- [ErrorCode.DOCS_REQUIRED]: 2,
15
- [ErrorCode.IDENTITY_REQUIRED]: 3,
16
- [ErrorCode.RATE_LIMITED]: 4,
17
- [ErrorCode.NOT_FOUND]: 5,
18
- [ErrorCode.NETWORK_ERROR]: 6,
19
- [ErrorCode.PARSE_ERROR]: 7,
20
- [ErrorCode.INTERNAL_ERROR]: 10
21
- };
22
- export class CLIError extends Error {
23
- code;
24
- retriable;
25
- constructor(code, message, retriable = false) {
26
- super(message);
27
- this.code = code;
28
- this.retriable = retriable;
29
- this.name = 'CLIError';
30
- }
31
- get exitCode() {
32
- return EXIT_CODE_MAP[this.code] ?? 10;
33
- }
34
- }
35
- export function isCLIError(err) {
36
- return err instanceof CLIError;
37
- }
@@ -1,10 +0,0 @@
1
- export interface ShapedData {
2
- data: unknown;
3
- metaUpdates: Record<string, unknown>;
4
- }
5
- export declare function parseFields(raw: string | undefined): string[] | undefined;
6
- export declare function shapeData(params: {
7
- data: unknown;
8
- fields?: string[];
9
- limit?: number;
10
- }): ShapedData;
@@ -1,61 +0,0 @@
1
- import { CLIError, ErrorCode } from './errors.js';
2
- export function parseFields(raw) {
3
- if (!raw) {
4
- return undefined;
5
- }
6
- const fields = raw
7
- .split(',')
8
- .map((field) => field.trim())
9
- .filter((field) => field.length > 0);
10
- if (fields.length === 0) {
11
- throw new CLIError(ErrorCode.VALIDATION_ERROR, '--fields requires at least one field');
12
- }
13
- return [...new Set(fields)];
14
- }
15
- function projectObject(source, fields) {
16
- const projected = {};
17
- for (const field of fields) {
18
- projected[field] = source[field];
19
- }
20
- return projected;
21
- }
22
- function applyFields(data, fields) {
23
- if (!fields) {
24
- return data;
25
- }
26
- if (Array.isArray(data)) {
27
- if (data.length === 0) {
28
- return data;
29
- }
30
- if (!data.every((item) => item && typeof item === 'object' && !Array.isArray(item))) {
31
- throw new CLIError(ErrorCode.VALIDATION_ERROR, '--fields can only be applied to object results or lists of objects');
32
- }
33
- return data.map((item) => projectObject(item, fields));
34
- }
35
- if (data && typeof data === 'object' && !Array.isArray(data)) {
36
- return projectObject(data, fields);
37
- }
38
- throw new CLIError(ErrorCode.VALIDATION_ERROR, '--fields can only be applied to object results or lists of objects');
39
- }
40
- export function shapeData(params) {
41
- const fieldShaped = applyFields(params.data, params.fields);
42
- const metaUpdates = {};
43
- if (Array.isArray(fieldShaped) && typeof params.limit === 'number') {
44
- if (params.limit < 1) {
45
- throw new CLIError(ErrorCode.VALIDATION_ERROR, '--limit must be at least 1');
46
- }
47
- const totalCount = fieldShaped.length;
48
- const data = fieldShaped.slice(0, params.limit);
49
- metaUpdates.total_count = totalCount;
50
- metaUpdates.returned_count = data.length;
51
- metaUpdates.truncated = data.length < totalCount;
52
- return {
53
- data,
54
- metaUpdates
55
- };
56
- }
57
- return {
58
- data: fieldShaped,
59
- metaUpdates
60
- };
61
- }
@@ -1,10 +0,0 @@
1
- import { RuntimeOptions } from './config.js';
2
- import { SecClient } from '../sec/client.js';
3
- export interface CommandContext {
4
- runtime: RuntimeOptions;
5
- secClient: SecClient;
6
- }
7
- export interface CommandResult {
8
- data: unknown;
9
- metaUpdates?: Record<string, unknown>;
10
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +0,0 @@
1
- export interface SecClientOptions {
2
- userAgent: string;
3
- verbose?: boolean;
4
- fetchImpl?: typeof fetch;
5
- logger?: (message: string) => void;
6
- }
7
- export declare class SecClient {
8
- private readonly userAgent;
9
- private readonly verbose;
10
- private readonly fetchImpl;
11
- private readonly logger;
12
- constructor(options: SecClientOptions);
13
- fetchSecJson<T>(url: string): Promise<T>;
14
- fetchSecText(url: string): Promise<string>;
15
- private log;
16
- private request;
17
- }
@@ -1,154 +0,0 @@
1
- import pLimit from 'p-limit';
2
- import { CLIError, ErrorCode } from '../core/errors.js';
3
- const REQUEST_INTERVAL_MS = 125;
4
- const MAX_ATTEMPTS = 4;
5
- const paceGate = pLimit(1);
6
- let nextAllowedAt = 0;
7
- const sleep = async (ms) => {
8
- if (ms <= 0) {
9
- return;
10
- }
11
- await new Promise((resolve) => setTimeout(resolve, ms));
12
- };
13
- const jitter = () => Math.floor(Math.random() * 120);
14
- function retryAfterMs(headerValue) {
15
- if (!headerValue) {
16
- return null;
17
- }
18
- const seconds = Number.parseInt(headerValue, 10);
19
- if (!Number.isNaN(seconds)) {
20
- return Math.max(0, seconds * 1000);
21
- }
22
- const retryAt = Date.parse(headerValue);
23
- if (Number.isNaN(retryAt)) {
24
- return null;
25
- }
26
- return Math.max(0, retryAt - Date.now());
27
- }
28
- async function paceRequests() {
29
- await paceGate(async () => {
30
- const now = Date.now();
31
- const delay = Math.max(0, nextAllowedAt - now);
32
- if (delay > 0) {
33
- await sleep(delay);
34
- }
35
- nextAllowedAt = Math.max(nextAllowedAt, Date.now()) + REQUEST_INTERVAL_MS;
36
- });
37
- }
38
- function isUndeclaredAutomationBody(value) {
39
- const lowered = value.toLowerCase();
40
- return (lowered.includes('undeclared automated tool') ||
41
- lowered.includes('please declare your traffic') ||
42
- lowered.includes('acceptable policy'));
43
- }
44
- function isRetriableNetworkError(err) {
45
- return err instanceof TypeError;
46
- }
47
- function toNetworkError(url, message, retriable = false) {
48
- return new CLIError(ErrorCode.NETWORK_ERROR, `SEC request failed for ${url}: ${message}`, retriable);
49
- }
50
- function toRateLimitedError(url) {
51
- return new CLIError(ErrorCode.RATE_LIMITED, `SEC rate limit reached for ${url}`, true);
52
- }
53
- export class SecClient {
54
- userAgent;
55
- verbose;
56
- fetchImpl;
57
- logger;
58
- constructor(options) {
59
- this.userAgent = options.userAgent;
60
- this.verbose = options.verbose ?? false;
61
- this.fetchImpl = options.fetchImpl ?? fetch;
62
- this.logger = options.logger ?? (() => undefined);
63
- }
64
- async fetchSecJson(url) {
65
- const raw = await this.request(url, 'json');
66
- try {
67
- return JSON.parse(raw);
68
- }
69
- catch (error) {
70
- throw new CLIError(ErrorCode.PARSE_ERROR, `Unable to parse SEC JSON response from ${url}: ${error.message}`);
71
- }
72
- }
73
- async fetchSecText(url) {
74
- return this.request(url, 'text');
75
- }
76
- log(message) {
77
- if (!this.verbose) {
78
- return;
79
- }
80
- this.logger(message);
81
- }
82
- async request(url, kind) {
83
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
84
- try {
85
- await paceRequests();
86
- this.log(`GET ${url} (attempt ${attempt}/${MAX_ATTEMPTS})`);
87
- const response = await this.fetchImpl(url, {
88
- method: 'GET',
89
- headers: {
90
- 'User-Agent': this.userAgent,
91
- 'Accept-Encoding': 'identity'
92
- }
93
- });
94
- if (response.status === 403) {
95
- const body = await response.text();
96
- if (isUndeclaredAutomationBody(body)) {
97
- throw new CLIError(ErrorCode.IDENTITY_REQUIRED, 'SEC rejected request as undeclared automation. Use a valid --user-agent or EDGAR_USER_AGENT.');
98
- }
99
- throw toNetworkError(url, '403 Forbidden');
100
- }
101
- if (response.status === 404) {
102
- throw new CLIError(ErrorCode.NOT_FOUND, `SEC resource not found at ${url}`);
103
- }
104
- if (response.status === 429) {
105
- if (attempt < MAX_ATTEMPTS) {
106
- const headerDelay = retryAfterMs(response.headers.get('retry-after'));
107
- const delay = headerDelay ?? 250 * 2 ** (attempt - 1) + jitter();
108
- this.log(`429 received, waiting ${delay}ms before retry`);
109
- await sleep(delay);
110
- continue;
111
- }
112
- throw toRateLimitedError(url);
113
- }
114
- if (response.status === 503) {
115
- if (attempt < MAX_ATTEMPTS) {
116
- const headerDelay = retryAfterMs(response.headers.get('retry-after'));
117
- const delay = headerDelay ?? 250 * 2 ** (attempt - 1) + jitter();
118
- this.log(`503 received, waiting ${delay}ms before retry`);
119
- await sleep(delay);
120
- continue;
121
- }
122
- throw toNetworkError(url, '503 Service Unavailable', true);
123
- }
124
- if (!response.ok) {
125
- if (response.status >= 500 && attempt < MAX_ATTEMPTS) {
126
- const delay = 250 * 2 ** (attempt - 1) + jitter();
127
- this.log(`HTTP ${response.status}, waiting ${delay}ms before retry`);
128
- await sleep(delay);
129
- continue;
130
- }
131
- throw toNetworkError(url, `HTTP ${response.status}`);
132
- }
133
- const body = await response.text();
134
- if (kind === 'json' && body.trim().length === 0) {
135
- throw new CLIError(ErrorCode.PARSE_ERROR, `SEC returned empty JSON response from ${url}`);
136
- }
137
- return body;
138
- }
139
- catch (error) {
140
- if (error instanceof CLIError) {
141
- throw error;
142
- }
143
- if (attempt < MAX_ATTEMPTS && isRetriableNetworkError(error)) {
144
- const delay = 250 * 2 ** (attempt - 1) + jitter();
145
- this.log(`Transient network error, waiting ${delay}ms before retry`);
146
- await sleep(delay);
147
- continue;
148
- }
149
- throw toNetworkError(url, error.message, isRetriableNetworkError(error));
150
- }
151
- }
152
- throw toNetworkError(url, 'Request failed after retries');
153
- }
154
- }
@@ -1,10 +0,0 @@
1
- export declare const SEC_DATA_HOST = "https://data.sec.gov";
2
- export declare const SEC_WWW_HOST = "https://www.sec.gov";
3
- export declare function submissionsUrl(cik: string): string;
4
- export declare function companyFactsUrl(cik: string): string;
5
- export declare function tickerMapUrl(): string;
6
- export declare function filingDocumentUrl(params: {
7
- cik: string;
8
- accession: string;
9
- primaryDocument: string;
10
- }): string;
@@ -1,19 +0,0 @@
1
- import { normalizeAccession, normalizeCik } from './normalizers.js';
2
- export const SEC_DATA_HOST = 'https://data.sec.gov';
3
- export const SEC_WWW_HOST = 'https://www.sec.gov';
4
- export function submissionsUrl(cik) {
5
- const cik10 = normalizeCik(cik);
6
- return `${SEC_DATA_HOST}/submissions/CIK${cik10}.json`;
7
- }
8
- export function companyFactsUrl(cik) {
9
- const cik10 = normalizeCik(cik);
10
- return `${SEC_DATA_HOST}/api/xbrl/companyfacts/CIK${cik10}.json`;
11
- }
12
- export function tickerMapUrl() {
13
- return `${SEC_WWW_HOST}/files/company_tickers.json`;
14
- }
15
- export function filingDocumentUrl(params) {
16
- const cikNumeric = String(Number.parseInt(normalizeCik(params.cik), 10));
17
- const accessionNoDash = normalizeAccession(params.accession).replace(/-/g, '');
18
- return `${SEC_WWW_HOST}/Archives/edgar/data/${cikNumeric}/${accessionNoDash}/${params.primaryDocument}`;
19
- }
@@ -1,5 +0,0 @@
1
- export declare function isLikelyCik(value: string): boolean;
2
- export declare function normalizeCik(value: string): string;
3
- export declare function normalizeTicker(value: string): string;
4
- export declare function normalizeAccession(value: string): string;
5
- export declare function dateInRange(value: string, from?: string, to?: string): boolean;
@@ -1,44 +0,0 @@
1
- import { z } from 'zod';
2
- import { CLIError, ErrorCode } from '../core/errors.js';
3
- const cikSchema = z.string().regex(/^\d{1,10}$/);
4
- const tickerSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9.-]{0,14}$/);
5
- const accessionSchema = z.string().regex(/^\d{10}-\d{2}-\d{6}$/);
6
- export function isLikelyCik(value) {
7
- return /^\d+$/.test(value.trim());
8
- }
9
- export function normalizeCik(value) {
10
- const trimmed = value.trim();
11
- const parsed = cikSchema.safeParse(trimmed);
12
- if (!parsed.success) {
13
- throw new CLIError(ErrorCode.VALIDATION_ERROR, `Invalid CIK: ${value}`);
14
- }
15
- return parsed.data.padStart(10, '0');
16
- }
17
- export function normalizeTicker(value) {
18
- const trimmed = value.trim();
19
- const parsed = tickerSchema.safeParse(trimmed);
20
- if (!parsed.success) {
21
- throw new CLIError(ErrorCode.VALIDATION_ERROR, `Invalid ticker: ${value}`);
22
- }
23
- return parsed.data.toUpperCase();
24
- }
25
- export function normalizeAccession(value) {
26
- const trimmed = value.trim();
27
- const parsed = accessionSchema.safeParse(trimmed);
28
- if (!parsed.success) {
29
- throw new CLIError(ErrorCode.VALIDATION_ERROR, '--accession must match XXXXXXXXXX-XX-XXXXXX');
30
- }
31
- return parsed.data;
32
- }
33
- export function dateInRange(value, from, to) {
34
- if (!from && !to) {
35
- return true;
36
- }
37
- if (from && value < from) {
38
- return false;
39
- }
40
- if (to && value > to) {
41
- return false;
42
- }
43
- return true;
44
- }
@@ -1,16 +0,0 @@
1
- import { SecClient } from './client.js';
2
- export interface TickerRecord {
3
- cik_str: number;
4
- ticker: string;
5
- title: string;
6
- }
7
- export interface ResolvedEntity {
8
- input: string;
9
- cik: string;
10
- cik_numeric: number;
11
- ticker: string | null;
12
- title: string | null;
13
- }
14
- export declare function resolveEntity(id: string, client: SecClient, options?: {
15
- strictMapMatch?: boolean;
16
- }): Promise<ResolvedEntity>;
@@ -1,57 +0,0 @@
1
- import { CLIError, ErrorCode } from '../core/errors.js';
2
- import { tickerMapUrl } from './endpoints.js';
3
- import { isLikelyCik, normalizeCik, normalizeTicker } from './normalizers.js';
4
- let cachedMap = null;
5
- let cachedAtMs = 0;
6
- const CACHE_TTL_MS = 15 * 60 * 1000;
7
- async function getTickerMap(client) {
8
- const now = Date.now();
9
- if (cachedMap && now - cachedAtMs < CACHE_TTL_MS) {
10
- return cachedMap;
11
- }
12
- const payload = await client.fetchSecJson(tickerMapUrl());
13
- const records = Object.values(payload)
14
- .filter((record) => record && typeof record === 'object')
15
- .filter((record) => typeof record.cik_str === 'number' && typeof record.ticker === 'string');
16
- cachedMap = records;
17
- cachedAtMs = now;
18
- return records;
19
- }
20
- function findByTicker(records, ticker) {
21
- return records.find((record) => record.ticker.toUpperCase() === ticker);
22
- }
23
- function findByCik(records, cik10) {
24
- const cikNumeric = Number.parseInt(cik10, 10);
25
- return records.find((record) => record.cik_str === cikNumeric);
26
- }
27
- export async function resolveEntity(id, client, options) {
28
- const strictMapMatch = options?.strictMapMatch ?? false;
29
- const records = await getTickerMap(client);
30
- if (isLikelyCik(id)) {
31
- const cik = normalizeCik(id);
32
- const cikNumeric = Number.parseInt(cik, 10);
33
- const match = findByCik(records, cik);
34
- if (!match && strictMapMatch) {
35
- throw new CLIError(ErrorCode.NOT_FOUND, `No SEC ticker-map record found for CIK ${cik}`);
36
- }
37
- return {
38
- input: id,
39
- cik,
40
- cik_numeric: cikNumeric,
41
- ticker: match?.ticker ?? null,
42
- title: match?.title ?? null
43
- };
44
- }
45
- const ticker = normalizeTicker(id);
46
- const match = findByTicker(records, ticker);
47
- if (!match) {
48
- throw new CLIError(ErrorCode.NOT_FOUND, `No SEC ticker-map record found for ticker ${ticker}`);
49
- }
50
- return {
51
- input: id,
52
- cik: String(match.cik_str).padStart(10, '0'),
53
- cik_numeric: match.cik_str,
54
- ticker: match.ticker,
55
- title: match.title
56
- };
57
- }