krx-cli 1.6.0 → 1.7.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 +1 -0
- package/SKILL.md +35 -1
- package/dist/cli.js +59 -20
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +49 -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.1"
|
|
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 !== void 0 && 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 = 5e5;
|
|
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 - 2e3) {
|
|
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,9 @@ 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: When querying a specific stock, ALWAYS pass 'isuCd' to filter. Without it, all stocks in the market are returned.
|
|
60586
|
+
- Full market listings can exceed the result size 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
60587
|
}
|
|
60546
60588
|
function buildInputSchema(endpoints) {
|
|
60547
60589
|
const shortNames = endpoints.map((e) => getEndpointShortName(e.path));
|
|
@@ -60559,6 +60601,7 @@ function buildInputSchema(endpoints) {
|
|
|
60559
60601
|
isuCd: external_exports.string().optional().describe("Stock/item code (ISU_CD) to filter a specific item"),
|
|
60560
60602
|
sort: external_exports.string().optional().describe("Sort results by this field name"),
|
|
60561
60603
|
sort_direction: external_exports.enum(["asc", "desc"]).optional().describe("Sort direction (default: desc)"),
|
|
60604
|
+
offset: external_exports.number().optional().describe("Skip first N results (use with limit for pagination)"),
|
|
60562
60605
|
limit: external_exports.number().optional().describe("Limit number of results returned"),
|
|
60563
60606
|
filter: external_exports.string().optional().describe(
|
|
60564
60607
|
'Filter expression (e.g. "FLUC_RT > 5", "MKT_NM == KOSPI"). Operators: >, <, >=, <=, ==, !='
|
|
@@ -60581,14 +60624,6 @@ function filterFields2(data, fields) {
|
|
|
60581
60624
|
return filtered;
|
|
60582
60625
|
});
|
|
60583
60626
|
}
|
|
60584
|
-
function errorResult(message) {
|
|
60585
|
-
return {
|
|
60586
|
-
content: [
|
|
60587
|
-
{ type: "text", text: JSON.stringify({ error: message }) }
|
|
60588
|
-
],
|
|
60589
|
-
isError: true
|
|
60590
|
-
};
|
|
60591
|
-
}
|
|
60592
60627
|
function createCategoryTool(categoryId) {
|
|
60593
60628
|
const endpoints = ENDPOINTS.filter((e) => e.category === categoryId);
|
|
60594
60629
|
return {
|
|
@@ -60645,22 +60680,20 @@ function createCategoryTool(categoryId) {
|
|
|
60645
60680
|
const filterExpr = args.filter;
|
|
60646
60681
|
const sortField2 = args.sort;
|
|
60647
60682
|
const sortDirection2 = args.sort_direction ?? "desc";
|
|
60683
|
+
const offsetN2 = args.offset;
|
|
60648
60684
|
const limitN2 = args.limit;
|
|
60649
60685
|
data2 = applyPipeline(data2, {
|
|
60650
60686
|
filter: filterExpr,
|
|
60651
60687
|
sort: sortField2,
|
|
60652
60688
|
direction: sortDirection2,
|
|
60689
|
+
offset: offsetN2,
|
|
60653
60690
|
limit: limitN2
|
|
60654
60691
|
});
|
|
60655
60692
|
const fields2 = args.fields;
|
|
60656
60693
|
if (fields2) {
|
|
60657
60694
|
data2 = filterFields2(data2, fields2);
|
|
60658
60695
|
}
|
|
60659
|
-
return
|
|
60660
|
-
content: [
|
|
60661
|
-
{ type: "text", text: JSON.stringify(data2, null, 2) }
|
|
60662
|
-
]
|
|
60663
|
-
};
|
|
60696
|
+
return successResult(data2);
|
|
60664
60697
|
}
|
|
60665
60698
|
const dateStr = args.date ?? getRecentTradingDate();
|
|
60666
60699
|
try {
|
|
@@ -60685,23 +60718,21 @@ function createCategoryTool(categoryId) {
|
|
|
60685
60718
|
const filterExpr2 = args.filter;
|
|
60686
60719
|
const sortField = args.sort;
|
|
60687
60720
|
const sortDirection = args.sort_direction ?? "desc";
|
|
60721
|
+
const offsetN = args.offset;
|
|
60688
60722
|
const limitN = args.limit;
|
|
60689
60723
|
let data = result.data;
|
|
60690
60724
|
data = applyPipeline(data, {
|
|
60691
60725
|
filter: filterExpr2,
|
|
60692
60726
|
sort: sortField,
|
|
60693
60727
|
direction: sortDirection,
|
|
60728
|
+
offset: offsetN,
|
|
60694
60729
|
limit: limitN
|
|
60695
60730
|
});
|
|
60696
60731
|
const fields = args.fields;
|
|
60697
60732
|
if (fields) {
|
|
60698
60733
|
data = filterFields2(data, fields);
|
|
60699
60734
|
}
|
|
60700
|
-
return
|
|
60701
|
-
content: [
|
|
60702
|
-
{ type: "text", text: JSON.stringify(data, null, 2) }
|
|
60703
|
-
]
|
|
60704
|
-
};
|
|
60735
|
+
return successResult(data);
|
|
60705
60736
|
}
|
|
60706
60737
|
};
|
|
60707
60738
|
}
|
|
@@ -61212,7 +61243,11 @@ function startHttpServer(options) {
|
|
|
61212
61243
|
sessionTimers.delete(sessionId);
|
|
61213
61244
|
}
|
|
61214
61245
|
}
|
|
61246
|
+
const originalWarn = console.warn;
|
|
61247
|
+
console.warn = () => {
|
|
61248
|
+
};
|
|
61215
61249
|
const app = createMcpExpressApp({ host });
|
|
61250
|
+
console.warn = originalWarn;
|
|
61216
61251
|
app.get("/health", (_req, res) => {
|
|
61217
61252
|
res.json({
|
|
61218
61253
|
status: "ok",
|
|
@@ -61359,7 +61394,11 @@ function registerServeCommand(program3) {
|
|
|
61359
61394
|
|
|
61360
61395
|
// src/cli/index.ts
|
|
61361
61396
|
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(
|
|
61397
|
+
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(
|
|
61398
|
+
"--offset <n>",
|
|
61399
|
+
"skip first N results (for pagination)",
|
|
61400
|
+
(v) => Number(v)
|
|
61401
|
+
).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
61402
|
"--retries <n>",
|
|
61364
61403
|
"max retries on network error (default: 3)",
|
|
61365
61404
|
parseInt
|