open-gil 0.1.0__tar.gz

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.
Files changed (41) hide show
  1. open_gil-0.1.0/CHANGELOG.md +12 -0
  2. open_gil-0.1.0/LICENSE +22 -0
  3. open_gil-0.1.0/MANIFEST.in +5 -0
  4. open_gil-0.1.0/PKG-INFO +313 -0
  5. open_gil-0.1.0/README.md +280 -0
  6. open_gil-0.1.0/pyproject.toml +51 -0
  7. open_gil-0.1.0/setup.cfg +4 -0
  8. open_gil-0.1.0/skills/open-gil/SKILL.md +140 -0
  9. open_gil-0.1.0/src/open_gil/__init__.py +4 -0
  10. open_gil-0.1.0/src/open_gil/__main__.py +6 -0
  11. open_gil-0.1.0/src/open_gil/cache.py +71 -0
  12. open_gil-0.1.0/src/open_gil/cli.py +207 -0
  13. open_gil-0.1.0/src/open_gil/config.py +101 -0
  14. open_gil-0.1.0/src/open_gil/errors.py +56 -0
  15. open_gil-0.1.0/src/open_gil/formatters.py +273 -0
  16. open_gil-0.1.0/src/open_gil/kakao.py +261 -0
  17. open_gil-0.1.0/src/open_gil/links.py +35 -0
  18. open_gil-0.1.0/src/open_gil/models.py +198 -0
  19. open_gil-0.1.0/src/open_gil/planner.py +167 -0
  20. open_gil-0.1.0/src/open_gil/timeutils.py +97 -0
  21. open_gil-0.1.0/src/open_gil/tmap.py +414 -0
  22. open_gil-0.1.0/src/open_gil.egg-info/PKG-INFO +313 -0
  23. open_gil-0.1.0/src/open_gil.egg-info/SOURCES.txt +39 -0
  24. open_gil-0.1.0/src/open_gil.egg-info/dependency_links.txt +1 -0
  25. open_gil-0.1.0/src/open_gil.egg-info/entry_points.txt +2 -0
  26. open_gil-0.1.0/src/open_gil.egg-info/requires.txt +7 -0
  27. open_gil-0.1.0/src/open_gil.egg-info/top_level.txt +1 -0
  28. open_gil-0.1.0/tests/fixtures/kakao_address_success.json +22 -0
  29. open_gil-0.1.0/tests/fixtures/kakao_keyword_success.json +23 -0
  30. open_gil-0.1.0/tests/fixtures/open_gil_songdo_olympic_success.json +125 -0
  31. open_gil-0.1.0/tests/fixtures/tmap_poi_success.json +30 -0
  32. open_gil-0.1.0/tests/fixtures/tmap_route_success.json +69 -0
  33. open_gil-0.1.0/tests/test_cli.py +97 -0
  34. open_gil-0.1.0/tests/test_config.py +50 -0
  35. open_gil-0.1.0/tests/test_formatters.py +109 -0
  36. open_gil-0.1.0/tests/test_kakao.py +105 -0
  37. open_gil-0.1.0/tests/test_links.py +13 -0
  38. open_gil-0.1.0/tests/test_live_tmap.py +25 -0
  39. open_gil-0.1.0/tests/test_planner.py +152 -0
  40. open_gil-0.1.0/tests/test_timeutils.py +25 -0
  41. open_gil-0.1.0/tests/test_tmap.py +142 -0
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial MVP CLI package.
6
+ - Added TMAP-backed public-transit route planning.
7
+ - Added `open-gil config set-key` and `open-gil plan`.
8
+ - Added JSON input/output for agent integrations.
9
+ - Added in-repository `skills/open-gil` agent instructions.
10
+ - Added Kakao Local coordinate fallback for place-name lookup.
11
+ - Moved `open-gil` into the `packages/open-gil` monorepo package layout.
12
+ - Added fixture/mock tests and optional live TMAP test.
open_gil-0.1.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 open-gil contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,5 @@
1
+ include CHANGELOG.md
2
+ include LICENSE
3
+ include README.md
4
+ recursive-include skills *.md
5
+ recursive-include tests *.py *.json
@@ -0,0 +1,313 @@
1
+ Metadata-Version: 2.4
2
+ Name: open-gil
3
+ Version: 0.1.0
4
+ Summary: API-backed public transit departure planner for Seoul, Gyeonggi, and Incheon.
5
+ Author: open-gil contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/doul735/open_tools
8
+ Project-URL: Repository, https://github.com/doul735/open_tools
9
+ Project-URL: Issues, https://github.com/doul735/open_tools/issues
10
+ Project-URL: Changelog, https://github.com/doul735/open_tools/blob/main/packages/open-gil/CHANGELOG.md
11
+ Keywords: transit,tmap,cli,korea,route-planning
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Natural Language :: Korean
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typer>=0.12
27
+ Requires-Dist: httpx>=0.27
28
+ Requires-Dist: pydantic>=2.7
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Requires-Dist: build>=1.2; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # open-gil
35
+
36
+ TMAP API로 계산하고, LLM이 지어내지 못하게 막는 대중교통 출발시간 CLI.
37
+
38
+ `open-gil`은 “몇 시에 출발해야 하지?”라는 질문에 대해 출발지, 목적지, 일정 시각을 받아 대중교통 경로와 추천 출발 시간을 계산합니다. Codex, Claude Code 같은 LLM은 자연어를 구조화하고 확인 질문을 하는 데만 쓰고, 출발시각, 환승, 소요시간, 도착시각은 TMAP 대중교통 API 응답으로만 만듭니다.
39
+
40
+ 초기 MVP 범위는 서울, 경기, 인천입니다.
41
+
42
+ ## Why
43
+
44
+ LLM은 길찾기 질문에 그럴듯한 답을 만들 수 있지만, 실제 대중교통 시간표와 환승 경로를 보장하지 못합니다. `open-gil`은 이 부분을 분리합니다.
45
+
46
+ - LLM: “사용자가 말한 시간이 일정 시작인지, 도착 마감인지, 출발 시간인지” 확인
47
+ - CLI: 장소 검색, 좌표 확정, TMAP 경로 조회, 출발시각 계산
48
+ - 사용자: 네이버지도와 카카오맵 링크로 최종 확인
49
+
50
+ 핵심 원칙은 단순합니다. 모르면 추측하지 않고, API가 준 것만 말합니다.
51
+
52
+ 개발 과정, 시행착오, 현재 한계, 다음 개선 방향은 [open-gil Development Story](https://github.com/doul735/open_tools/blob/main/docs/open-gil-development-story.md)에 정리되어 있습니다.
53
+
54
+ ## Quick Start
55
+
56
+ Until the first PyPI release is live, install from this repository:
57
+
58
+ ```bash
59
+ pipx install "git+https://github.com/doul735/open_tools.git#subdirectory=packages/open-gil"
60
+ ```
61
+
62
+ After PyPI release:
63
+
64
+ ```bash
65
+ pipx install open-gil
66
+ ```
67
+
68
+ Configure your TMAP key:
69
+
70
+ ```bash
71
+ open-gil config set-key
72
+ ```
73
+
74
+ 그 다음 바로 물어볼 수 있습니다.
75
+
76
+ ```bash
77
+ open-gil plan \
78
+ --origin "송도달빛축제공원역" \
79
+ --destination "올림픽공원 올림픽홀" \
80
+ --event-at "2026-06-06 13:00"
81
+ ```
82
+
83
+ `--event-at`은 일정 시작 시각입니다. open-gil은 기본적으로 15분 전 도착을 목표로 출발시각을 추천합니다.
84
+
85
+ ## What You Get
86
+
87
+ 하루 10회 수준의 낮은 API 호출 제한을 고려해, 기본 모드는 TMAP 경로 조회 1회로 총 소요시간을 받아 목표 도착시각에서 출발시각을 역산합니다.
88
+
89
+ 이 방식은 API 호출을 아끼지만, “가장 늦은 출발”과 “이전/다음 수단”을 전수 검증하지는 않습니다. TMAP에 도착시각 기준 검색이 별도로 제공되지 않는 한, 그 기능은 여러 출발시각을 반복 조회해야 하므로 기본 MVP에서는 실행하지 않습니다.
90
+
91
+ 예시 출력은 이런 형태입니다.
92
+
93
+ ```text
94
+ 송도달빛축제공원역 -> 올림픽공원 올림픽홀
95
+ 기준: 2026-06-06 13:00 일정 시작, 목표 도착 2026-06-06 12:45
96
+
97
+ 추천: 2026-06-06 11:29 출발 -> 2026-06-06 12:45 도착 / 소요 1시간 16분 / 목표 도착 가능
98
+ 경로: 도보 -> 지하철 인천1호선 -> 지하철 7호선 -> 도보
99
+
100
+ 추천 경로 상세
101
+ - 도보: 출발지 -> 송도달빛축제공원역
102
+ - 버스 광역:M6450: 송도달빛축제공원역 승차 -> 선릉역 하차
103
+ - 같은 정류장 환승: 선릉역에서 버스 광역:M6450 하차 후 버스 간선:360 탑승
104
+ - 버스 간선:360: 선릉역 승차 -> 잠실트리지움아파트앞 하차
105
+ - 같은 정류장 환승: 잠실트리지움아파트앞에서 버스 간선:360 하차 후 버스 지선:3323 탑승
106
+ - 버스 지선:3323: 잠실트리지움아파트앞 승차 -> 올림픽베어스타운아파트앞 하차
107
+ - 도보: 올림픽베어스타운아파트앞 -> 올림픽홀
108
+
109
+ 탐색 방식: 하루 호출 제한을 고려해 TMAP 경로 조회 1회로 총 소요시간을 받아 목표 도착시각에서 역산했습니다. 이전/다음 수단 전수 탐색은 실행하지 않았습니다.
110
+
111
+ 확인 링크
112
+ 지도앱은 같은 출발/도착 좌표로 다시 길찾기하므로 위 TMAP 경로와 다를 수 있습니다.
113
+ - 네이버지도: nmap://route/public?...
114
+ - 카카오맵: http://m.map.kakao.com/scheme/route?...
115
+
116
+ TMAP API 응답 기준입니다. 현장 지연, 운행 중단, 행사장 혼잡은 실제와 다를 수 있습니다.
117
+ ```
118
+
119
+ 위 출력은 형식 예시입니다. 실제 시간과 경로는 실행 시점의 TMAP API 응답에 따라 달라집니다.
120
+
121
+ ## Time Modes
122
+
123
+ 세 가지 시간 의도를 명확히 나눕니다.
124
+
125
+ ```bash
126
+ # 일정 시작 시각. 15분 전 도착 목표.
127
+ open-gil plan --origin "강남역" --destination "서울역" --event-at "2026-06-06 19:00"
128
+
129
+ # 특정 시각까지 도착. 추가 버퍼 없음.
130
+ open-gil plan --origin "강남역" --destination "서울역" --arrive-by "2026-06-06 18:30"
131
+
132
+ # 특정 시각에 출발.
133
+ open-gil plan --origin "강남역" --destination "서울역" --depart-at "2026-06-06 09:00"
134
+ ```
135
+
136
+ 날짜 없이 `09:00`처럼 입력하면 직접 CLI 사용에서는 오늘 날짜로 해석합니다. LLM 스킬 흐름에서는 날짜를 사용자에게 확인해야 합니다.
137
+
138
+ ## JSON for Agents
139
+
140
+ Codex나 Claude Code 같은 에이전트는 JSON을 쓰는 편이 안전합니다.
141
+
142
+ ```bash
143
+ cat <<'JSON' | open-gil plan --json
144
+ {
145
+ "origin": {"name": "송도달빛축제공원역"},
146
+ "destination": {"name": "올림픽공원 올림픽홀"},
147
+ "event_at": "2026-06-06 13:00"
148
+ }
149
+ JSON
150
+ ```
151
+
152
+ 성공 응답은 항상 envelope 형태입니다.
153
+
154
+ ```json
155
+ {
156
+ "status": "ok",
157
+ "data": {
158
+ "time_mode": "event_at",
159
+ "arrival_buffer_minutes": 15,
160
+ "search_strategy": "quota_safe_target_arrival_single_lookup",
161
+ "route_api_calls_used": 1,
162
+ "candidates": []
163
+ }
164
+ }
165
+ ```
166
+
167
+ 오류도 구조화됩니다.
168
+
169
+ ```json
170
+ {
171
+ "status": "error",
172
+ "error": {
173
+ "code": "OPEN_GIL_AUTH_INVALID",
174
+ "message": "TMAP API 키가 유효하지 않습니다.",
175
+ "remediation": "TMAP_API_KEY 값 또는 open-gil config set-key로 저장한 키를 확인하세요."
176
+ }
177
+ }
178
+ ```
179
+
180
+ 장소 후보가 애매하면 `OPEN_GIL_PLACE_AMBIGUOUS`와 후보 목록을 반환합니다. 에이전트는 이때 임의로 고르지 말고 사용자에게 선택을 물어야 합니다.
181
+
182
+ 403 Forbidden처럼 키는 전달됐지만 호출 권한이 거절되면 `OPEN_GIL_AUTH_FORBIDDEN`으로 반환합니다. 이 경우 TMAP 앱키에 해당 API 상품/권한이 활성화되어 있는지, 요금제/도메인/IP 제한이 있는지 확인해야 합니다.
183
+
184
+ 호출 한도 초과는 `OPEN_GIL_QUOTA_EXCEEDED`로 반환합니다. 이 경우 자동 재시도하지 말고 TMAP 한도 초기화나 요금제/쿼터를 확인해야 합니다.
185
+
186
+ ## Natural-Language Agent Flow
187
+
188
+ `$open-gil` 같은 에이전트 스킬은 자연어를 구조화한 뒤 CLI를 호출합니다.
189
+
190
+ 정확한 상태는 다음과 같습니다.
191
+
192
+ - 장소명 검색과 경로 계산이 모두 성공하면 그대로 답변합니다.
193
+ - TMAP POI 장소명 검색이 `OPEN_GIL_AUTH_FORBIDDEN`, `OPEN_GIL_PLACE_NOT_FOUND`, `OPEN_GIL_QUOTA_EXCEEDED`로 실패하고 `KAKAO_REST_API_KEY`가 있으면 Kakao Local 주소/키워드 검색으로 좌표를 보조 확인합니다.
194
+ - 좌표가 확정되면 `open-gil plan --json`을 좌표 입력으로 다시 실행합니다. 이때 경로, 출발시각, 요금, 환승 정보는 TMAP Transit API 결과만 사용합니다.
195
+ - Kakao Local 후보가 애매하거나 민감한 장소면 에이전트가 임의로 고르지 않고 사용자에게 선택 또는 좌표 제공을 요청해야 합니다.
196
+ - TMAP Transit API 자체가 권한 오류, 한도 초과, 경로 없음으로 실패하면 경로 계산은 불가능합니다.
197
+
198
+ 즉 “자연어 스킬”은 좌표가 확정되고 Transit API가 성공하면 동작합니다. Kakao Local은 좌표화 fallback일 뿐이며, 경로 계산 source of truth는 항상 TMAP Transit API입니다.
199
+
200
+ ## Coordinates
201
+
202
+ 이미 장소를 확정했다면 좌표를 직접 넣을 수 있습니다. 좌표가 있으면 장소명 검색보다 우선합니다.
203
+
204
+ ```bash
205
+ open-gil plan \
206
+ --origin-lat 37.407722 --origin-lon 126.625572 --origin-label "송도달빛축제공원역" \
207
+ --destination-lat 37.516289 --destination-lon 127.117314 --destination-label "올림픽홀" \
208
+ --event-at "2026-06-06 13:00"
209
+ ```
210
+
211
+ ## API Keys
212
+
213
+ TMAP API 키는 필수입니다.
214
+
215
+ 1. SK Open API에서 앱을 만들고 appKey를 발급합니다.
216
+ 2. TMAP 대중교통 API와 POI 검색 API 사용 권한을 확인합니다.
217
+ 3. 환경변수나 로컬 설정 파일로 키를 설정합니다.
218
+
219
+ 환경변수가 우선입니다.
220
+
221
+ ```bash
222
+ export TMAP_API_KEY="발급받은_appKey"
223
+ ```
224
+
225
+ 로컬 파일에 저장할 수도 있습니다.
226
+
227
+ ```bash
228
+ open-gil config set-key
229
+ ```
230
+
231
+ Kakao Local fallback은 선택이지만 자연어 장소명 품질을 크게 올립니다. Kakao Developers에서 REST API 키를 발급하고 Local API 사용을 확인한 뒤 설정합니다.
232
+
233
+ ```bash
234
+ export KAKAO_REST_API_KEY="발급받은_REST_API_key"
235
+ ```
236
+
237
+ 또는 로컬 파일에 저장합니다.
238
+
239
+ ```bash
240
+ open-gil config set-kakao-key
241
+ ```
242
+
243
+ 설정 파일은 `~/.config/open-gil/config.json`에 평문으로 저장됩니다. POSIX 환경에서는 `0600` 권한으로 맞춥니다.
244
+
245
+ 공식 문서:
246
+
247
+ - TMAP 대중교통 API: https://transit.tmapmobility.com/docs/routes
248
+ - TMAP POI 검색 API: https://tmap-skopenapi.readme.io/reference/%EC%9E%A5%EC%86%8C%ED%86%B5%ED%95%A9%EA%B2%80%EC%83%89
249
+ - Kakao Local API: https://developers.kakao.com/docs/latest/ko/local/dev-guide
250
+ - NAVER Maps URL Scheme: https://guide.ncloud-docs.com/docs/en/maps-url-scheme
251
+ - KakaoMap URL Scheme: https://apis.map.kakao.com/ios_v2/docs/getting-started/urlscheme/
252
+
253
+ ## Install from Source
254
+
255
+ ```bash
256
+ git clone https://github.com/doul735/open_tools.git
257
+ cd open_tools
258
+ python -m venv .venv
259
+ . .venv/bin/activate
260
+ python -m pip install -e "./packages/open-gil[dev]"
261
+ ```
262
+
263
+ ## Tests
264
+
265
+ 기본 테스트는 fixture/mock 기반이라 API 키가 없어도 실행됩니다.
266
+
267
+ ```bash
268
+ PYTHONDONTWRITEBYTECODE=1 python -m pytest packages/open-gil/tests -p no:cacheprovider
269
+ ```
270
+
271
+ 실제 TMAP API 테스트는 `TMAP_API_KEY`가 있을 때만 실행됩니다.
272
+
273
+ ```bash
274
+ export TMAP_API_KEY="발급받은_appKey"
275
+ PYTHONDONTWRITEBYTECODE=1 python -m pytest packages/open-gil/tests/test_live_tmap.py -p no:cacheprovider
276
+ ```
277
+
278
+ ## Release Prep
279
+
280
+ 이 저장소는 GitHub Actions CI와 PyPI Trusted Publishing용 워크플로를 포함합니다.
281
+
282
+ - CI: `.github/workflows/ci.yml`
283
+ - PyPI publish: `.github/workflows/publish.yml`
284
+ - Dependabot: `.github/dependabot.yml`
285
+
286
+ 로컬 배포 산출물 검증:
287
+
288
+ ```bash
289
+ python -m pip install -e "./packages/open-gil[dev]"
290
+ python -m build packages/open-gil
291
+ ```
292
+
293
+ PyPI에 실제 배포하려면 PyPI에서 Trusted Publisher를 이 저장소와 `publish.yml` 워크플로에 연결하세요.
294
+
295
+ Pending publisher 값:
296
+
297
+ ```text
298
+ Project name: open-gil
299
+ Owner: doul735
300
+ Repository name: open_tools
301
+ Workflow name: publish.yml
302
+ Environment name: pypi
303
+ ```
304
+
305
+ ## Limits
306
+
307
+ - 현재 MVP 범위는 서울, 경기, 인천입니다.
308
+ - 계산 근거는 TMAP 대중교통 API입니다.
309
+ - 목표 도착 모드는 기본적으로 경로 조회 1회로 출발시각을 역산합니다. 낮은 호출 제한 때문에 이전/추천/다음 수단 전수 탐색은 기본 제공하지 않습니다.
310
+ - 네이버지도와 카카오맵 링크는 확인용이며 계산 근거가 아닙니다.
311
+ - 네이버지도와 카카오맵 링크는 같은 출발/도착 좌표로 앱 자체 길찾기를 다시 실행하므로 TMAP 추천 경로와 다를 수 있습니다.
312
+ - 별도 실시간 지연, 운행 중단, 행사장 혼잡 정보를 보장하지 않습니다.
313
+ - 원문 자연어 프롬프트는 외부 API로 보내거나 캐시하지 않습니다.
@@ -0,0 +1,280 @@
1
+ # open-gil
2
+
3
+ TMAP API로 계산하고, LLM이 지어내지 못하게 막는 대중교통 출발시간 CLI.
4
+
5
+ `open-gil`은 “몇 시에 출발해야 하지?”라는 질문에 대해 출발지, 목적지, 일정 시각을 받아 대중교통 경로와 추천 출발 시간을 계산합니다. Codex, Claude Code 같은 LLM은 자연어를 구조화하고 확인 질문을 하는 데만 쓰고, 출발시각, 환승, 소요시간, 도착시각은 TMAP 대중교통 API 응답으로만 만듭니다.
6
+
7
+ 초기 MVP 범위는 서울, 경기, 인천입니다.
8
+
9
+ ## Why
10
+
11
+ LLM은 길찾기 질문에 그럴듯한 답을 만들 수 있지만, 실제 대중교통 시간표와 환승 경로를 보장하지 못합니다. `open-gil`은 이 부분을 분리합니다.
12
+
13
+ - LLM: “사용자가 말한 시간이 일정 시작인지, 도착 마감인지, 출발 시간인지” 확인
14
+ - CLI: 장소 검색, 좌표 확정, TMAP 경로 조회, 출발시각 계산
15
+ - 사용자: 네이버지도와 카카오맵 링크로 최종 확인
16
+
17
+ 핵심 원칙은 단순합니다. 모르면 추측하지 않고, API가 준 것만 말합니다.
18
+
19
+ 개발 과정, 시행착오, 현재 한계, 다음 개선 방향은 [open-gil Development Story](https://github.com/doul735/open_tools/blob/main/docs/open-gil-development-story.md)에 정리되어 있습니다.
20
+
21
+ ## Quick Start
22
+
23
+ Until the first PyPI release is live, install from this repository:
24
+
25
+ ```bash
26
+ pipx install "git+https://github.com/doul735/open_tools.git#subdirectory=packages/open-gil"
27
+ ```
28
+
29
+ After PyPI release:
30
+
31
+ ```bash
32
+ pipx install open-gil
33
+ ```
34
+
35
+ Configure your TMAP key:
36
+
37
+ ```bash
38
+ open-gil config set-key
39
+ ```
40
+
41
+ 그 다음 바로 물어볼 수 있습니다.
42
+
43
+ ```bash
44
+ open-gil plan \
45
+ --origin "송도달빛축제공원역" \
46
+ --destination "올림픽공원 올림픽홀" \
47
+ --event-at "2026-06-06 13:00"
48
+ ```
49
+
50
+ `--event-at`은 일정 시작 시각입니다. open-gil은 기본적으로 15분 전 도착을 목표로 출발시각을 추천합니다.
51
+
52
+ ## What You Get
53
+
54
+ 하루 10회 수준의 낮은 API 호출 제한을 고려해, 기본 모드는 TMAP 경로 조회 1회로 총 소요시간을 받아 목표 도착시각에서 출발시각을 역산합니다.
55
+
56
+ 이 방식은 API 호출을 아끼지만, “가장 늦은 출발”과 “이전/다음 수단”을 전수 검증하지는 않습니다. TMAP에 도착시각 기준 검색이 별도로 제공되지 않는 한, 그 기능은 여러 출발시각을 반복 조회해야 하므로 기본 MVP에서는 실행하지 않습니다.
57
+
58
+ 예시 출력은 이런 형태입니다.
59
+
60
+ ```text
61
+ 송도달빛축제공원역 -> 올림픽공원 올림픽홀
62
+ 기준: 2026-06-06 13:00 일정 시작, 목표 도착 2026-06-06 12:45
63
+
64
+ 추천: 2026-06-06 11:29 출발 -> 2026-06-06 12:45 도착 / 소요 1시간 16분 / 목표 도착 가능
65
+ 경로: 도보 -> 지하철 인천1호선 -> 지하철 7호선 -> 도보
66
+
67
+ 추천 경로 상세
68
+ - 도보: 출발지 -> 송도달빛축제공원역
69
+ - 버스 광역:M6450: 송도달빛축제공원역 승차 -> 선릉역 하차
70
+ - 같은 정류장 환승: 선릉역에서 버스 광역:M6450 하차 후 버스 간선:360 탑승
71
+ - 버스 간선:360: 선릉역 승차 -> 잠실트리지움아파트앞 하차
72
+ - 같은 정류장 환승: 잠실트리지움아파트앞에서 버스 간선:360 하차 후 버스 지선:3323 탑승
73
+ - 버스 지선:3323: 잠실트리지움아파트앞 승차 -> 올림픽베어스타운아파트앞 하차
74
+ - 도보: 올림픽베어스타운아파트앞 -> 올림픽홀
75
+
76
+ 탐색 방식: 하루 호출 제한을 고려해 TMAP 경로 조회 1회로 총 소요시간을 받아 목표 도착시각에서 역산했습니다. 이전/다음 수단 전수 탐색은 실행하지 않았습니다.
77
+
78
+ 확인 링크
79
+ 지도앱은 같은 출발/도착 좌표로 다시 길찾기하므로 위 TMAP 경로와 다를 수 있습니다.
80
+ - 네이버지도: nmap://route/public?...
81
+ - 카카오맵: http://m.map.kakao.com/scheme/route?...
82
+
83
+ TMAP API 응답 기준입니다. 현장 지연, 운행 중단, 행사장 혼잡은 실제와 다를 수 있습니다.
84
+ ```
85
+
86
+ 위 출력은 형식 예시입니다. 실제 시간과 경로는 실행 시점의 TMAP API 응답에 따라 달라집니다.
87
+
88
+ ## Time Modes
89
+
90
+ 세 가지 시간 의도를 명확히 나눕니다.
91
+
92
+ ```bash
93
+ # 일정 시작 시각. 15분 전 도착 목표.
94
+ open-gil plan --origin "강남역" --destination "서울역" --event-at "2026-06-06 19:00"
95
+
96
+ # 특정 시각까지 도착. 추가 버퍼 없음.
97
+ open-gil plan --origin "강남역" --destination "서울역" --arrive-by "2026-06-06 18:30"
98
+
99
+ # 특정 시각에 출발.
100
+ open-gil plan --origin "강남역" --destination "서울역" --depart-at "2026-06-06 09:00"
101
+ ```
102
+
103
+ 날짜 없이 `09:00`처럼 입력하면 직접 CLI 사용에서는 오늘 날짜로 해석합니다. LLM 스킬 흐름에서는 날짜를 사용자에게 확인해야 합니다.
104
+
105
+ ## JSON for Agents
106
+
107
+ Codex나 Claude Code 같은 에이전트는 JSON을 쓰는 편이 안전합니다.
108
+
109
+ ```bash
110
+ cat <<'JSON' | open-gil plan --json
111
+ {
112
+ "origin": {"name": "송도달빛축제공원역"},
113
+ "destination": {"name": "올림픽공원 올림픽홀"},
114
+ "event_at": "2026-06-06 13:00"
115
+ }
116
+ JSON
117
+ ```
118
+
119
+ 성공 응답은 항상 envelope 형태입니다.
120
+
121
+ ```json
122
+ {
123
+ "status": "ok",
124
+ "data": {
125
+ "time_mode": "event_at",
126
+ "arrival_buffer_minutes": 15,
127
+ "search_strategy": "quota_safe_target_arrival_single_lookup",
128
+ "route_api_calls_used": 1,
129
+ "candidates": []
130
+ }
131
+ }
132
+ ```
133
+
134
+ 오류도 구조화됩니다.
135
+
136
+ ```json
137
+ {
138
+ "status": "error",
139
+ "error": {
140
+ "code": "OPEN_GIL_AUTH_INVALID",
141
+ "message": "TMAP API 키가 유효하지 않습니다.",
142
+ "remediation": "TMAP_API_KEY 값 또는 open-gil config set-key로 저장한 키를 확인하세요."
143
+ }
144
+ }
145
+ ```
146
+
147
+ 장소 후보가 애매하면 `OPEN_GIL_PLACE_AMBIGUOUS`와 후보 목록을 반환합니다. 에이전트는 이때 임의로 고르지 말고 사용자에게 선택을 물어야 합니다.
148
+
149
+ 403 Forbidden처럼 키는 전달됐지만 호출 권한이 거절되면 `OPEN_GIL_AUTH_FORBIDDEN`으로 반환합니다. 이 경우 TMAP 앱키에 해당 API 상품/권한이 활성화되어 있는지, 요금제/도메인/IP 제한이 있는지 확인해야 합니다.
150
+
151
+ 호출 한도 초과는 `OPEN_GIL_QUOTA_EXCEEDED`로 반환합니다. 이 경우 자동 재시도하지 말고 TMAP 한도 초기화나 요금제/쿼터를 확인해야 합니다.
152
+
153
+ ## Natural-Language Agent Flow
154
+
155
+ `$open-gil` 같은 에이전트 스킬은 자연어를 구조화한 뒤 CLI를 호출합니다.
156
+
157
+ 정확한 상태는 다음과 같습니다.
158
+
159
+ - 장소명 검색과 경로 계산이 모두 성공하면 그대로 답변합니다.
160
+ - TMAP POI 장소명 검색이 `OPEN_GIL_AUTH_FORBIDDEN`, `OPEN_GIL_PLACE_NOT_FOUND`, `OPEN_GIL_QUOTA_EXCEEDED`로 실패하고 `KAKAO_REST_API_KEY`가 있으면 Kakao Local 주소/키워드 검색으로 좌표를 보조 확인합니다.
161
+ - 좌표가 확정되면 `open-gil plan --json`을 좌표 입력으로 다시 실행합니다. 이때 경로, 출발시각, 요금, 환승 정보는 TMAP Transit API 결과만 사용합니다.
162
+ - Kakao Local 후보가 애매하거나 민감한 장소면 에이전트가 임의로 고르지 않고 사용자에게 선택 또는 좌표 제공을 요청해야 합니다.
163
+ - TMAP Transit API 자체가 권한 오류, 한도 초과, 경로 없음으로 실패하면 경로 계산은 불가능합니다.
164
+
165
+ 즉 “자연어 스킬”은 좌표가 확정되고 Transit API가 성공하면 동작합니다. Kakao Local은 좌표화 fallback일 뿐이며, 경로 계산 source of truth는 항상 TMAP Transit API입니다.
166
+
167
+ ## Coordinates
168
+
169
+ 이미 장소를 확정했다면 좌표를 직접 넣을 수 있습니다. 좌표가 있으면 장소명 검색보다 우선합니다.
170
+
171
+ ```bash
172
+ open-gil plan \
173
+ --origin-lat 37.407722 --origin-lon 126.625572 --origin-label "송도달빛축제공원역" \
174
+ --destination-lat 37.516289 --destination-lon 127.117314 --destination-label "올림픽홀" \
175
+ --event-at "2026-06-06 13:00"
176
+ ```
177
+
178
+ ## API Keys
179
+
180
+ TMAP API 키는 필수입니다.
181
+
182
+ 1. SK Open API에서 앱을 만들고 appKey를 발급합니다.
183
+ 2. TMAP 대중교통 API와 POI 검색 API 사용 권한을 확인합니다.
184
+ 3. 환경변수나 로컬 설정 파일로 키를 설정합니다.
185
+
186
+ 환경변수가 우선입니다.
187
+
188
+ ```bash
189
+ export TMAP_API_KEY="발급받은_appKey"
190
+ ```
191
+
192
+ 로컬 파일에 저장할 수도 있습니다.
193
+
194
+ ```bash
195
+ open-gil config set-key
196
+ ```
197
+
198
+ Kakao Local fallback은 선택이지만 자연어 장소명 품질을 크게 올립니다. Kakao Developers에서 REST API 키를 발급하고 Local API 사용을 확인한 뒤 설정합니다.
199
+
200
+ ```bash
201
+ export KAKAO_REST_API_KEY="발급받은_REST_API_key"
202
+ ```
203
+
204
+ 또는 로컬 파일에 저장합니다.
205
+
206
+ ```bash
207
+ open-gil config set-kakao-key
208
+ ```
209
+
210
+ 설정 파일은 `~/.config/open-gil/config.json`에 평문으로 저장됩니다. POSIX 환경에서는 `0600` 권한으로 맞춥니다.
211
+
212
+ 공식 문서:
213
+
214
+ - TMAP 대중교통 API: https://transit.tmapmobility.com/docs/routes
215
+ - TMAP POI 검색 API: https://tmap-skopenapi.readme.io/reference/%EC%9E%A5%EC%86%8C%ED%86%B5%ED%95%A9%EA%B2%80%EC%83%89
216
+ - Kakao Local API: https://developers.kakao.com/docs/latest/ko/local/dev-guide
217
+ - NAVER Maps URL Scheme: https://guide.ncloud-docs.com/docs/en/maps-url-scheme
218
+ - KakaoMap URL Scheme: https://apis.map.kakao.com/ios_v2/docs/getting-started/urlscheme/
219
+
220
+ ## Install from Source
221
+
222
+ ```bash
223
+ git clone https://github.com/doul735/open_tools.git
224
+ cd open_tools
225
+ python -m venv .venv
226
+ . .venv/bin/activate
227
+ python -m pip install -e "./packages/open-gil[dev]"
228
+ ```
229
+
230
+ ## Tests
231
+
232
+ 기본 테스트는 fixture/mock 기반이라 API 키가 없어도 실행됩니다.
233
+
234
+ ```bash
235
+ PYTHONDONTWRITEBYTECODE=1 python -m pytest packages/open-gil/tests -p no:cacheprovider
236
+ ```
237
+
238
+ 실제 TMAP API 테스트는 `TMAP_API_KEY`가 있을 때만 실행됩니다.
239
+
240
+ ```bash
241
+ export TMAP_API_KEY="발급받은_appKey"
242
+ PYTHONDONTWRITEBYTECODE=1 python -m pytest packages/open-gil/tests/test_live_tmap.py -p no:cacheprovider
243
+ ```
244
+
245
+ ## Release Prep
246
+
247
+ 이 저장소는 GitHub Actions CI와 PyPI Trusted Publishing용 워크플로를 포함합니다.
248
+
249
+ - CI: `.github/workflows/ci.yml`
250
+ - PyPI publish: `.github/workflows/publish.yml`
251
+ - Dependabot: `.github/dependabot.yml`
252
+
253
+ 로컬 배포 산출물 검증:
254
+
255
+ ```bash
256
+ python -m pip install -e "./packages/open-gil[dev]"
257
+ python -m build packages/open-gil
258
+ ```
259
+
260
+ PyPI에 실제 배포하려면 PyPI에서 Trusted Publisher를 이 저장소와 `publish.yml` 워크플로에 연결하세요.
261
+
262
+ Pending publisher 값:
263
+
264
+ ```text
265
+ Project name: open-gil
266
+ Owner: doul735
267
+ Repository name: open_tools
268
+ Workflow name: publish.yml
269
+ Environment name: pypi
270
+ ```
271
+
272
+ ## Limits
273
+
274
+ - 현재 MVP 범위는 서울, 경기, 인천입니다.
275
+ - 계산 근거는 TMAP 대중교통 API입니다.
276
+ - 목표 도착 모드는 기본적으로 경로 조회 1회로 출발시각을 역산합니다. 낮은 호출 제한 때문에 이전/추천/다음 수단 전수 탐색은 기본 제공하지 않습니다.
277
+ - 네이버지도와 카카오맵 링크는 확인용이며 계산 근거가 아닙니다.
278
+ - 네이버지도와 카카오맵 링크는 같은 출발/도착 좌표로 앱 자체 길찾기를 다시 실행하므로 TMAP 추천 경로와 다를 수 있습니다.
279
+ - 별도 실시간 지연, 운행 중단, 행사장 혼잡 정보를 보장하지 않습니다.
280
+ - 원문 자연어 프롬프트는 외부 API로 보내거나 캐시하지 않습니다.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "open-gil"
7
+ version = "0.1.0"
8
+ description = "API-backed public transit departure planner for Seoul, Gyeonggi, and Incheon."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "open-gil contributors" }]
14
+ keywords = ["transit", "tmap", "cli", "korea", "route-planning"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: End Users/Desktop",
19
+ "Natural Language :: Korean",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Utilities",
27
+ ]
28
+ dependencies = [
29
+ "typer>=0.12",
30
+ "httpx>=0.27",
31
+ "pydantic>=2.7",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=8", "build>=1.2"]
36
+
37
+ [project.scripts]
38
+ open-gil = "open_gil.cli:app"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/doul735/open_tools"
42
+ Repository = "https://github.com/doul735/open_tools"
43
+ Issues = "https://github.com/doul735/open_tools/issues"
44
+ Changelog = "https://github.com/doul735/open_tools/blob/main/packages/open-gil/CHANGELOG.md"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+ addopts = "-q"