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 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.6.0"
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("--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(
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