# dictcli

## 🇰🇷 한국어

오프라인 SQLite 사전을 조회하는 CLI이며, REST API 서버(`serve`)와 MCP stdio 서버(`mcp`) 모드도 제공합니다.

### 기능

- SQLite DB 파일/디렉터리 기반 사전 검색
- 검색 명령:
  - `exact` (표제어 정확 일치)
  - `prefix` (표제어 접두 검색)
  - `meaning` (FTS5 의미/본문 검색)
- 실행 모드:
  - 로컬 CLI
  - HTTP REST API 서버
  - MCP 서버(stdio)

### 요구 사항

- Go `1.25+`
- 사전 DB 경로(별도 생성):
  - 단일 통합 DB 파일: `/path/to/dictionary_fts5.db`, 또는
  - 개별 DB 디렉터리: `/path/to/dicts`

### 빌드 (Taskfile 사용, 정적 링크)

`dictionary-cli-app`에서:

```bash
./build.sh
```

`build.sh`는 Taskfile의 `build` 작업을 호출합니다:

```bash
task build
```

Taskfile 빌드 명령은 아래와 같으며, `CGO_ENABLED=0`으로 정적 링크 바이너리를 생성합니다:

```bash
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o bin/dictcli ./cmd/dictcli
```

선택 검증(리눅스):

```bash
ldd ./bin/dictcli
```

출력에 `not a dynamic executable`가 나오면 정적 링크입니다.

### 테스트

`dictionary-data/dicts` DB들로 CLI 동작을 검증:

```bash
./test.sh
```

`test.sh`는 다음을 수행합니다.

- `scripts/demo_cli_usage.sh` 스모크 테스트 실행
- `dicts/*.db` 전체에 대해 `groups` 호출 sanity check 실행

### 실행 예시 (CLI)

```bash
./bin/dictcli --db /absolute/path/to/dictionary-data/dictionary_fts5.db groups
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 사전
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group en-ko prefix --term dict
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en meaning --query dictionary
```

### 릴리스에서 사전 다운로드

최신 릴리스의 `metadata.json`과 ZIP 에셋을 사용해 다운로드 가능한 사전을 조회/다운로드할 수 있습니다.

```bash
# 다운로드 가능한 사전 목록 확인 (metadata.json 반영)
./bin/dictcli download --list

# source_dict 기준으로 다운로드 + 압축 해제(.db)
./bin/dictcli download --name lingoes-naver-english-korean --out /absolute/path/to/dictionary-data/dicts

# ZIP 파일만 다운로드 (압축 해제 비활성화)
./bin/dictcli download --name cc-kedict.zip --out ./downloads --extract=false
```

출력 모드:

```bash
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output rich
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output plain
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output md
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output json
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output xml
```

### 설정

`config.ini`는 선택 사항입니다(`config.example.ini` 참고).

DB 경로 우선순위:
1. `--db <path>`
2. `DICTCLI_DB_PATH`
3. `config.ini`의 `[database] path`

설정 파일 경로 우선순위:
1. `--config <path>`
2. `DICTCLI_CONFIG`
3. 현재 경로의 `./config.ini`

### REST API (전체 사용법)

서버 실행:

```bash
./bin/dictcli \
  --db /absolute/path/to/dictionary-data/dicts \
  --group ko-en \
  --limit 20 \
  --timeout 60 \
  serve --host 127.0.0.1 --port 8080
```

#### 엔드포인트

- `GET /healthz`
- `GET /groups`
- `GET /exact`
- `GET /prefix`
- `GET /meaning`

#### 공통 쿼리 파라미터

- `group`: 사전 그룹 ID (예: `ko-en`, `en-ko`)
- `limit`: 결과 개수
- `timeout`: 초 단위 타임아웃
- `source_prefix`: `source_dict` 접두어 필터 (예: `lingoes`)

기본값은 전역 옵션(`--group`, `--limit`, `--timeout`, `--source-prefix`) 또는 설정 파일에서 가져옵니다.

#### 명령별 필수 파라미터

- `/exact`: `term` + `group` (group이 전역 기본값으로 채워지지 않으면 필수)
- `/prefix`: `term` + `group` (동일)
- `/meaning`: `query` + `group` (동일)

