krx-cli 1.6.0 → 1.7.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 +1 -0
- package/SKILL.md +35 -1
- package/dist/cli.js +58 -20
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +48 -19
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,6 +180,7 @@ krx schema stock.stk_bydd_trd
|
|
|
180
180
|
| `--code <isuCd>` | 종목코드 필터 (ISU_CD) | - |
|
|
181
181
|
| `--sort <field>` | 결과 정렬 기준 필드 | - |
|
|
182
182
|
| `--asc` | 오름차순 정렬 (기본: 내림차순) | - |
|
|
183
|
+
| `--offset <n>` | 처음 N개 건너뛰기 (페이지네이션) | - |
|
|
183
184
|
| `--limit <n>` | 결과 개수 제한 | - |
|
|
184
185
|
| `--from <date>` | 기간 조회 시작일 (YYYYMMDD) | - |
|
|
185
186
|
| `--to <date>` | 기간 조회 종료일 (YYYYMMDD) | - |
|
package/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ install: npm install -g krx-cli
|
|
|
7
7
|
binary: krx
|
|
8
8
|
metadata:
|
|
9
9
|
author: kyo504
|
|
10
|
-
version: "1.
|
|
10
|
+
version: "1.7.0"
|
|
11
11
|
invariants:
|
|
12
12
|
- Always use YYYYMMDD format for --date (e.g., 20260310)
|
|
13
13
|
- Data is T-1 (previous trading day), available from 2010 onwards
|
|
@@ -182,6 +182,7 @@ krx schema index.kospi_dd_trd # Specific endpoint schema
|
|
|
182
182
|
--code <isuCd> Filter by stock code (ISU_CD)
|
|
183
183
|
--sort <field> Sort results by field name
|
|
184
184
|
--asc Sort ascending (default: descending)
|
|
185
|
+
--offset <n> Skip first N results (for pagination)
|
|
185
186
|
--limit <n> Limit number of results
|
|
186
187
|
--from <date> Start date for range query (YYYYMMDD)
|
|
187
188
|
--to <date> End date for range query (YYYYMMDD)
|
|
@@ -205,6 +206,39 @@ krx schema index.kospi_dd_trd # Specific endpoint schema
|
|
|
205
206
|
6 = Service not approved (category not activated)
|
|
206
207
|
```
|
|
207
208
|
|
|
209
|
+
## Handling Large Results
|
|
210
|
+
|
|
211
|
+
Full market listings (e.g., all KOSPI stocks) output 900+ rows with 15+ fields each. This can exceed context limits. Always narrow results using these strategies:
|
|
212
|
+
|
|
213
|
+
### Strategy 1: Select only needed fields (preferred)
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Instead of all fields, select only what's needed
|
|
217
|
+
krx stock list --date 20260310 --market kospi --fields ISU_NM,TDD_CLSPRC,FLUC_RT
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Strategy 2: Paginate with offset + limit
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Page 1: first 100 rows
|
|
224
|
+
krx stock list --date 20260310 --market kospi --limit 100
|
|
225
|
+
|
|
226
|
+
# Page 2: next 100 rows
|
|
227
|
+
krx stock list --date 20260310 --market kospi --offset 100 --limit 100
|
|
228
|
+
|
|
229
|
+
# Page 3: next 100 rows
|
|
230
|
+
krx stock list --date 20260310 --market kospi --offset 200 --limit 100
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Strategy 3: Filter to relevant subset
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Only stocks with >5% change
|
|
237
|
+
krx stock list --date 20260310 --market kospi --filter "FLUC_RT > 5"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
IMPORTANT: When the user asks for "all" data, prefer Strategy 1 (fields) first. If still too large, combine with Strategy 2 (pagination). Always tell the user the total count.
|
|
241
|
+
|
|
208
242
|
## Common Patterns
|
|
209
243
|
|
|
210
244
|
### Get KOSPI closing price for a specific date
|
package/dist/cli.js
CHANGED
|
@@ -48869,6 +48869,9 @@ function applyPipeline(data, options) {
|
|
|
48869
48869
|
if (options.sort) {
|
|
48870
48870
|
result = sortData(result, options.sort, options.direction ?? "desc");
|
|
48871
48871
|
}
|
|
48872
|
+
if (options.offset && options.offset > 0) {
|
|
48873
|
+
result = result.slice(options.offset);
|
|
48874
|
+
}
|
|
48872
48875
|
if (options.limit && options.limit > 0) {
|
|
48873
48876
|
result = limitData(result, options.limit);
|
|
48874
48877
|
}
|
|
@@ -48971,6 +48974,7 @@ async function executeCommand(options) {
|
|
|
48971
48974
|
filter: parentOpts.filter,
|
|
48972
48975
|
sort: parentOpts.sort,
|
|
48973
48976
|
direction: parentOpts.asc ? "asc" : "desc",
|
|
48977
|
+
offset: parentOpts.offset,
|
|
48974
48978
|
limit: parentOpts.limit
|
|
48975
48979
|
});
|
|
48976
48980
|
if (data.length === 0) {
|
|
@@ -60522,6 +60526,42 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
60522
60526
|
}
|
|
60523
60527
|
};
|
|
60524
60528
|
|
|
60529
|
+
// src/mcp/tools/result.ts
|
|
60530
|
+
var MAX_RESULT_BYTES = 9e5;
|
|
60531
|
+
function successResult(data) {
|
|
60532
|
+
const text = JSON.stringify(data, null, 2);
|
|
60533
|
+
if (Buffer.byteLength(text, "utf-8") <= MAX_RESULT_BYTES) {
|
|
60534
|
+
return { content: [{ type: "text", text }] };
|
|
60535
|
+
}
|
|
60536
|
+
let lo = 0;
|
|
60537
|
+
let hi = data.length;
|
|
60538
|
+
while (lo < hi) {
|
|
60539
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
60540
|
+
const candidate = JSON.stringify(data.slice(0, mid), null, 2);
|
|
60541
|
+
if (Buffer.byteLength(candidate, "utf-8") <= MAX_RESULT_BYTES - 200) {
|
|
60542
|
+
lo = mid;
|
|
60543
|
+
} else {
|
|
60544
|
+
hi = mid - 1;
|
|
60545
|
+
}
|
|
60546
|
+
}
|
|
60547
|
+
const truncated = data.slice(0, lo);
|
|
60548
|
+
const result = {
|
|
60549
|
+
data: truncated,
|
|
60550
|
+
_truncated: {
|
|
60551
|
+
total: data.length,
|
|
60552
|
+
returned: lo,
|
|
60553
|
+
message: `Result truncated: ${lo} of ${data.length} rows returned. To get remaining rows, use offset=${lo} with limit=${lo}. Or use 'fields' to reduce row size.`
|
|
60554
|
+
}
|
|
60555
|
+
};
|
|
60556
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
60557
|
+
}
|
|
60558
|
+
function errorResult(message) {
|
|
60559
|
+
return {
|
|
60560
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
60561
|
+
isError: true
|
|
60562
|
+
};
|
|
60563
|
+
}
|
|
60564
|
+
|
|
60525
60565
|
// src/mcp/tools/index.ts
|
|
60526
60566
|
function getEndpointShortName(path6) {
|
|
60527
60567
|
return path6.split("/").pop() ?? path6;
|
|
@@ -60541,7 +60581,8 @@ Notes:
|
|
|
60541
60581
|
- Date format: YYYYMMDD (e.g., 20260310)
|
|
60542
60582
|
- Data is T-1 (previous trading day)
|
|
60543
60583
|
- All response values are strings
|
|
60544
|
-
- Rate limit: 10,000 calls/day
|
|
60584
|
+
- Rate limit: 10,000 calls/day
|
|
60585
|
+
- IMPORTANT: Full market listings can exceed the 1MB result limit. Use 'fields' to select only needed columns, or 'limit'+'offset' for pagination. If the response contains '_truncated', follow its instructions to retrieve remaining data.`;
|
|
60545
60586
|
}
|
|
60546
60587
|
function buildInputSchema(endpoints) {
|
|
60547
60588
|
const shortNames = endpoints.map((e) => getEndpointShortName(e.path));
|
|
@@ -60559,6 +60600,7 @@ function buildInputSchema(endpoints) {
|
|
|
60559
60600
|
isuCd: external_exports.string().optional().describe("Stock/item code (ISU_CD) to filter a specific item"),
|
|
60560
60601
|
sort: external_exports.string().optional().describe("Sort results by this field name"),
|
|
60561
60602
|
sort_direction: external_exports.enum(["asc", "desc"]).optional().describe("Sort direction (default: desc)"),
|
|
60603
|
+
offset: external_exports.number().optional().describe("Skip first N results (use with limit for pagination)"),
|
|
60562
60604
|
limit: external_exports.number().optional().describe("Limit number of results returned"),
|
|
60563
60605
|
filter: external_exports.string().optional().describe(
|
|
60564
60606
|
'Filter expression (e.g. "FLUC_RT > 5", "MKT_NM == KOSPI"). Operators: >, <, >=, <=, ==, !='
|
|
@@ -60581,14 +60623,6 @@ function filterFields2(data, fields) {
|
|
|
60581
60623
|
return filtered;
|
|
60582
60624
|
});
|
|
60583
60625
|
}
|
|
60584
|
-
function errorResult(message) {
|
|
60585
|
-
return {
|
|
60586
|
-
content: [
|
|
60587
|
-
{ type: "text", text: JSON.stringify({ error: message }) }
|
|
60588
|
-
],
|
|
60589
|
-
isError: true
|
|
60590
|
-
};
|
|
60591
|
-
}
|
|
60592
60626
|
function createCategoryTool(categoryId) {
|
|
60593
60627
|
const endpoints = ENDPOINTS.filter((e) => e.category === categoryId);
|
|
60594
60628
|
return {
|
|
@@ -60645,22 +60679,20 @@ function createCategoryTool(categoryId) {
|
|
|
60645
60679
|
const filterExpr = args.filter;
|
|
60646
60680
|
const sortField2 = args.sort;
|
|
60647
60681
|
const sortDirection2 = args.sort_direction ?? "desc";
|
|
60682
|
+
const offsetN2 = args.offset;
|
|
60648
60683
|
const limitN2 = args.limit;
|
|
60649
60684
|
data2 = applyPipeline(data2, {
|
|
60650
60685
|
filter: filterExpr,
|
|
60651
60686
|
sort: sortField2,
|
|
60652
60687
|
direction: sortDirection2,
|
|
60688
|
+
offset: offsetN2,
|
|
60653
60689
|
limit: limitN2
|
|
60654
60690
|
});
|
|
60655
60691
|
const fields2 = args.fields;
|
|
60656
60692
|
if (fields2) {
|
|
60657
60693
|
data2 = filterFields2(data2, fields2);
|
|
60658
60694
|
}
|
|
60659
|
-
return
|
|
60660
|
-
content: [
|
|
60661
|
-
{ type: "text", text: JSON.stringify(data2, null, 2) }
|
|
60662
|
-
]
|
|
60663
|
-
};
|
|
60695
|
+
return successResult(data2);
|
|
60664
60696
|
}
|
|
60665
60697
|
const dateStr = args.date ?? getRecentTradingDate();
|
|
60666
60698
|
try {
|
|
@@ -60685,23 +60717,21 @@ function createCategoryTool(categoryId) {
|
|
|
60685
60717
|
const filterExpr2 = args.filter;
|
|
60686
60718
|
const sortField = args.sort;
|
|
60687
60719
|
const sortDirection = args.sort_direction ?? "desc";
|
|
60720
|
+
const offsetN = args.offset;
|
|
60688
60721
|
const limitN = args.limit;
|
|
60689
60722
|
let data = result.data;
|
|
60690
60723
|
data = applyPipeline(data, {
|
|
60691
60724
|
filter: filterExpr2,
|
|
60692
60725
|
sort: sortField,
|
|
60693
60726
|
direction: sortDirection,
|
|
60727
|
+
offset: offsetN,
|
|
60694
60728
|
limit: limitN
|
|
60695
60729
|
});
|
|
60696
60730
|
const fields = args.fields;
|
|
60697
60731
|
if (fields) {
|
|
60698
60732
|
data = filterFields2(data, fields);
|
|
60699
60733
|
}
|
|
60700
|
-
return
|
|
60701
|
-
content: [
|
|
60702
|
-
{ type: "text", text: JSON.stringify(data, null, 2) }
|
|
60703
|
-
]
|
|
60704
|
-
};
|
|
60734
|
+
return successResult(data);
|
|
60705
60735
|
}
|
|
60706
60736
|
};
|
|
60707
60737
|
}
|
|
@@ -61212,7 +61242,11 @@ function startHttpServer(options) {
|
|
|
61212
61242
|
sessionTimers.delete(sessionId);
|
|
61213
61243
|
}
|
|
61214
61244
|
}
|
|
61245
|
+
const originalWarn = console.warn;
|
|
61246
|
+
console.warn = () => {
|
|
61247
|
+
};
|
|
61215
61248
|
const app = createMcpExpressApp({ host });
|
|
61249
|
+
console.warn = originalWarn;
|
|
61216
61250
|
app.get("/health", (_req, res) => {
|
|
61217
61251
|
res.json({
|
|
61218
61252
|
status: "ok",
|
|
@@ -61359,7 +61393,11 @@ function registerServeCommand(program3) {
|
|
|
61359
61393
|
|
|
61360
61394
|
// src/cli/index.ts
|
|
61361
61395
|
var program2 = new Command();
|
|
61362
|
-
program2.name("krx").description("Agent-native CLI for KRX (Korea Exchange) Open API").version(getVersion()).option("-o, --output <format>", "output format: json, table, ndjson, csv").option("-f, --fields <fields>", "comma-separated fields to include").option("--dry-run", "show request without calling API").option("-v, --verbose", "verbose output to stderr").option("--code <isuCd>", "filter by stock code (ISU_CD)").option("--sort <field>", "sort results by field name").option("--asc", "sort ascending (default: descending)").option(
|
|
61396
|
+
program2.name("krx").description("Agent-native CLI for KRX (Korea Exchange) Open API").version(getVersion()).option("-o, --output <format>", "output format: json, table, ndjson, csv").option("-f, --fields <fields>", "comma-separated fields to include").option("--dry-run", "show request without calling API").option("-v, --verbose", "verbose output to stderr").option("--code <isuCd>", "filter by stock code (ISU_CD)").option("--sort <field>", "sort results by field name").option("--asc", "sort ascending (default: descending)").option(
|
|
61397
|
+
"--offset <n>",
|
|
61398
|
+
"skip first N results (for pagination)",
|
|
61399
|
+
(v) => Number(v)
|
|
61400
|
+
).option("--limit <n>", "limit number of results", parseInt).option("--no-cache", "bypass cache and fetch fresh data").option("--from <date>", "start date for range query (YYYYMMDD)").option("--to <date>", "end date for range query (YYYYMMDD)").option("--filter <expression>", 'filter results (e.g. "FLUC_RT > 5")').option("--save <path>", "save output to file instead of stdout").option(
|
|
61363
61401
|
"--retries <n>",
|
|
61364
61402
|
"max retries on network error (default: 3)",
|
|
61365
61403
|
parseInt
|