edgar-cli 0.1.3 → 0.2.1
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 +3 -125
- package/bin/edgar-lib.cjs +118 -0
- package/bin/edgar.cjs +6 -0
- package/package.json +20 -34
- package/LICENSE +0 -21
- package/dist/cli.d.ts +0 -10
- package/dist/cli.js +0 -314
- package/dist/commands/facts.d.ts +0 -8
- package/dist/commands/facts.js +0 -125
- package/dist/commands/filings.d.ts +0 -14
- package/dist/commands/filings.js +0 -198
- package/dist/commands/research.d.ts +0 -28
- package/dist/commands/research.js +0 -623
- package/dist/commands/resolve.d.ts +0 -2
- package/dist/commands/resolve.js +0 -7
- package/dist/core/config.d.ts +0 -23
- package/dist/core/config.js +0 -51
- package/dist/core/envelope.d.ts +0 -28
- package/dist/core/envelope.js +0 -37
- package/dist/core/errors.d.ts +0 -18
- package/dist/core/errors.js +0 -37
- package/dist/core/output-shape.d.ts +0 -10
- package/dist/core/output-shape.js +0 -61
- package/dist/core/runtime.d.ts +0 -10
- package/dist/core/runtime.js +0 -1
- package/dist/sec/client.d.ts +0 -17
- package/dist/sec/client.js +0 -154
- package/dist/sec/endpoints.d.ts +0 -10
- package/dist/sec/endpoints.js +0 -19
- package/dist/sec/normalizers.d.ts +0 -5
- package/dist/sec/normalizers.js +0 -44
- package/dist/sec/ticker-map.d.ts +0 -16
- package/dist/sec/ticker-map.js +0 -57
package/README.md
CHANGED
|
@@ -1,133 +1,11 @@
|
|
|
1
1
|
# edgar-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Go-native SEC EDGAR CLI for `npx` workflows.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- `npx`-friendly Node/TypeScript package (no Python runtime needed)
|
|
8
|
-
- JSON envelope output by default for stable automation
|
|
9
|
-
- Strict SEC identity enforcement (`--user-agent` or `EDGAR_USER_AGENT`)
|
|
10
|
-
- Core commands:
|
|
11
|
-
- `resolve`
|
|
12
|
-
- `filings list`
|
|
13
|
-
- `filings get`
|
|
14
|
-
- `facts get`
|
|
15
|
-
- `research sync`
|
|
16
|
-
- `research ask`
|
|
17
|
-
|
|
18
|
-
## Install / Run
|
|
5
|
+
This package is a small Node launcher that dispatches to a prebuilt native Go binary for the current platform.
|
|
19
6
|
|
|
20
7
|
```bash
|
|
21
8
|
npx edgar-cli --help
|
|
22
9
|
```
|
|
23
10
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install
|
|
28
|
-
npm run build
|
|
29
|
-
node dist/cli.js --help
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## SEC Identity Requirement
|
|
33
|
-
|
|
34
|
-
SEC endpoints require declared automated access identity.
|
|
35
|
-
|
|
36
|
-
Use either:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
export EDGAR_USER_AGENT="Your Name your.email@example.com"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Or pass per command:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" resolve AAPL
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
If identity is missing, commands fail with `IDENTITY_REQUIRED`.
|
|
49
|
-
|
|
50
|
-
## Examples
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Resolve ticker -> canonical SEC identity mapping
|
|
54
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" resolve AAPL
|
|
55
|
-
|
|
56
|
-
# List recent 10-K filings
|
|
57
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" filings list --id AAPL --form 10-K --query-limit 5
|
|
58
|
-
|
|
59
|
-
# Get filing document URL by accession
|
|
60
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" filings get --id AAPL --accession 0000320193-26-000006 --format url
|
|
61
|
-
|
|
62
|
-
# Get filing converted to Markdown
|
|
63
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" filings get --id AAPL --accession 0000320193-26-000006 --format markdown
|
|
64
|
-
|
|
65
|
-
# Get concept data (latest per unit)
|
|
66
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" facts get --id AAPL --taxonomy us-gaap --concept Revenues --latest
|
|
67
|
-
|
|
68
|
-
# Query explicit local docs (repeat --doc or pass --manifest)
|
|
69
|
-
npx edgar-cli research ask "board resignation details" --doc ./cache/nvda-8k.md --top-k 5
|
|
70
|
-
|
|
71
|
-
# Build a deterministic cached corpus for a ticker/profile
|
|
72
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" research sync --id NVDA --profile core
|
|
73
|
-
|
|
74
|
-
# Query by ticker against cached corpus (auto-syncs on cache miss)
|
|
75
|
-
npx edgar-cli --user-agent "Your Name your.email@example.com" research ask "what changed on the board?" --id NVDA --profile core
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Research Profiles and Cache
|
|
79
|
-
|
|
80
|
-
`research sync` and `research ask --id` use deterministic filing profiles:
|
|
81
|
-
|
|
82
|
-
- `core`: latest 1x `10-K`, latest 3x `10-Q`, and recent `8-K` (last 180 days, up to 12)
|
|
83
|
-
- `events`: recent `8-K` (last 365 days, up to 24)
|
|
84
|
-
- `financials`: latest 2x `10-K` and latest 6x `10-Q`
|
|
85
|
-
|
|
86
|
-
By default, cached corpora are stored in:
|
|
87
|
-
|
|
88
|
-
- `$EDGAR_CACHE_DIR` (if set), else
|
|
89
|
-
- `$XDG_CACHE_HOME/edgar-cli` (if set), else
|
|
90
|
-
- `~/.cache/edgar-cli`
|
|
91
|
-
|
|
92
|
-
Override per command with `--cache-dir`.
|
|
93
|
-
|
|
94
|
-
## Output Contract (default)
|
|
95
|
-
|
|
96
|
-
All JSON-mode commands emit:
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"ok": true,
|
|
101
|
-
"command": "resolve",
|
|
102
|
-
"provider": "sec",
|
|
103
|
-
"data": {},
|
|
104
|
-
"error": null,
|
|
105
|
-
"meta": {
|
|
106
|
-
"timestamp": "2026-02-11T00:00:00Z",
|
|
107
|
-
"output_schema": "v1",
|
|
108
|
-
"view": "summary"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Compliance Notes
|
|
114
|
-
|
|
115
|
-
- This CLI targets SEC-hosted endpoints only in V0.
|
|
116
|
-
- Respect SEC fair-access guidance and use a valid identity in your user-agent.
|
|
117
|
-
|
|
118
|
-
References:
|
|
119
|
-
|
|
120
|
-
- [SEC Developer](https://www.sec.gov/developer)
|
|
121
|
-
- [SEC Webmaster FAQ: code support](https://www.sec.gov/os/webmaster-faq#code-support)
|
|
122
|
-
|
|
123
|
-
## Security
|
|
124
|
-
|
|
125
|
-
See [`SECURITY.md`](SECURITY.md) for vulnerability reporting guidance.
|
|
126
|
-
|
|
127
|
-
## Development
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
npm run typecheck
|
|
131
|
-
npm run test
|
|
132
|
-
npm run build
|
|
133
|
-
```
|
|
11
|
+
See the repository README for command examples and compliance notes.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const { spawnSync } = require("node:child_process");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const NATIVE_TARGETS = {
|
|
6
|
+
"darwin-arm64": {
|
|
7
|
+
packageName: "edgar-cli-darwin-arm64",
|
|
8
|
+
binaryRelPath: "bin/edgar"
|
|
9
|
+
},
|
|
10
|
+
"linux-x64": {
|
|
11
|
+
packageName: "edgar-cli-linux-x64",
|
|
12
|
+
binaryRelPath: "bin/edgar"
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function targetKey(platform, arch) {
|
|
17
|
+
return `${platform}-${arch}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveNativeBinary({
|
|
21
|
+
platform = process.platform,
|
|
22
|
+
arch = process.arch,
|
|
23
|
+
requireResolve = require.resolve,
|
|
24
|
+
exists = fs.existsSync,
|
|
25
|
+
baseDir = __dirname
|
|
26
|
+
} = {}) {
|
|
27
|
+
const key = targetKey(platform, arch);
|
|
28
|
+
const spec = NATIVE_TARGETS[key];
|
|
29
|
+
|
|
30
|
+
if (!spec) {
|
|
31
|
+
return {
|
|
32
|
+
supported: false,
|
|
33
|
+
key,
|
|
34
|
+
packageName: null,
|
|
35
|
+
binaryPath: null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const localNodeModulesBinary = path.resolve(
|
|
40
|
+
baseDir,
|
|
41
|
+
"..",
|
|
42
|
+
"node_modules",
|
|
43
|
+
spec.packageName,
|
|
44
|
+
spec.binaryRelPath
|
|
45
|
+
);
|
|
46
|
+
if (exists(localNodeModulesBinary)) {
|
|
47
|
+
return {
|
|
48
|
+
supported: true,
|
|
49
|
+
key,
|
|
50
|
+
packageName: spec.packageName,
|
|
51
|
+
binaryPath: localNodeModulesBinary
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const binaryPath = requireResolve(`${spec.packageName}/${spec.binaryRelPath}`);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
supported: true,
|
|
60
|
+
key,
|
|
61
|
+
packageName: spec.packageName,
|
|
62
|
+
binaryPath
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error && error.code === "MODULE_NOT_FOUND") {
|
|
66
|
+
return {
|
|
67
|
+
supported: true,
|
|
68
|
+
key,
|
|
69
|
+
packageName: spec.packageName,
|
|
70
|
+
binaryPath: null
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runEdgar({
|
|
78
|
+
argv,
|
|
79
|
+
env = process.env,
|
|
80
|
+
spawn = spawnSync,
|
|
81
|
+
stderr = process.stderr,
|
|
82
|
+
platform = process.platform,
|
|
83
|
+
arch = process.arch,
|
|
84
|
+
resolveNative = resolveNativeBinary
|
|
85
|
+
}) {
|
|
86
|
+
const native = resolveNative({ platform, arch });
|
|
87
|
+
|
|
88
|
+
if (!native.supported) {
|
|
89
|
+
stderr.write(`edgar-cli native runtime is not available for ${native.key}.\n`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!native.binaryPath) {
|
|
94
|
+
stderr.write(`edgar-cli native runtime package missing: ${native.packageName}\n`);
|
|
95
|
+
stderr.write("Reinstall with optional dependencies enabled, then retry `npx edgar-cli --help`.\n");
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = spawn(native.binaryPath, argv, { stdio: "inherit", env });
|
|
100
|
+
if (result.error) {
|
|
101
|
+
stderr.write(`edgar-cli native launcher failed: ${result.error.message}\n`);
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
if (typeof result.status === "number") {
|
|
105
|
+
return result.status;
|
|
106
|
+
}
|
|
107
|
+
if (result.signal) {
|
|
108
|
+
stderr.write(`edgar-cli native launcher terminated by signal ${result.signal}\n`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
NATIVE_TARGETS,
|
|
116
|
+
resolveNativeBinary,
|
|
117
|
+
runEdgar
|
|
118
|
+
};
|
package/bin/edgar.cjs
ADDED
package/package.json
CHANGED
|
@@ -1,63 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edgar-cli",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Go-native SEC EDGAR CLI for npx",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"type": "
|
|
6
|
+
"type": "commonjs",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"edgar-cli": "
|
|
12
|
-
"edgar": "
|
|
11
|
+
"edgar-cli": "bin/edgar.cjs",
|
|
12
|
+
"edgar": "bin/edgar.cjs"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"
|
|
16
|
-
"README.md"
|
|
17
|
-
"LICENSE"
|
|
15
|
+
"bin",
|
|
16
|
+
"README.md"
|
|
18
17
|
],
|
|
19
18
|
"engines": {
|
|
20
19
|
"node": ">=18"
|
|
21
20
|
},
|
|
22
21
|
"repository": {
|
|
23
22
|
"type": "git",
|
|
24
|
-
"url": "git+https://github.com/finlayi/edgar-cli.git"
|
|
23
|
+
"url": "git+https://github.com/finlayi/edgar-cli.git",
|
|
24
|
+
"directory": "npm"
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/finlayi/edgar-cli#readme",
|
|
27
27
|
"bugs": {
|
|
28
28
|
"url": "https://github.com/finlayi/edgar-cli/issues"
|
|
29
29
|
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "tsc -p tsconfig.json",
|
|
32
|
-
"dev": "tsx src/cli.ts",
|
|
33
|
-
"test": "vitest run",
|
|
34
|
-
"lint": "eslint . --ext .ts",
|
|
35
|
-
"typecheck": "tsc --noEmit"
|
|
36
|
-
},
|
|
37
30
|
"keywords": [
|
|
38
31
|
"edgar",
|
|
39
32
|
"sec",
|
|
40
33
|
"cli",
|
|
41
34
|
"npx",
|
|
42
|
-
"filings"
|
|
35
|
+
"filings",
|
|
36
|
+
"go",
|
|
37
|
+
"native"
|
|
43
38
|
],
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"p-limit": "^7.1.1",
|
|
48
|
-
"turndown": "^7.2.2",
|
|
49
|
-
"zod": "^4.1.5"
|
|
39
|
+
"optionalDependencies": {
|
|
40
|
+
"edgar-cli-darwin-arm64": "0.2.1",
|
|
41
|
+
"edgar-cli-linux-x64": "0.2.1"
|
|
50
42
|
},
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"eslint": "^8.57.1",
|
|
57
|
-
"nock": "^14.0.10",
|
|
58
|
-
"prettier": "^3.6.2",
|
|
59
|
-
"tsx": "^4.20.5",
|
|
60
|
-
"typescript": "^5.9.2",
|
|
61
|
-
"vitest": "^3.2.4"
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build:native": "node scripts/build-native.cjs",
|
|
45
|
+
"sync:versions": "node scripts/sync-platform-versions.cjs",
|
|
46
|
+
"test:versions": "node scripts/sync-platform-versions.cjs --check",
|
|
47
|
+
"test": "npm run test:versions && node --test test/*.test.cjs"
|
|
62
48
|
}
|
|
63
49
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Ian Finlay
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/dist/cli.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
interface CliIo {
|
|
4
|
-
stdout: (message: string) => void;
|
|
5
|
-
stderr: (message: string) => void;
|
|
6
|
-
env: NodeJS.ProcessEnv;
|
|
7
|
-
}
|
|
8
|
-
export declare function buildProgram(io: CliIo): Command;
|
|
9
|
-
export declare function runCli(argv: string[], io?: CliIo): Promise<number>;
|
|
10
|
-
export {};
|
package/dist/cli.js
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from 'node:fs';
|
|
3
|
-
import { Command, CommanderError } from 'commander';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { runFactsGet } from './commands/facts.js';
|
|
6
|
-
import { runFilingsGet, runFilingsList } from './commands/filings.js';
|
|
7
|
-
import { parseResearchProfile, runResearchAsk, runResearchAskById, runResearchSync } from './commands/research.js';
|
|
8
|
-
import { runResolve } from './commands/resolve.js';
|
|
9
|
-
import { buildRuntimeOptions, parseDateString, parseNonNegativeInt, parsePositiveInt, requireUserAgent } from './core/config.js';
|
|
10
|
-
import { failureEnvelope, successEnvelope } from './core/envelope.js';
|
|
11
|
-
import { CLIError, ErrorCode, EXIT_CODE_MAP, isCLIError } from './core/errors.js';
|
|
12
|
-
import { shapeData } from './core/output-shape.js';
|
|
13
|
-
import { SecClient } from './sec/client.js';
|
|
14
|
-
class CLIAbortError extends Error {
|
|
15
|
-
exitCode;
|
|
16
|
-
constructor(exitCode) {
|
|
17
|
-
super(`CLI exited with code ${exitCode}`);
|
|
18
|
-
this.exitCode = exitCode;
|
|
19
|
-
this.name = 'CLIAbortError';
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function defaultIo() {
|
|
23
|
-
return {
|
|
24
|
-
stdout: (message) => process.stdout.write(message),
|
|
25
|
-
stderr: (message) => process.stderr.write(message),
|
|
26
|
-
env: process.env
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function humanPrint(io, data) {
|
|
30
|
-
io.stdout(`${JSON.stringify(data, null, 2)}\n`);
|
|
31
|
-
}
|
|
32
|
-
function emitSuccess(params) {
|
|
33
|
-
const { context, io, command, result } = params;
|
|
34
|
-
if (context.runtime.humanMode) {
|
|
35
|
-
humanPrint(io, result.data);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const shaped = shapeData({
|
|
39
|
-
data: result.data,
|
|
40
|
-
fields: context.runtime.fields,
|
|
41
|
-
limit: context.runtime.limit
|
|
42
|
-
});
|
|
43
|
-
const metaUpdates = {
|
|
44
|
-
...(result.metaUpdates ?? {}),
|
|
45
|
-
...shaped.metaUpdates
|
|
46
|
-
};
|
|
47
|
-
const envelope = successEnvelope({
|
|
48
|
-
command,
|
|
49
|
-
data: shaped.data,
|
|
50
|
-
view: context.runtime.view,
|
|
51
|
-
metaUpdates
|
|
52
|
-
});
|
|
53
|
-
io.stdout(`${JSON.stringify(envelope)}\n`);
|
|
54
|
-
}
|
|
55
|
-
function emitError(params) {
|
|
56
|
-
const { command, err, runtimeView, humanMode, io } = params;
|
|
57
|
-
if (humanMode) {
|
|
58
|
-
io.stderr(`${err.code} ${err.message}\n`);
|
|
59
|
-
return err.exitCode;
|
|
60
|
-
}
|
|
61
|
-
const envelope = failureEnvelope({
|
|
62
|
-
command,
|
|
63
|
-
code: err.code,
|
|
64
|
-
message: err.message,
|
|
65
|
-
retriable: err.retriable,
|
|
66
|
-
view: runtimeView
|
|
67
|
-
});
|
|
68
|
-
io.stdout(`${JSON.stringify(envelope)}\n`);
|
|
69
|
-
return err.exitCode;
|
|
70
|
-
}
|
|
71
|
-
function toCliError(err) {
|
|
72
|
-
if (isCLIError(err)) {
|
|
73
|
-
return err;
|
|
74
|
-
}
|
|
75
|
-
return new CLIError(ErrorCode.INTERNAL_ERROR, err.message || 'Unexpected error');
|
|
76
|
-
}
|
|
77
|
-
async function executeCommand(command, commandObj, io, handler, options) {
|
|
78
|
-
const globalOptions = commandObj.optsWithGlobals();
|
|
79
|
-
const runtime = buildRuntimeOptions({
|
|
80
|
-
json: globalOptions.json,
|
|
81
|
-
human: globalOptions.human,
|
|
82
|
-
view: globalOptions.view,
|
|
83
|
-
fields: globalOptions.fields,
|
|
84
|
-
limit: globalOptions.limit,
|
|
85
|
-
verbose: globalOptions.verbose,
|
|
86
|
-
userAgent: globalOptions.userAgent
|
|
87
|
-
}, io.env);
|
|
88
|
-
try {
|
|
89
|
-
const requiresSecIdentity = options?.requiresSecIdentity ?? true;
|
|
90
|
-
const userAgent = requiresSecIdentity
|
|
91
|
-
? requireUserAgent(runtime.userAgent)
|
|
92
|
-
: runtime.userAgent ?? 'edgar-cli local research';
|
|
93
|
-
const secClient = new SecClient({
|
|
94
|
-
userAgent,
|
|
95
|
-
verbose: runtime.verbose,
|
|
96
|
-
logger: (message) => io.stderr(`[debug] ${message}\n`)
|
|
97
|
-
});
|
|
98
|
-
const context = {
|
|
99
|
-
runtime,
|
|
100
|
-
secClient
|
|
101
|
-
};
|
|
102
|
-
const result = await handler(context);
|
|
103
|
-
emitSuccess({ command, result, context, io });
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
const cliError = toCliError(error);
|
|
107
|
-
const exitCode = emitError({
|
|
108
|
-
command,
|
|
109
|
-
err: cliError,
|
|
110
|
-
runtimeView: runtime.view,
|
|
111
|
-
humanMode: runtime.humanMode,
|
|
112
|
-
io
|
|
113
|
-
});
|
|
114
|
-
throw new CLIAbortError(exitCode);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
export function buildProgram(io) {
|
|
118
|
-
const program = new Command();
|
|
119
|
-
program
|
|
120
|
-
.name('edgar')
|
|
121
|
-
.description('Agent-friendly SEC EDGAR CLI')
|
|
122
|
-
.option('--json', 'Emit JSON envelope output (default)')
|
|
123
|
-
.option('--human', 'Emit human-readable output')
|
|
124
|
-
.option('--view <view>', 'Output view mode (summary|full)', 'summary')
|
|
125
|
-
.option('--fields <fields>', 'Select specific response fields in JSON mode')
|
|
126
|
-
.option('--limit <n>', 'Limit output rows in JSON mode')
|
|
127
|
-
.option('--verbose', 'Enable verbose debug logs')
|
|
128
|
-
.option('--user-agent <value>', 'SEC identity (required for network commands), e.g. "Name email@domain.com"')
|
|
129
|
-
.showHelpAfterError(true)
|
|
130
|
-
.exitOverride()
|
|
131
|
-
.addHelpText('after', '\nSEC identity is required for network commands.\nSet --user-agent or EDGAR_USER_AGENT.')
|
|
132
|
-
.configureOutput({
|
|
133
|
-
writeOut: (message) => io.stdout(message),
|
|
134
|
-
writeErr: (message) => io.stderr(message)
|
|
135
|
-
});
|
|
136
|
-
program
|
|
137
|
-
.command('resolve')
|
|
138
|
-
.description('Resolve ticker/CIK to canonical SEC identity fields')
|
|
139
|
-
.argument('<id>', 'Ticker (AAPL) or CIK (320193 / 0000320193)')
|
|
140
|
-
.action(async function actionResolve(id) {
|
|
141
|
-
await executeCommand('resolve', this, io, async (context) => runResolve(id, context));
|
|
142
|
-
});
|
|
143
|
-
const filings = program.command('filings').description('Query filing metadata and filing documents');
|
|
144
|
-
filings
|
|
145
|
-
.command('list')
|
|
146
|
-
.requiredOption('--id <id>', 'Ticker or CIK')
|
|
147
|
-
.option('--form <form>', 'SEC form type, e.g. 10-K')
|
|
148
|
-
.option('--from <yyyy-mm-dd>', 'Lower filing-date bound')
|
|
149
|
-
.option('--to <yyyy-mm-dd>', 'Upper filing-date bound')
|
|
150
|
-
.option('--query-limit <n>', 'Limit rows before envelope shaping')
|
|
151
|
-
.option('--offset <n>', 'Offset rows before query-limit slicing', '0')
|
|
152
|
-
.action(async function actionFilingsList(options) {
|
|
153
|
-
const from = options.from ? parseDateString(options.from, '--from') : undefined;
|
|
154
|
-
const to = options.to ? parseDateString(options.to, '--to') : undefined;
|
|
155
|
-
const queryLimit = options.queryLimit === undefined
|
|
156
|
-
? undefined
|
|
157
|
-
: parsePositiveInt(options.queryLimit, '--query-limit');
|
|
158
|
-
const offset = parseNonNegativeInt(options.offset, '--offset');
|
|
159
|
-
await executeCommand('filings list', this, io, async (context) => runFilingsList({
|
|
160
|
-
id: options.id,
|
|
161
|
-
form: options.form,
|
|
162
|
-
from,
|
|
163
|
-
to,
|
|
164
|
-
queryLimit,
|
|
165
|
-
offset
|
|
166
|
-
}, context));
|
|
167
|
-
});
|
|
168
|
-
filings
|
|
169
|
-
.command('get')
|
|
170
|
-
.requiredOption('--id <id>', 'Ticker or CIK')
|
|
171
|
-
.requiredOption('--accession <accession>', 'Accession number: XXXXXXXXXX-XX-XXXXXX')
|
|
172
|
-
.option('--format <format>', 'url|html|text|markdown', 'url')
|
|
173
|
-
.action(async function actionFilingsGet(options) {
|
|
174
|
-
const format = options.format;
|
|
175
|
-
if (!['url', 'html', 'text', 'markdown'].includes(format)) {
|
|
176
|
-
throw new CLIAbortError(emitError({
|
|
177
|
-
command: 'filings get',
|
|
178
|
-
err: new CLIError(ErrorCode.VALIDATION_ERROR, '--format must be one of url|html|text|markdown'),
|
|
179
|
-
runtimeView: 'summary',
|
|
180
|
-
humanMode: false,
|
|
181
|
-
io
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
184
|
-
await executeCommand('filings get', this, io, async (context) => runFilingsGet({
|
|
185
|
-
id: options.id,
|
|
186
|
-
accession: options.accession,
|
|
187
|
-
format: format
|
|
188
|
-
}, context));
|
|
189
|
-
});
|
|
190
|
-
const facts = program.command('facts').description('Query SEC company facts (XBRL)');
|
|
191
|
-
facts
|
|
192
|
-
.command('get')
|
|
193
|
-
.requiredOption('--id <id>', 'Ticker or CIK')
|
|
194
|
-
.option('--taxonomy <taxonomy>', 'us-gaap|dei')
|
|
195
|
-
.option('--concept <concept>', 'Concept name, e.g. Revenues')
|
|
196
|
-
.option('--unit <unit>', 'Unit key, e.g. USD')
|
|
197
|
-
.option('--latest', 'Return only latest point per unit')
|
|
198
|
-
.action(async function actionFactsGet(options) {
|
|
199
|
-
const taxonomyValue = options.taxonomy;
|
|
200
|
-
if (taxonomyValue && !['us-gaap', 'dei'].includes(taxonomyValue)) {
|
|
201
|
-
throw new CLIAbortError(emitError({
|
|
202
|
-
command: 'facts get',
|
|
203
|
-
err: new CLIError(ErrorCode.VALIDATION_ERROR, '--taxonomy must be us-gaap or dei'),
|
|
204
|
-
runtimeView: 'summary',
|
|
205
|
-
humanMode: false,
|
|
206
|
-
io
|
|
207
|
-
}));
|
|
208
|
-
}
|
|
209
|
-
await executeCommand('facts get', this, io, async (context) => runFactsGet({
|
|
210
|
-
id: options.id,
|
|
211
|
-
taxonomy: taxonomyValue,
|
|
212
|
-
concept: options.concept,
|
|
213
|
-
unit: options.unit,
|
|
214
|
-
latest: Boolean(options.latest)
|
|
215
|
-
}, context));
|
|
216
|
-
});
|
|
217
|
-
const research = program
|
|
218
|
-
.command('research')
|
|
219
|
-
.description('Run deterministic research workflows over explicit docs or cached filing profiles');
|
|
220
|
-
research
|
|
221
|
-
.command('sync')
|
|
222
|
-
.description('Cache a deterministic research corpus for a company/profile')
|
|
223
|
-
.requiredOption('--id <id>', 'Ticker or CIK')
|
|
224
|
-
.option('--profile <profile>', 'core|events|financials', 'core')
|
|
225
|
-
.option('--cache-dir <path>', 'Override cache directory')
|
|
226
|
-
.option('--refresh', 'Force refetch even when cached docs exist')
|
|
227
|
-
.action(async function actionResearchSync(options) {
|
|
228
|
-
const profile = parseResearchProfile(options.profile);
|
|
229
|
-
await executeCommand('research sync', this, io, async (context) => runResearchSync({
|
|
230
|
-
id: options.id,
|
|
231
|
-
profile,
|
|
232
|
-
cacheDir: options.cacheDir,
|
|
233
|
-
refresh: Boolean(options.refresh)
|
|
234
|
-
}, context), { requiresSecIdentity: true });
|
|
235
|
-
});
|
|
236
|
-
research
|
|
237
|
-
.command('ask')
|
|
238
|
-
.description('Query explicitly provided local docs, or a cached company profile corpus when --id is used')
|
|
239
|
-
.argument('<query>', 'Natural language query')
|
|
240
|
-
.option('--id <id>', 'Ticker or CIK for cached/profile-based research')
|
|
241
|
-
.option('--profile <profile>', 'core|events|financials (used with --id)', 'core')
|
|
242
|
-
.option('--cache-dir <path>', 'Override cache directory')
|
|
243
|
-
.option('--refresh', 'With --id, force refetch of filings before querying')
|
|
244
|
-
.option('--doc <path>', 'Path to a local document (repeatable)', collectValues, [])
|
|
245
|
-
.option('--manifest <path>', 'Path to JSON manifest: either ["doc1", ...] or {"docs": ["doc1", ...]}')
|
|
246
|
-
.option('--top-k <n>', 'Maximum number of chunks to return', '8')
|
|
247
|
-
.option('--chunk-lines <n>', 'Number of lines per retrieval chunk', '40')
|
|
248
|
-
.option('--chunk-overlap <n>', 'Line overlap between retrieval chunks', '10')
|
|
249
|
-
.action(async function actionResearchAsk(query, options) {
|
|
250
|
-
const topK = parsePositiveInt(options.topK, '--top-k');
|
|
251
|
-
const chunkLines = parsePositiveInt(options.chunkLines, '--chunk-lines');
|
|
252
|
-
const chunkOverlap = parseNonNegativeInt(options.chunkOverlap, '--chunk-overlap');
|
|
253
|
-
const requiresSecIdentity = Boolean(options.id);
|
|
254
|
-
const profile = parseResearchProfile(options.profile);
|
|
255
|
-
await executeCommand('research ask', this, io, async (context) => options.id
|
|
256
|
-
? runResearchAskById({
|
|
257
|
-
id: options.id,
|
|
258
|
-
query,
|
|
259
|
-
profile,
|
|
260
|
-
cacheDir: options.cacheDir,
|
|
261
|
-
refresh: Boolean(options.refresh),
|
|
262
|
-
topK,
|
|
263
|
-
chunkLines,
|
|
264
|
-
chunkOverlap
|
|
265
|
-
}, context)
|
|
266
|
-
: runResearchAsk({
|
|
267
|
-
query,
|
|
268
|
-
docs: options.doc ?? [],
|
|
269
|
-
manifestPath: options.manifest,
|
|
270
|
-
topK,
|
|
271
|
-
chunkLines,
|
|
272
|
-
chunkOverlap
|
|
273
|
-
}, context), { requiresSecIdentity });
|
|
274
|
-
});
|
|
275
|
-
return program;
|
|
276
|
-
}
|
|
277
|
-
function collectValues(value, previous) {
|
|
278
|
-
return [...previous, value];
|
|
279
|
-
}
|
|
280
|
-
export async function runCli(argv, io = defaultIo()) {
|
|
281
|
-
const program = buildProgram(io);
|
|
282
|
-
try {
|
|
283
|
-
await program.parseAsync(argv, { from: 'user' });
|
|
284
|
-
return 0;
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
if (error instanceof CLIAbortError) {
|
|
288
|
-
return error.exitCode;
|
|
289
|
-
}
|
|
290
|
-
if (error instanceof CommanderError) {
|
|
291
|
-
return error.exitCode;
|
|
292
|
-
}
|
|
293
|
-
const cliError = toCliError(error);
|
|
294
|
-
io.stderr(`${cliError.code} ${cliError.message}\n`);
|
|
295
|
-
return EXIT_CODE_MAP[cliError.code] ?? 10;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function isDirectExecution() {
|
|
299
|
-
const argvPath = process.argv[1];
|
|
300
|
-
if (!argvPath) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
try {
|
|
304
|
-
return realpathSync(argvPath) === realpathSync(fileURLToPath(import.meta.url));
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
if (isDirectExecution()) {
|
|
311
|
-
runCli(process.argv.slice(2)).then((exitCode) => {
|
|
312
|
-
process.exit(exitCode);
|
|
313
|
-
});
|
|
314
|
-
}
|