`/meaning` 전용 파라미터:

- `ranking=true|false` (기본 `false`)
  - `false`: fast 모드(기본)
  - `true`: 랭킹 모드(느리지만 정밀)

#### 요청 예시

```bash
curl -s "http://127.0.0.1:8080/healthz"
curl -s "http://127.0.0.1:8080/groups"
curl -s --get --data-urlencode "group=ko-en" --data-urlencode "term=가게" --data-urlencode "limit=3" "http://127.0.0.1:8080/exact"
curl -s --get --data-urlencode "group=en-ko" --data-urlencode "term=dict" --data-urlencode "limit=5" "http://127.0.0.1:8080/prefix"
curl -s --get --data-urlencode "group=ko-en" --data-urlencode "query=dictionary" --data-urlencode "limit=5" --data-urlencode "ranking=false" "http://127.0.0.1:8080/meaning"
```

#### 응답 형식

성공:

```json
{
  "results": [
    {
      "group": "ko-en",
      "headword": "사전",
      "source_dict": "stardict-koreanenglish",
      "overlap_count": 2,
      "definition": "dictionary ...",
      "duplicates": [
        {
          "source_dict": "stardict-koreanenglish",
          "definition": "dictionary ..."
        }
      ]
    }
  ]
}
```

에러:

```json
{
  "error": "exact requires query params: term, group"
}
```

`/groups` 응답:

```json
{
  "groups": ["en-ko", "ko-en", "ko-ko"]
}
```

`/healthz` 응답:

```json
{
  "status": "ok"
}
```

### MCP 서버 (상세)

실행:

```bash
./bin/dictcli \
  --db /absolute/path/to/dictionary-data/dicts \
  --group ko-en \
  --limit 20 \
  --timeout 60 \
  mcp --name dictcli --version 0.1.0
```

등록되는 MCP 도구:

- `groups`
  - 인자 없음
  - 응답: `{ "groups": [...] }`
- `exact`
  - 필수: `term`
  - 선택: `group`, `limit`, `source_prefix`, `timeout`
  - 응답: `{ "results": [...] }`
- `prefix`
  - 필수: `term`
  - 선택: `group`, `limit`, `source_prefix`, `timeout`
  - 응답: `{ "results": [...] }`
- `meaning`
  - 필수: `query`
  - 선택: `group`, `limit`, `ranking`, `source_prefix`, `timeout`
  - 응답: `{ "results": [...] }`

MCP 클라이언트 설정 예시:

- `manual/mcp_client_config.example.json`

### Ubuntu systemd 서비스

아래 파일을 추가했습니다:

- 서비스 유닛: `deploy/systemd/dictcli-api.service`
- 실행 스크립트: `scripts/run_dictcli_api.sh`
- 설치 스크립트: `deploy/systemd/install_ubuntu_service.sh`

#### 빠른 설치 (권장)

```bash
sudo ./deploy/systemd/install_ubuntu_service.sh
```

필요 시 환경변수를 오버라이드해서 설치할 수 있습니다:

```bash
sudo APP_USER=dictcli APP_GROUP=dictcli INSTALL_ROOT=/opt/dictcli \
  DICTCLI_DB_PATH_DEFAULT=/home/keith/workspace/dictionary/dictionary-data/dicts \
  ./deploy/systemd/install_ubuntu_service.sh
```

#### 1) 배포 경로 예시

```bash
sudo mkdir -p /opt/dictcli/bin /opt/dictcli/scripts
sudo cp ./bin/dictcli /opt/dictcli/bin/dictcli
sudo cp ./scripts/run_dictcli_api.sh /opt/dictcli/scripts/run_dictcli_api.sh
sudo chmod +x /opt/dictcli/scripts/run_dictcli_api.sh
```

#### 2) 서비스 사용자 생성 (예시)

```bash
sudo useradd --system --no-create-home --shell /usr/sbin/nologin dictcli || true
sudo chown -R dictcli:dictcli /opt/dictcli
```

#### 3) 환경 파일 생성

`/etc/default/dictcli`:

