From 70e3dd3cc56940219bee5e447cf684963854a67b Mon Sep 17 00:00:00 2001 From: Hana Date: Tue, 9 Jun 2026 05:44:42 +0000 Subject: [PATCH] Reduce output duplication and add install scripts --- README.md | 66 +++++++++++++++++++++++++++++---------- scripts/install.sh | 67 ++++++++++++++++++++++++++++++++++++++++ scripts/uninstall.sh | 16 ++++++++++ src/commands/account.rs | 2 +- src/commands/cache.rs | 4 +-- src/commands/dart.rs | 14 +++------ src/commands/market.rs | 4 +-- src/commands/order.rs | 4 +-- src/commands/search.rs | 2 +- src/commands/universe.rs | 17 +++------- 10 files changed, 149 insertions(+), 47 deletions(-) create mode 100755 scripts/install.sh create mode 100755 scripts/uninstall.sh diff --git a/README.md b/README.md index 69effef..6493762 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,40 @@ 이 프로그램은 투자 판단을 대신하지 않습니다. `order buy`, `order sell`은 실전 주문을 전송할 수 있으므로 실행 전 입력값과 계좌 상태를 반드시 확인해야 합니다. +## Install + +로컬 repo에서 release 바이너리를 빌드해 `~/.local/bin/openstock`에 설치합니다. + +```bash +./scripts/install.sh +``` + +원라인 설치: + +```bash +curl -fsSL https://git.hananakick.cc/Autotrade/openstock/raw/branch/main/scripts/install.sh | sh +``` + +원라인 설치 스크립트는 현재 디렉터리에 openstock source tree가 있으면 그대로 빌드하고, 없으면 main branch archive를 임시 디렉터리에 내려받아 빌드합니다. 설치 위치는 `OPENSTOCK_INSTALL_DIR`, 바이너리 이름은 `OPENSTOCK_BIN_NAME`, archive URL은 `OPENSTOCK_ARCHIVE_URL`로 바꿀 수 있습니다. + +```bash +OPENSTOCK_INSTALL_DIR=/usr/local/bin ./scripts/install.sh +``` + +삭제: + +```bash +./scripts/uninstall.sh +``` + +원라인 삭제: + +```bash +curl -fsSL https://git.hananakick.cc/Autotrade/openstock/raw/branch/main/scripts/uninstall.sh | sh +``` + +삭제 스크립트는 설치된 바이너리만 제거하고 `~/.config/openstock`의 설정/캐시는 보존합니다. + ## Architecture | Layer | Path | Role | @@ -28,7 +62,7 @@ "value": "실제 값" } ], - "raw": "원본 또는 상세 데이터" + "raw": "직접 API 호출 원본 또는 null" } ``` @@ -58,7 +92,7 @@ | `fields[].name` | string | 출력 필드 이름입니다. | | `fields[].description` | string | 해당 필드의 의미입니다. CLI에서 key 의미를 해석할 때 이 값을 우선 사용합니다. | | `fields[].value` | any | 실제 값입니다. 원본 API 숫자 문자열은 정밀도 보존을 위해 문자열일 수 있습니다. | -| `raw` | any/null | 원본 API 응답 또는 명령별 상세 payload입니다. 재처리/디버깅/AI 분석용입니다. | +| `raw` | any/null | 중복 출력을 줄이기 위해 typed command는 기본적으로 `null`입니다. `api call`처럼 원본 응답 자체가 목적인 명령만 원본 응답을 넣습니다. | 오류 출력의 `fields`는 다음 고정 키를 사용합니다. @@ -213,7 +247,7 @@ | `price` | object | KIS 현재가/호가/시세/투자주의 관련 값입니다. | | `company` | object | KIS 종목 및 기업 기본 정보입니다. | -`price`와 `company`는 KIS 원본 field name을 보존합니다. 자세한 원본은 `raw.price`, `raw.company`를 봅니다. +`price`와 `company`는 KIS field name을 보존한 조회 결과입니다. #### `market history` @@ -332,7 +366,7 @@ | `balance` | array | KIS 잔고/평가금/예수금 요약 목록입니다. | | `holdings` | array | 보유종목 상세 목록입니다. | -`balance`와 `holdings`는 KIS 원본 field name을 보존합니다. 전체 원본은 `raw`를 봅니다. +`balance`와 `holdings`는 KIS field name을 보존한 조회 결과입니다. #### `order buy`, `order sell` @@ -498,7 +532,7 @@ KIS endpoint를 직접 GET 호출합니다. `tr_id` 파라미터는 HTTP header | External IO | KIS endpoint GET | | File IO | `~/.config/openstock/.env` read | | Output fields | `broker`, `endpoint`, `params`, `request_semantics`, `response`, `response_semantics` | -| Raw | parsed KIS response | +| Raw | parsed KIS response. `api call`은 직접 호출 명령이므로 원본 응답을 `raw`에도 유지합니다. | | Side effect | endpoint에 따라 다름. typed command 사용 권장 | ### `openstock search ` @@ -511,7 +545,7 @@ Naver 모바일 주식 검색 API로 종목 후보를 검색합니다. | External IO | `GET https://m.stock.naver.com/front-api/search/autoComplete` | | File IO | 없음 | | Output fields | `provider`, `query`, `stocks` | -| Raw | Naver 원본 JSON | +| Raw | `null` | | Side effect | 없음 | `stocks` 항목은 가능한 경우 `code`, `name`, `market`, `market_code`, `nation_code`, `category`, `reuters_code`, `url`을 포함합니다. @@ -526,7 +560,7 @@ KIND 상장법인목록을 받아 stock universe cache를 갱신합니다. | External IO | `GET https://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13` | | File write | `~/.config/openstock/cache/universe/kind/latest.json`, `meta.json`, `YYYY-MM-DD.json` | | Output fields | `source`, `cache_date`, `refreshed_at`, `refreshed`, `stock_count`, `counts_by_market` | -| Raw | universe metadata | +| Raw | `null` | | Side effect | writes/updates local cache; prunes old snapshots | ### `openstock universe status` @@ -581,7 +615,7 @@ KIS 현재가와 종목/기업 기본 정보를 조회합니다. | Env read | `KIS_ACCESS_TOKEN`, `KIS_APPKEY`, `KIS_APPSECRET` | | External IO | KIS `GET /uapi/domestic-stock/v1/quotations/inquire-price`, TR ID `FHKST01010100`; KIS `GET /uapi/domestic-stock/v1/quotations/search-stock-info`, TR ID `CTPF1002R` | | Output fields | `broker`, `symbol`, `price`, `company` | -| Raw | KIS price/company 원본 응답 | +| Raw | `null` | | Side effect | 없음 | ### `openstock market history --from YYYYMMDD --to YYYYMMDD [--period D|W|M|Y] [--raw-price]` @@ -594,7 +628,7 @@ KIS 기간별 OHLCV를 조회합니다. | Env read | `KIS_ACCESS_TOKEN`, `KIS_APPKEY`, `KIS_APPSECRET` | | External IO | `GET /uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice`, TR ID `FHKST03010100` | | Output fields | `broker`, `symbol`, `period`, `date_range`, `adjusted`, `count`, `candles`, `summary` | -| Raw | normalized payload plus KIS raw response | +| Raw | `null` | | Side effect | 없음 | `candles`는 날짜 오름차순이며 각 항목은 `date`, `open`, `high`, `low`, `close`, `volume`, `trading_value`, `change`, `change_sign`, `change_rate`를 포함합니다. 숫자는 원본 정밀도 보존을 위해 문자열로 반환합니다. @@ -644,7 +678,7 @@ KRX 종목코드를 OpenDART 고유번호로 변환합니다. | File read/write | `symbol` 사용 시 corp code cache read; cache refresh 가능 | | External IO | `GET https://opendart.fss.or.kr/api/list.json` | | Output fields | `corp_code`, `from`, `to`, `page`, `resolved`, `disclosures` | -| Raw | OpenDART list response | +| Raw | `null` | | Side effect | cache refresh 가능 | `disclosures` 항목은 OpenDART가 반환하는 `corp_cls`, `corp_code`, `corp_name`, `stock_code`, `rcept_no`, `report_nm`, `rcept_dt`, `flr_nm`, `rm` 등을 포함합니다. @@ -660,7 +694,7 @@ KRX 종목코드를 OpenDART 고유번호로 변환합니다. | External IO | `GET https://opendart.fss.or.kr/api/document.xml` | | File read/write | `~/.config/openstock/cache/opendart/documents/{rcept_no}.zip` | | Output fields | `rcept_no`, `source`, `viewer_url`, `cached`, `zip_path`, `zip_bytes`, `files`, `text`, `text_chars`, `truncated` | -| Raw | same document object | +| Raw | `null` | | Side effect | writes ZIP cache; prunes old document ZIP files | `text`는 XML tag, style, script를 제거한 본문입니다. `--max-chars`를 넘으면 앞부분만 출력하고 `truncated=true`를 반환합니다. @@ -676,7 +710,7 @@ KRX 종목코드를 OpenDART 고유번호로 변환합니다. | File read/write | corp code cache, document ZIP cache | | External IO | OpenDART `list.json`; selected document cache miss or `--force`이면 `document.xml` | | Output fields | `symbol`, `resolved`, `date_range`, `selected_index`, `selected_disclosure`, `disclosures`, `document` | -| Raw | query, resolved corp, selected disclosure, list response, document object | +| Raw | `null` | | Side effect | cache refresh/write 가능 | ### `openstock account status` @@ -689,7 +723,7 @@ KIS 계좌 잔고와 보유종목을 조회합니다. | Env read | `KIS_ACCESS_TOKEN`, `KIS_APPKEY`, `KIS_APPSECRET`, `KIS_ACCOUNT` | | External IO | KIS `GET /uapi/domestic-stock/v1/trading/inquire-balance` | | Output fields | `broker`, `account`, `balance`, `holdings` | -| Raw | KIS balance response | +| Raw | `null` | | Side effect | 없음 | ### `openstock order buy --qty QTY (--price PRICE|--market)` @@ -702,7 +736,7 @@ KIS 계좌 잔고와 보유종목을 조회합니다. | Env read | `KIS_ACCESS_TOKEN`, `KIS_APPKEY`, `KIS_APPSECRET`, `KIS_ACCOUNT` | | External IO | KIS `POST /uapi/domestic-stock/v1/trading/order-cash`, TR ID `TTTC0802U` | | Output fields | `broker`, `side`, `symbol`, `qty`, `order_type`, `price`, `order` | -| Raw | KIS order response | +| Raw | `null` | | Side effect | financial_order: 실전 매수 주문 | ### `openstock order sell --qty QTY (--price PRICE|--market)` @@ -715,7 +749,7 @@ KIS 계좌 잔고와 보유종목을 조회합니다. | Env read | `KIS_ACCESS_TOKEN`, `KIS_APPKEY`, `KIS_APPSECRET`, `KIS_ACCOUNT` | | External IO | KIS `POST /uapi/domestic-stock/v1/trading/order-cash`, TR ID `TTTC0801U` | | Output fields | `broker`, `side`, `symbol`, `qty`, `order_type`, `price`, `order` | -| Raw | KIS order response | +| Raw | `null` | | Side effect | financial_order: 실전 매도 주문 | ### `openstock order status [order_no] [--from YYYYMMDD] [--to YYYYMMDD]` @@ -728,7 +762,7 @@ KIS 계좌 잔고와 보유종목을 조회합니다. | Env read | `KIS_ACCESS_TOKEN`, `KIS_APPKEY`, `KIS_APPSECRET`, `KIS_ACCOUNT` | | External IO | KIS `GET /uapi/domestic-stock/v1/trading/inquire-daily-ccld` | | Output fields | `broker`, `account`, `order_no`, `orders`, `summary` | -| Raw | KIS order status response | +| Raw | `null` | | Side effect | 없음 | ### `openstock cache status` diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..a83bf04 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env sh +set -eu + +INSTALL_DIR="${OPENSTOCK_INSTALL_DIR:-$HOME/.local/bin}" +BIN_NAME="${OPENSTOCK_BIN_NAME:-openstock}" +ARCHIVE_URL="${OPENSTOCK_ARCHIVE_URL:-https://git.hananakick.cc/Autotrade/openstock/archive/main.tar.gz}" +SOURCE_DIR="" +TMP_DIR="" + +cleanup() { + if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then + rm -rf "$TMP_DIR" + fi +} +trap cleanup EXIT + +is_openstock_source() { + [ -f "$1/Cargo.toml" ] && grep -q 'name = "openstock"' "$1/Cargo.toml" +} + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd || pwd)" + +if is_openstock_source "$PWD"; then + SOURCE_DIR="$PWD" +elif is_openstock_source "$SCRIPT_DIR/.."; then + SOURCE_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)" +else + if ! command -v curl >/dev/null 2>&1; then + echo "curl is required when installing outside an openstock source tree" >&2 + exit 1 + fi + if ! command -v tar >/dev/null 2>&1; then + echo "tar is required when installing outside an openstock source tree" >&2 + exit 1 + fi + + TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openstock-install.XXXXXX")" + curl -fsSL "$ARCHIVE_URL" | tar -xz -C "$TMP_DIR" + SOURCE_DIR="$(find "$TMP_DIR" -mindepth 1 -maxdepth 2 -name Cargo.toml -print | head -n 1 | xargs dirname)" + + if ! is_openstock_source "$SOURCE_DIR"; then + echo "failed to locate openstock source in downloaded archive" >&2 + exit 1 + fi +fi + +if ! command -v cargo >/dev/null 2>&1; then + echo "cargo is required to build openstock" >&2 + exit 1 +fi + +mkdir -p "$INSTALL_DIR" + +cd "$SOURCE_DIR" +cargo build --release +cp "$SOURCE_DIR/target/release/openstock" "$INSTALL_DIR/$BIN_NAME" +chmod 755 "$INSTALL_DIR/$BIN_NAME" + +echo "installed: $INSTALL_DIR/$BIN_NAME" +echo "config: ${OPENSTOCK_CONFIG_DIR:-$HOME/.config/openstock}" +case ":$PATH:" in + *":$INSTALL_DIR:"*) ;; + *) + echo "warning: $INSTALL_DIR is not in PATH" >&2 + echo "add this to your shell profile: export PATH=\"$INSTALL_DIR:\$PATH\"" >&2 + ;; +esac diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..138e59a --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh +set -eu + +INSTALL_DIR="${OPENSTOCK_INSTALL_DIR:-$HOME/.local/bin}" +BIN_NAME="${OPENSTOCK_BIN_NAME:-openstock}" +TARGET="$INSTALL_DIR/$BIN_NAME" + +if [ -e "$TARGET" ]; then + rm -f "$TARGET" + echo "removed: $TARGET" +else + echo "not installed: $TARGET" +fi + +echo "config and cache are preserved at: ${OPENSTOCK_CONFIG_DIR:-$HOME/.config/openstock}" +echo "remove them manually only if you no longer need credentials or cached data." diff --git a/src/commands/account.rs b/src/commands/account.rs index 707e940..142036a 100644 --- a/src/commands/account.rs +++ b/src/commands/account.rs @@ -54,7 +54,7 @@ pub fn handle_account(sub: &AccountCommands) { .unwrap_or_else(|| serde_json::json!([])), ), ], - value, + serde_json::Value::Null, ) ); } diff --git a/src/commands/cache.rs b/src/commands/cache.rs index 2ec1b77..9f5ea30 100644 --- a/src/commands/cache.rs +++ b/src/commands/cache.rs @@ -52,7 +52,7 @@ pub fn handle_cache(sub: &CacheCommands) { serde_json::json!(status.namespaces), ), ], - serde_json::json!(status), + serde_json::Value::Null, ) ), Err(err) => eprintln!( @@ -106,7 +106,7 @@ pub fn handle_cache(sub: &CacheCommands) { serde_json::json!(reports), ), ], - serde_json::json!(reports), + serde_json::Value::Null, ) ); } diff --git a/src/commands/dart.rs b/src/commands/dart.rs index fe4f909..c39da00 100644 --- a/src/commands/dart.rs +++ b/src/commands/dart.rs @@ -344,7 +344,7 @@ fn print_corp_snapshot( serde_json::json!(snapshot.meta.listed_count), ), ], - serde_json::json!(snapshot.meta), + serde_json::Value::Null, ) ); } @@ -505,7 +505,7 @@ fn print_disclosures( ), crate::core::output::field("disclosures", "조회된 공시 목록", list), ], - value, + serde_json::Value::Null, ) ); } @@ -568,7 +568,7 @@ fn print_document(value: document::DartDocument) { serde_json::json!(value.truncated), ), ], - serde_json::json!(value), + serde_json::Value::Null, ) ); } @@ -630,13 +630,7 @@ fn print_show( serde_json::json!(document), ), ], - serde_json::json!({ - "query": query, - "resolved": resolved, - "selected_disclosure": selected, - "disclosures": disclosures_value, - "document": document, - }), + serde_json::Value::Null, ) ); } diff --git a/src/commands/market.rs b/src/commands/market.rs index 4befcf0..34bdab2 100644 --- a/src/commands/market.rs +++ b/src/commands/market.rs @@ -100,7 +100,7 @@ fn handle_market_overview(symbol: &str) { .unwrap_or_else(|| serde_json::json!({})), ), ], - value, + serde_json::Value::Null, ) ); } @@ -194,7 +194,7 @@ fn handle_market_history(command: &MarketHistoryCommand) { .unwrap_or_else(|| serde_json::json!({})), ), ], - value, + serde_json::Value::Null, ) ); } diff --git a/src/commands/order.rs b/src/commands/order.rs index da9722e..64bfb1e 100644 --- a/src/commands/order.rs +++ b/src/commands/order.rs @@ -145,7 +145,7 @@ fn print_order_result(command: &str, description: &str, json: &str) { .unwrap_or_else(|| serde_json::json!({})), ), ], - value, + serde_json::Value::Null, ) ); } @@ -199,7 +199,7 @@ fn print_order_status_result(json: &str) { .unwrap_or_else(|| serde_json::json!({})), ), ], - value, + serde_json::Value::Null, ) ); } diff --git a/src/commands/search.rs b/src/commands/search.rs index 3e24854..fd8d981 100644 --- a/src/commands/search.rs +++ b/src/commands/search.rs @@ -35,7 +35,7 @@ pub fn handle_search(query: &str) { .unwrap_or_else(|| serde_json::json!([])), ), ], - value, + serde_json::Value::Null, ) ); } diff --git a/src/commands/universe.rs b/src/commands/universe.rs index 87068ad..691340f 100644 --- a/src/commands/universe.rs +++ b/src/commands/universe.rs @@ -171,10 +171,7 @@ pub fn handle_universe(sub: &UniverseCommands) { serde_json::json!(page), ), ], - serde_json::json!({ - "meta": snapshot.meta, - "stocks": page, - }), + serde_json::Value::Null, ) ); } @@ -237,10 +234,7 @@ pub fn handle_universe(sub: &UniverseCommands) { serde_json::json!(chunks), ), ], - serde_json::json!({ - "meta": snapshot.meta, - "chunks": chunks, - }), + serde_json::Value::Null, ) ); } @@ -296,10 +290,7 @@ pub fn handle_universe(sub: &UniverseCommands) { serde_json::json!(report.errors), ), ], - serde_json::json!({ - "meta": snapshot.meta, - "report": report, - }), + serde_json::Value::Null, ) ), Err(err) => eprintln!( @@ -362,7 +353,7 @@ fn print_snapshot(command: &str, description: &str, snapshot: &kind::universe::U serde_json::json!(snapshot.meta.counts_by_market), ), ], - serde_json::json!(snapshot.meta), + serde_json::Value::Null, ) ); }