```bash
DICTCLI_BIN=/opt/dictcli/bin/dictcli
DICTCLI_DB_PATH=/home/keith/workspace/dictionary/dictionary-data/dicts
DICTCLI_HOST=127.0.0.1
DICTCLI_PORT=8080
DICTCLI_GROUP=ko-en
DICTCLI_LIMIT=20
DICTCLI_TIMEOUT=60
# DICTCLI_SOURCE_PREFIX=lingoes
```

#### 4) 서비스 등록 및 시작

```bash
sudo cp ./deploy/systemd/dictcli-api.service /etc/systemd/system/dictcli-api.service
sudo systemctl daemon-reload
sudo systemctl enable --now dictcli-api
sudo systemctl status dictcli-api
```

---

## 🇺🇸 English

Offline dictionary CLI with optional REST API server (`serve`) and MCP stdio server (`mcp`).

### Features

- Query dictionary data from SQLite DB files/directories
- Search commands:
  - `exact` (exact headword)
  - `prefix` (headword prefix)
  - `meaning` (FTS5 full-text/meaning)
- Runtime modes:
  - local CLI
  - HTTP REST API server
  - MCP server over stdio

### Requirements

- Go `1.25+`
- Dictionary DB path (built separately):
  - unified DB file: `/path/to/dictionary_fts5.db`, or
  - per-dictionary DB directory: `/path/to/dicts`

### Build (Taskfile, static linking)

From `dictionary-cli-app`:

```bash
./build.sh
```

`build.sh` calls the Taskfile build target:

```bash
task build
```

Taskfile uses static-link friendly build flags via `CGO_ENABLED=0`:

```bash
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o bin/dictcli ./cmd/dictcli
```

Optional check on Linux:

```bash
ldd ./bin/dictcli
```

If it prints `not a dynamic executable`, it is statically linked.

### Test

Run CLI tests against DB files under `dictionary-data/dicts`:

```bash
./test.sh
```

`test.sh` runs:

- the smoke script `scripts/demo_cli_usage.sh`
- per-file sanity checks (`dictcli --db <each db> groups`)

### Run (CLI examples)

```bash
./bin/dictcli --db /absolute/path/to/dictionary-data/dictionary_fts5.db groups
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 사전
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group en-ko prefix --term dict
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en meaning --query dictionary
```

### Download dictionaries from release

You can browse and download dictionary ZIP assets from the latest release using release `metadata.json`.

```bash
# List downloadable dictionaries (metadata-aware)
./bin/dictcli download --list

# Download by source_dict and extract .db files
./bin/dictcli download --name lingoes-naver-english-korean --out /absolute/path/to/dictionary-data/dicts

# Download ZIP only (no extraction)
./bin/dictcli download --name cc-kedict.zip --out ./downloads --extract=false
```

Output modes:

```bash
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output rich
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output plain
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output md
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output json
./bin/dictcli --db /absolute/path/to/dictionary-data/dicts --group ko-en exact --term 옥편 --output xml
```

### Configuration

Config file is optional (`config.example.ini`).

DB path resolution priority:
1. `--db <path>`
2. `DICTCLI_DB_PATH`
3. `[database] path` in `config.ini`

Config path resolution priority:
1. `--config <path>`
2. `DICTCLI_CONFIG`
3. `./config.ini`

### REST API (full usage)

Start server:

```bash
./bin/dictcli \
  --db /absolute/path/to/dictionary-data/dicts \
  --group ko-en \
  --limit 20 \
  --timeout 60 \
  serve --host 127.0.0.1 --port 8080
```

#### Endpoints

- `GET /healthz`
- `GET /groups`
- `GET /exact`
- `GET /prefix`
- `GET /meaning`

#### Common query parameters

- `group`: dictionary group ID (for example `ko-en`, `en-ko`)
- `limit`: max result count
- `timeout`: timeout in seconds
- `source_prefix`: `source_dict` prefix filter (for example `lingoes`)

Defaults come from global options (`--group`, `--limit`, `--timeout`, `--source-prefix`) and/or config values.

#### Required per endpoint

- `/exact`: `term` + `group` (group can come from defaults)
- `/prefix`: `term` + `group` (same)
- `/meaning`: `query` + `group` (same)

`/meaning` specific parameter:

- `ranking=true|false` (default `false`)
  - `false`: fast mode (default)
  - `true`: ranked mode (slower, more precise)

#### Request examples

```bash
curl -s "http://127.0.0.1:8080/healthz"
curl -s "http://127.0.0.1:8080/groups"
curl -s --get --data-urlencode "group=ko-en" --data-urlencode "term=가게" --data-urlencode "limit=3" "http://127.0.0.1:8080/exact"
curl -s --get --data-urlencode "group=en-ko" --data-urlencode "term=dict" --data-urlencode "limit=5" "http://127.0.0.1:8080/prefix"
curl -s --get --data-urlencode "group=ko-en" --data-urlencode "query=dictionary" --data-urlencode "limit=5" --data-urlencode "ranking=false" "http://127.0.0.1:8080/meaning"
```

#### Response formats

Success:

```json
{
  "results": [
    {
      "group": "ko-en",
      "headword": "사전",
      "source_dict": "stardict-koreanenglish",
      "overlap_count": 2,
      "definition": "dictionary ...",
      "duplicates": [
        {
          "source_dict": "stardict-koreanenglish",
          "definition": "dictionary ..."
        }
      ]
    }
  ]
}
```

Error:

```json
{
  "error": "exact requires query params: term, group"
}
```

`/groups`:

```json
{
  "groups": ["en-ko", "ko-en", "ko-ko"]
}
```

`/healthz`:

```json
{
  "status": "ok"
}
```

### MCP server (detailed)

Start MCP server:

```bash
./bin/dictcli \
  --db /absolute/path/to/dictionary-data/dicts \
  --group ko-en \
  --limit 20 \
  --timeout 60 \
  mcp --name dictcli --version 0.1.0
```

Exposed MCP tools:

- `groups`
  - no args
  - response: `{ "groups": [...] }`
- `exact`
  - required: `term`
  - optional: `group`, `limit`, `source_prefix`, `timeout`
  - response: `{ "results": [...] }`
- `prefix`
  - required: `term`
  - optional: `group`, `limit`, `source_prefix`, `timeout`
  - response: `{ "results": [...] }`
- `meaning`
  - required: `query`
  - optional: `group`, `limit`, `ranking`, `source_prefix`, `timeout`
  - response: `{ "results": [...] }`

MCP client config example:

- `manual/mcp_client_config.example.json`

### Ubuntu systemd service

Added files:

- systemd unit: `deploy/systemd/dictcli-api.service`
- launcher script: `scripts/run_dictcli_api.sh`
- installer script: `deploy/systemd/install_ubuntu_service.sh`

#### Quick install (recommended)

```bash
sudo ./deploy/systemd/install_ubuntu_service.sh
```

You can override defaults with environment variables:

```bash
sudo APP_USER=dictcli APP_GROUP=dictcli INSTALL_ROOT=/opt/dictcli \
  DICTCLI_DB_PATH_DEFAULT=/home/keith/workspace/dictionary/dictionary-data/dicts \
  ./deploy/systemd/install_ubuntu_service.sh
```

#### 1) Deployment example

```bash
sudo mkdir -p /opt/dictcli/bin /opt/dictcli/scripts
sudo cp ./bin/dictcli /opt/dictcli/bin/dictcli
sudo cp ./scripts/run_dictcli_api.sh /opt/dictcli/scripts/run_dictcli_api.sh
sudo chmod +x /opt/dictcli/scripts/run_dictcli_api.sh
```

#### 2) Service user example

```bash
sudo useradd --system --no-create-home --shell /usr/sbin/nologin dictcli || true
sudo chown -R dictcli:dictcli /opt/dictcli
```

#### 3) Environment file

Create `/etc/default/dictcli`:

```bash
DICTCLI_BIN=/opt/dictcli/bin/dictcli
DICTCLI_DB_PATH=/home/keith/workspace/dictionary/dictionary-data/dicts
DICTCLI_HOST=127.0.0.1
DICTCLI_PORT=8080
DICTCLI_GROUP=ko-en
DICTCLI_LIMIT=20
DICTCLI_TIMEOUT=60
# DICTCLI_SOURCE_PREFIX=lingoes
```

#### 4) Enable service

```bash
sudo cp ./deploy/systemd/dictcli-api.service /etc/systemd/system/dictcli-api.service
sudo systemctl daemon-reload
sudo systemctl enable --now dictcli-api
sudo systemctl status dictcli-api
```
