sincenety 0.1.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 +331 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +305 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/gatherer.d.ts +19 -0
- package/dist/core/gatherer.js +125 -0
- package/dist/core/gatherer.js.map +1 -0
- package/dist/email/sender.d.ts +14 -0
- package/dist/email/sender.js +137 -0
- package/dist/email/sender.js.map +1 -0
- package/dist/email/template.d.ts +47 -0
- package/dist/email/template.js +342 -0
- package/dist/email/template.js.map +1 -0
- package/dist/encryption/crypto.d.ts +13 -0
- package/dist/encryption/crypto.js +44 -0
- package/dist/encryption/crypto.js.map +1 -0
- package/dist/encryption/key.d.ts +8 -0
- package/dist/encryption/key.js +42 -0
- package/dist/encryption/key.js.map +1 -0
- package/dist/grouper/session.d.ts +33 -0
- package/dist/grouper/session.js +63 -0
- package/dist/grouper/session.js.map +1 -0
- package/dist/parser/history.d.ts +12 -0
- package/dist/parser/history.js +29 -0
- package/dist/parser/history.js.map +1 -0
- package/dist/parser/session-jsonl.d.ts +40 -0
- package/dist/parser/session-jsonl.js +242 -0
- package/dist/parser/session-jsonl.js.map +1 -0
- package/dist/report/markdown.d.ts +5 -0
- package/dist/report/markdown.js +75 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/terminal.d.ts +7 -0
- package/dist/report/terminal.js +114 -0
- package/dist/report/terminal.js.map +1 -0
- package/dist/scheduler/install.d.ts +19 -0
- package/dist/scheduler/install.js +231 -0
- package/dist/scheduler/install.js.map +1 -0
- package/dist/storage/adapter.d.ts +57 -0
- package/dist/storage/adapter.js +5 -0
- package/dist/storage/adapter.js.map +1 -0
- package/dist/storage/mariadb-adapter.d.ts +38 -0
- package/dist/storage/mariadb-adapter.js +327 -0
- package/dist/storage/mariadb-adapter.js.map +1 -0
- package/dist/storage/sqljs-adapter.d.ts +29 -0
- package/dist/storage/sqljs-adapter.js +379 -0
- package/dist/storage/sqljs-adapter.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# sincenety
|
|
2
|
+
|
|
3
|
+
**Claude Code 작업 갈무리 도구** — `sincenety` 한 번 실행으로 마지막 갈무리 이후의 모든 Claude Code 작업을 자동 분석하여 구조화된 기록을 생성합니다.
|
|
4
|
+
|
|
5
|
+
start/stop 없이, 실행 시점 기준으로 소급하여 모든 작업을 정리합니다.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
$ sincenety
|
|
9
|
+
|
|
10
|
+
📋 2026년 4월 7일 화요일 작업 갈무리 (12:00 ~ 18:05)
|
|
11
|
+
총 4개 세션, 1605개 메시지 | 토큰: 9.0Kin / 212.7Kout
|
|
12
|
+
────────────────────────────────────────────────────────
|
|
13
|
+
[claudflare_web] 11:22 ~ 14:49 (3시간 28분, 445msg, 66.4Ktok)
|
|
14
|
+
pathcosmos.com 보안 강화 + 웹 분석 구축
|
|
15
|
+
모델: claude-opus-4-6
|
|
16
|
+
...
|
|
17
|
+
✅ 갈무리 완료. 기록이 저장되었습니다.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 핵심 기능
|
|
23
|
+
|
|
24
|
+
### 소급 갈무리
|
|
25
|
+
|
|
26
|
+
별도 기록 행위 없이, `sincenety` 실행 시 마지막 갈무리 시점 이후의 `~/.claude/` 데이터를 분석하여 프로젝트별/세션별 작업 내용을 자동 재구성합니다.
|
|
27
|
+
|
|
28
|
+
- **세션 JSONL 파싱** — `~/.claude/projects/[project]/[sessionId].jsonl`에서 토큰 사용량, 모델명, 정밀 타임스탬프 추출
|
|
29
|
+
- **history.jsonl 보조 인덱스** — 빠른 세션 목록 조회용
|
|
30
|
+
- **갈무리 포인트** — 매 실행 시 "여기까지 정리했음" 마커를 저장하여 중복 없이 이어서 정리
|
|
31
|
+
|
|
32
|
+
### 풍부한 작업 기록
|
|
33
|
+
|
|
34
|
+
| 항목 | 설명 |
|
|
35
|
+
|------|------|
|
|
36
|
+
| 작업 타이틀 | 첫 사용자 메시지에서 자동 추출 |
|
|
37
|
+
| 작업 설명 | 주요 사용자 메시지 3~5개 연결 |
|
|
38
|
+
| 토큰 사용량 | 입력/출력/캐시 토큰 메시지별 합산 |
|
|
39
|
+
| 작업 시간 | 첫 메시지 ~ 마지막 메시지 정밀 측정 |
|
|
40
|
+
| 사용 모델 | assistant 응답에서 모델명 추출 |
|
|
41
|
+
| 카테고리 | 프로젝트 경로 기반 자동 분류 |
|
|
42
|
+
|
|
43
|
+
### 이메일 리포트
|
|
44
|
+
|
|
45
|
+
Gmail SMTP를 통해 갈무리 리포트를 이메일로 발송합니다. 세션별 컬러 코딩, 토큰 대시보드, 갈무리 요약이 포함된 HTML 이메일입니다.
|
|
46
|
+
|
|
47
|
+
### 자동 스케줄링
|
|
48
|
+
|
|
49
|
+
오후 6시(기본)에 자동으로 갈무리 + 이메일 발송. macOS는 launchd, Linux는 crontab을 자동으로 설정합니다.
|
|
50
|
+
|
|
51
|
+
### 암호화 저장
|
|
52
|
+
|
|
53
|
+
모든 데이터는 AES-256-GCM으로 암호화되어 `~/.sincenety/sincenety.db`에 저장됩니다. 머신 바운드 키(hostname + username + 랜덤 salt)를 기본으로 사용하며, 선택적으로 passphrase를 설정할 수 있습니다.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 설치 및 사용
|
|
58
|
+
|
|
59
|
+
### 설치
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# npx로 즉시 실행 (향후 npm publish 후)
|
|
63
|
+
npx sincenety@latest
|
|
64
|
+
|
|
65
|
+
# 또는 로컬 빌드
|
|
66
|
+
git clone <repo-url>
|
|
67
|
+
cd sincenety
|
|
68
|
+
npm install
|
|
69
|
+
npm run build
|
|
70
|
+
npm link # 글로벌 등록
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 기본 사용
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# 갈무리 (마지막 포인트 이후)
|
|
77
|
+
sincenety
|
|
78
|
+
|
|
79
|
+
# 특정 시점부터 갈무리
|
|
80
|
+
sincenety --since "09:00"
|
|
81
|
+
sincenety --since "2026-04-07 09:00"
|
|
82
|
+
|
|
83
|
+
# 빠른 모드 (토큰 추출 없이 history.jsonl만 사용)
|
|
84
|
+
sincenety --no-detail
|
|
85
|
+
|
|
86
|
+
# 작업 로그 조회
|
|
87
|
+
sincenety log
|
|
88
|
+
sincenety log --date 2026-04-06
|
|
89
|
+
sincenety log --week
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 이메일 설정
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 수신 이메일
|
|
96
|
+
sincenety config --email user@gmail.com
|
|
97
|
+
|
|
98
|
+
# SMTP 설정 (Gmail 앱 비밀번호)
|
|
99
|
+
sincenety config --smtp-user sender@gmail.com
|
|
100
|
+
sincenety config --smtp-pass # 프롬프트에서 비밀번호 입력 (쉘 히스토리 노출 방지)
|
|
101
|
+
|
|
102
|
+
# 이메일 발송
|
|
103
|
+
sincenety email
|
|
104
|
+
sincenety email --date 2026-04-06
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> Gmail 앱 비밀번호 생성: https://myaccount.google.com/apppasswords
|
|
108
|
+
|
|
109
|
+
### 자동 갈무리 스케줄
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 오후 6시 자동 갈무리 + 이메일 설치
|
|
113
|
+
sincenety schedule --install
|
|
114
|
+
|
|
115
|
+
# 시간 변경
|
|
116
|
+
sincenety schedule --install --time 19:00
|
|
117
|
+
|
|
118
|
+
# 상태 확인 / 해제
|
|
119
|
+
sincenety schedule --status
|
|
120
|
+
sincenety schedule --uninstall
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Claude Code Skill
|
|
124
|
+
|
|
125
|
+
Claude Code 안에서 `/sincenety`로 직접 호출 가능합니다. `~/.claude/skills/sincenety/SKILL.md`에 등록됩니다.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 아키텍처
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
sincenety/
|
|
133
|
+
├── src/
|
|
134
|
+
│ ├── cli.ts # CLI 진입점 (commander, 5개 서브커맨드)
|
|
135
|
+
│ ├── core/
|
|
136
|
+
│ │ └── gatherer.ts # 갈무리 핵심 로직 (파싱→그룹핑→저장→리포트)
|
|
137
|
+
│ ├── parser/
|
|
138
|
+
│ │ ├── history.ts # ~/.claude/history.jsonl 스트리밍 파서
|
|
139
|
+
│ │ └── session-jsonl.ts # 세션 JSONL 파서 (토큰/모델/타이밍 추출)
|
|
140
|
+
│ ├── grouper/
|
|
141
|
+
│ │ └── session.ts # sessionId+project 기준 그룹핑
|
|
142
|
+
│ ├── storage/
|
|
143
|
+
│ │ ├── adapter.ts # StorageAdapter 인터페이스
|
|
144
|
+
│ │ └── sqljs-adapter.ts # sql.js 구현 (암호화 DB, 자동 마이그레이션)
|
|
145
|
+
│ ├── encryption/
|
|
146
|
+
│ │ ├── key.ts # PBKDF2 키 파생 (머신 바운드 + passphrase)
|
|
147
|
+
│ │ └── crypto.ts # AES-256-GCM encrypt/decrypt
|
|
148
|
+
│ ├── report/
|
|
149
|
+
│ │ ├── terminal.ts # 터미널 출력 포매터
|
|
150
|
+
│ │ └── markdown.ts # 마크다운 리포트 생성
|
|
151
|
+
│ ├── email/
|
|
152
|
+
│ │ ├── sender.ts # nodemailer 이메일 발송
|
|
153
|
+
│ │ └── template.ts # Bright 컬러코딩 HTML 이메일 템플릿
|
|
154
|
+
│ ├── scheduler/
|
|
155
|
+
│ │ └── install.ts # launchd/cron 자동 설치
|
|
156
|
+
│ └── types/
|
|
157
|
+
│ └── sql.js.d.ts # sql.js 타입 정의
|
|
158
|
+
├── tests/
|
|
159
|
+
│ └── encryption.test.ts # 암호화 테스트 (26개)
|
|
160
|
+
├── package.json
|
|
161
|
+
├── tsconfig.json
|
|
162
|
+
├── CLAUDE.md
|
|
163
|
+
└── README.md
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 데이터 흐름
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
~/.claude/history.jsonl ──→ 세션 목록 추출 (sessionId + project)
|
|
170
|
+
│
|
|
171
|
+
▼
|
|
172
|
+
~/.claude/projects/[project]/[sessionId].jsonl ──→ 토큰/모델/타이밍 추출
|
|
173
|
+
│
|
|
174
|
+
▼
|
|
175
|
+
그룹핑 + 요약 생성
|
|
176
|
+
│
|
|
177
|
+
┌──────────────┼──────────────┐
|
|
178
|
+
▼ ▼ ▼
|
|
179
|
+
터미널 출력 DB 저장 (암호화) 이메일 발송
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### DB 스키마
|
|
183
|
+
|
|
184
|
+
**4개 테이블:**
|
|
185
|
+
|
|
186
|
+
| 테이블 | 설명 |
|
|
187
|
+
|--------|------|
|
|
188
|
+
| `sessions` | 세션별 작업 기록 (22개 컬럼 — 토큰, 시간, 타이틀, 설명, 모델 등) |
|
|
189
|
+
| `gather_reports` | 갈무리 실행마다 리포트 저장 (마크다운 + JSON) |
|
|
190
|
+
| `checkpoints` | 갈무리 포인트 (마지막 처리 timestamp) |
|
|
191
|
+
| `config` | 설정 (이메일, SMTP 등) |
|
|
192
|
+
|
|
193
|
+
DB 파일: `~/.sincenety/sincenety.db` (AES-256-GCM 암호화, 0600 권한)
|
|
194
|
+
|
|
195
|
+
### 암호화 상세
|
|
196
|
+
|
|
197
|
+
- **알고리즘**: AES-256-GCM (인증된 암호화)
|
|
198
|
+
- **키 파생**: PBKDF2 (SHA-256, 100,000 iterations)
|
|
199
|
+
- **키 소스**: `hostname + username + 랜덤 salt` (머신 바운드)
|
|
200
|
+
- **Salt**: `~/.sincenety/sincenety.salt` (32바이트 랜덤, 설치 시 1회 생성, 0600 권한)
|
|
201
|
+
- **파일 포맷**: `[4B magic "SNCT"][12B IV][ciphertext][16B auth tag]`
|
|
202
|
+
- **선택적 passphrase**: `sincenety config --set-passphrase` (향후 구현 예정)
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 기술 스택
|
|
207
|
+
|
|
208
|
+
| 구성요소 | 기술 |
|
|
209
|
+
|---------|------|
|
|
210
|
+
| 언어 | TypeScript (ESM, Node16 모듈) |
|
|
211
|
+
| 런타임 | Node.js >= 18 |
|
|
212
|
+
| CLI | commander |
|
|
213
|
+
| DB | sql.js (WASM SQLite, native 의존성 없음) |
|
|
214
|
+
| 암호화 | Node.js 내장 crypto (AES-256-GCM) |
|
|
215
|
+
| 이메일 | nodemailer (Gmail SMTP) |
|
|
216
|
+
| 테스트 | vitest |
|
|
217
|
+
|
|
218
|
+
### 의존성 (최소)
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
dependencies:
|
|
222
|
+
commander # CLI 파싱
|
|
223
|
+
nodemailer # 이메일 발송
|
|
224
|
+
sql.js # WASM SQLite
|
|
225
|
+
|
|
226
|
+
devDependencies:
|
|
227
|
+
typescript
|
|
228
|
+
vitest
|
|
229
|
+
tsx
|
|
230
|
+
@types/node
|
|
231
|
+
@types/nodemailer
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 개발 가이드
|
|
237
|
+
|
|
238
|
+
### 빌드 및 실행
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npm install # 의존성 설치
|
|
242
|
+
npm run build # TypeScript 컴파일 (dist/)
|
|
243
|
+
npm run dev # tsx로 개발 실행
|
|
244
|
+
npm test # vitest 테스트
|
|
245
|
+
node dist/cli.js # 직접 실행
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 테스트
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# 암호화 테스트 (26개)
|
|
252
|
+
npx vitest run tests/encryption.test.ts
|
|
253
|
+
|
|
254
|
+
# 전체 테스트
|
|
255
|
+
npm test
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 로컬 npx 테스트
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npx . # 현재 디렉토리를 npx로 실행
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 개발 이력
|
|
267
|
+
|
|
268
|
+
### v0.1 (2026-04-07) — MVP
|
|
269
|
+
|
|
270
|
+
- `history.jsonl` 기반 소급 갈무리
|
|
271
|
+
- sql.js 암호화 DB (AES-256-GCM)
|
|
272
|
+
- 세션 그룹핑 (sessionId + project)
|
|
273
|
+
- 터미널 리포트
|
|
274
|
+
- Claude Code Skill 등록 (`/sincenety`)
|
|
275
|
+
|
|
276
|
+
### v0.1.1 (2026-04-07) — 보안 강화
|
|
277
|
+
|
|
278
|
+
보안 감사 결과 CRITICAL 2개 + HIGH 3개 발견, 전부 수정:
|
|
279
|
+
- 랜덤 salt 생성/저장 (`~/.sincenety/sincenety.salt`)
|
|
280
|
+
- DB/salt 파일 권한 0600, 디렉토리 0700
|
|
281
|
+
- 복호화 실패 시 에러 메시지 (무음 DB 교체 제거)
|
|
282
|
+
- CLI 입력 검증 (날짜, 경로, 빈 값)
|
|
283
|
+
- MariaDB URI 보안 (환경변수 참조)
|
|
284
|
+
|
|
285
|
+
### v0.2 (2026-04-07) — 풍부한 작업 기록 + 이메일 + 스케줄링
|
|
286
|
+
|
|
287
|
+
데이터소스 전환: `history.jsonl` → `~/.claude/projects/[project]/[sessionId].jsonl`
|
|
288
|
+
|
|
289
|
+
- **토큰 추적**: 메시지별 input/output/cache 토큰 합산
|
|
290
|
+
- **모델 추적**: assistant 응답에서 사용 모델 추출
|
|
291
|
+
- **정밀 타이밍**: ISO 8601 타임스탬프 기반 (밀리초 정밀도)
|
|
292
|
+
- **DB 스키마 v2**: sessions 14개 컬럼 추가, gather_reports/config 테이블
|
|
293
|
+
- **자동 마이그레이션**: v1 → v2 ALTER TABLE ADD COLUMN
|
|
294
|
+
- **마크다운 리포트**: 토큰/모델 포함 풍부한 리포트
|
|
295
|
+
- **이메일 발송**: nodemailer + Gmail SMTP
|
|
296
|
+
- **HTML 이메일 템플릿**: Bright 컬러코딩 대시보드
|
|
297
|
+
- Section 01: 세션 갈무리 요약 (최상단)
|
|
298
|
+
- Section 02: 오늘의 수치 (토큰/비용 대시보드)
|
|
299
|
+
- Section 03: 세션별 상세 작업 로그
|
|
300
|
+
- Section 04: 하루의 성과
|
|
301
|
+
- **자동 스케줄링**: launchd (macOS) / crontab (Linux) 자동 설치
|
|
302
|
+
- **`--auto` 플래그**: 갈무리 + 이메일 자동 발송 (스케줄러용)
|
|
303
|
+
|
|
304
|
+
### 향후 계획
|
|
305
|
+
|
|
306
|
+
- [ ] npm publish → `npx sincenety@latest` 배포
|
|
307
|
+
- [ ] passphrase 설정 기능 완성
|
|
308
|
+
- [ ] 유사 작업 매칭 (TF-IDF 기반)
|
|
309
|
+
- [ ] MariaDB/PostgreSQL 외부 DB 연결 (현재 비활성화)
|
|
310
|
+
- [ ] 주간/월간 요약 리포트
|
|
311
|
+
- [ ] ccusage 연동 (토큰 비용 자동 계산)
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 프로젝트 수치 (2026-04-07 기준)
|
|
316
|
+
|
|
317
|
+
| 지표 | 수치 |
|
|
318
|
+
|------|------|
|
|
319
|
+
| TypeScript 소스 파일 | 15개 |
|
|
320
|
+
| 총 코드 라인 | 3,013줄 |
|
|
321
|
+
| 암호화 테스트 | 26/26 통과 |
|
|
322
|
+
| CLI 명령어 | 5개 (갈무리, log, config, email, schedule) |
|
|
323
|
+
| DB 테이블 | 4개 |
|
|
324
|
+
| 의존성 (production) | 3개 (commander, nodemailer, sql.js) |
|
|
325
|
+
| 보안 이슈 발견/수정 | 8/8 |
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 라이선스
|
|
330
|
+
|
|
331
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { gather } from "./core/gatherer.js";
|
|
7
|
+
import { SqlJsAdapter } from "./storage/sqljs-adapter.js";
|
|
8
|
+
import { formatGatherReport, formatLogReport, } from "./report/terminal.js";
|
|
9
|
+
import { sendGatherEmail, isEmailConfigured } from "./email/sender.js";
|
|
10
|
+
import { installSchedule, uninstallSchedule, getScheduleStatus, } from "./scheduler/install.js";
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name("sincenety")
|
|
14
|
+
.description("Claude Code 작업 갈무리 도구")
|
|
15
|
+
.version("0.1.0");
|
|
16
|
+
// 기본 명령: 갈무리
|
|
17
|
+
program
|
|
18
|
+
.argument("[dummy]", "", "") // commander requires this for default action
|
|
19
|
+
.option("--since <time>", "갈무리 시작 시점 (예: '09:00', '2026-04-07 09:00')")
|
|
20
|
+
.option("--history <path>", "history.jsonl 경로 (기본: ~/.claude/history.jsonl)")
|
|
21
|
+
.option("--detail", "세션 JSONL 상세 모드 (토큰/시간 추출)", true)
|
|
22
|
+
.option("--no-detail", "history.jsonl만 사용 (빠른 모드)")
|
|
23
|
+
.option("--auto", "자동 갈무리 + 이메일 발송 (스케줄러용)")
|
|
24
|
+
.action(async (_, options) => {
|
|
25
|
+
// "log" 서브커맨드가 아닌 경우에만 갈무리 실행
|
|
26
|
+
if (program.args[0] === "log")
|
|
27
|
+
return;
|
|
28
|
+
let sinceTimestamp;
|
|
29
|
+
if (options.since) {
|
|
30
|
+
sinceTimestamp = parseSinceOption(options.since);
|
|
31
|
+
}
|
|
32
|
+
// --history 경로 검증
|
|
33
|
+
let historyPath;
|
|
34
|
+
if (options.history) {
|
|
35
|
+
historyPath = resolve(options.history);
|
|
36
|
+
if (!existsSync(historyPath)) {
|
|
37
|
+
console.error(` ❌ history 파일을 찾을 수 없습니다: ${historyPath}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const storage = new SqlJsAdapter();
|
|
42
|
+
try {
|
|
43
|
+
await storage.initialize();
|
|
44
|
+
const result = await gather(storage, {
|
|
45
|
+
sinceTimestamp,
|
|
46
|
+
historyPath,
|
|
47
|
+
useSessionJsonl: options.detail !== false,
|
|
48
|
+
});
|
|
49
|
+
console.log(formatGatherReport(result));
|
|
50
|
+
// --auto: 갈무리 후 이메일 발송 (설정된 경우)
|
|
51
|
+
if (options.auto) {
|
|
52
|
+
const configured = await isEmailConfigured(storage);
|
|
53
|
+
if (configured) {
|
|
54
|
+
try {
|
|
55
|
+
await sendGatherEmail(storage);
|
|
56
|
+
}
|
|
57
|
+
catch (emailErr) {
|
|
58
|
+
console.error(` 이메일 발송 실패: ${emailErr instanceof Error ? emailErr.message : String(emailErr)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await storage.close();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// log 서브커맨드: 저장된 기록 조회
|
|
72
|
+
program
|
|
73
|
+
.command("log")
|
|
74
|
+
.description("저장된 작업 기록 조회")
|
|
75
|
+
.option("--date <date>", "조회 날짜 (예: today, 2026-04-07)", "today")
|
|
76
|
+
.option("--week", "최근 7일 조회")
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
const storage = new SqlJsAdapter();
|
|
79
|
+
try {
|
|
80
|
+
await storage.initialize();
|
|
81
|
+
if (options.week) {
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const weekAgo = new Date(now.getTime() - 7 * 86400000);
|
|
84
|
+
weekAgo.setHours(0, 0, 0, 0);
|
|
85
|
+
const records = await storage.getSessionsByRange(weekAgo.getTime(), now.getTime());
|
|
86
|
+
console.log(formatLogReport(records, "최근 7일"));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const dateStr = options.date === "today"
|
|
90
|
+
? new Date().toISOString().slice(0, 10)
|
|
91
|
+
: options.date;
|
|
92
|
+
// 날짜 형식 검증
|
|
93
|
+
if (isNaN(new Date(dateStr).getTime())) {
|
|
94
|
+
console.error(` ❌ 유효하지 않은 날짜 형식입니다: ${options.date}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const records = await storage.getSessionsByDate(dateStr);
|
|
98
|
+
console.log(formatLogReport(records, dateStr));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await storage.close();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// config 서브커맨드: 설정 관리
|
|
110
|
+
program
|
|
111
|
+
.command("config")
|
|
112
|
+
.description("설정 관리")
|
|
113
|
+
// MariaDB/외부 DB 연결은 향후 개발 예정 — 현재 비활성화
|
|
114
|
+
.option("--set-passphrase", "암호화 passphrase 설정")
|
|
115
|
+
.option("--email <address>", "수신 이메일 주소 설정")
|
|
116
|
+
.option("--smtp-host <host>", "SMTP 호스트 (기본: smtp.gmail.com)")
|
|
117
|
+
.option("--smtp-port <port>", "SMTP 포트 (기본: 587)")
|
|
118
|
+
.option("--smtp-user <user>", "SMTP 사용자 (발신 이메일)")
|
|
119
|
+
.option("--smtp-pass", "SMTP 앱 비밀번호 설정 (프롬프트 입력)")
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
const storage = new SqlJsAdapter();
|
|
122
|
+
try {
|
|
123
|
+
await storage.initialize();
|
|
124
|
+
let changed = false;
|
|
125
|
+
if (options.setPassphrase) {
|
|
126
|
+
console.log(" Passphrase 설정은 아직 준비 중입니다. (coming soon)");
|
|
127
|
+
}
|
|
128
|
+
if (options.email) {
|
|
129
|
+
await storage.setConfig("email", options.email);
|
|
130
|
+
console.log(` email = ${options.email}`);
|
|
131
|
+
changed = true;
|
|
132
|
+
}
|
|
133
|
+
if (options.smtpHost) {
|
|
134
|
+
await storage.setConfig("smtp_host", options.smtpHost);
|
|
135
|
+
console.log(` smtp_host = ${options.smtpHost}`);
|
|
136
|
+
changed = true;
|
|
137
|
+
}
|
|
138
|
+
if (options.smtpPort) {
|
|
139
|
+
await storage.setConfig("smtp_port", options.smtpPort);
|
|
140
|
+
console.log(` smtp_port = ${options.smtpPort}`);
|
|
141
|
+
changed = true;
|
|
142
|
+
}
|
|
143
|
+
if (options.smtpUser) {
|
|
144
|
+
await storage.setConfig("smtp_user", options.smtpUser);
|
|
145
|
+
console.log(` smtp_user = ${options.smtpUser}`);
|
|
146
|
+
changed = true;
|
|
147
|
+
}
|
|
148
|
+
if (options.smtpPass) {
|
|
149
|
+
// 보안을 위해 프롬프트로 입력받음
|
|
150
|
+
const password = await promptPassword(" SMTP 앱 비밀번호: ");
|
|
151
|
+
if (password) {
|
|
152
|
+
await storage.setConfig("smtp_pass", password);
|
|
153
|
+
console.log(" smtp_pass = ********");
|
|
154
|
+
changed = true;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log(" 비밀번호가 입력되지 않았습니다.");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (changed) {
|
|
161
|
+
console.log(" 설정이 저장되었습니다.");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await storage.close();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// email 서브커맨드: 갈무리 리포트 이메일 발송
|
|
169
|
+
program
|
|
170
|
+
.command("email")
|
|
171
|
+
.description("갈무리 리포트 이메일 발송")
|
|
172
|
+
.option("--date <date>", "특정 날짜 리포트 발송 (예: 2026-04-06)")
|
|
173
|
+
.action(async (options) => {
|
|
174
|
+
const storage = new SqlJsAdapter();
|
|
175
|
+
try {
|
|
176
|
+
await storage.initialize();
|
|
177
|
+
if (options.date) {
|
|
178
|
+
// 날짜로 리포트 조회
|
|
179
|
+
const reports = await storage.getGatherReportsByDate(options.date);
|
|
180
|
+
if (reports.length === 0) {
|
|
181
|
+
console.log(` ${options.date}에 해당하는 갈무리 리포트가 없습니다.`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// 가장 최근 리포트 발송 (해당 날짜)
|
|
185
|
+
// sendGatherEmail은 latest만 지원하므로 ID로 매칭
|
|
186
|
+
const target = reports[0]; // 가장 최근 (DESC)
|
|
187
|
+
await sendGatherEmail(storage, target.id);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
await sendGatherEmail(storage);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
await storage.close();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
// schedule 서브커맨드: 자동 갈무리 스케줄 관리
|
|
202
|
+
program
|
|
203
|
+
.command("schedule")
|
|
204
|
+
.description("자동 갈무리 스케줄 관리")
|
|
205
|
+
.option("--install", "스케줄 설치 (기본 18:00)")
|
|
206
|
+
.option("--uninstall", "스케줄 해제")
|
|
207
|
+
.option("--status", "스케줄 상태 확인")
|
|
208
|
+
.option("--time <time>", "실행 시간 (예: 19:00)", "18:00")
|
|
209
|
+
.action(async (options) => {
|
|
210
|
+
try {
|
|
211
|
+
if (options.uninstall) {
|
|
212
|
+
await uninstallSchedule();
|
|
213
|
+
}
|
|
214
|
+
else if (options.status) {
|
|
215
|
+
const status = await getScheduleStatus();
|
|
216
|
+
console.log(` 스케줄 상태: ${status}`);
|
|
217
|
+
}
|
|
218
|
+
else if (options.install) {
|
|
219
|
+
await installSchedule({ time: options.time });
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(" 사용법:");
|
|
223
|
+
console.log(" sincenety schedule --install");
|
|
224
|
+
console.log(" sincenety schedule --install --time 19:00");
|
|
225
|
+
console.log(" sincenety schedule --uninstall");
|
|
226
|
+
console.log(" sincenety schedule --status");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
function promptPassword(prompt) {
|
|
235
|
+
return new Promise((resolve) => {
|
|
236
|
+
const rl = createInterface({
|
|
237
|
+
input: process.stdin,
|
|
238
|
+
output: process.stdout,
|
|
239
|
+
});
|
|
240
|
+
// 비밀번호 입력 시 에코 숨기기
|
|
241
|
+
if (process.stdin.isTTY) {
|
|
242
|
+
process.stdout.write(prompt);
|
|
243
|
+
const stdin = process.stdin;
|
|
244
|
+
const wasRaw = stdin.isRaw;
|
|
245
|
+
stdin.setRawMode(true);
|
|
246
|
+
let password = "";
|
|
247
|
+
const onData = (ch) => {
|
|
248
|
+
const c = ch.toString("utf8");
|
|
249
|
+
if (c === "\n" || c === "\r") {
|
|
250
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
251
|
+
stdin.removeListener("data", onData);
|
|
252
|
+
process.stdout.write("\n");
|
|
253
|
+
rl.close();
|
|
254
|
+
resolve(password);
|
|
255
|
+
}
|
|
256
|
+
else if (c === "\u0003") {
|
|
257
|
+
// Ctrl+C
|
|
258
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
259
|
+
stdin.removeListener("data", onData);
|
|
260
|
+
rl.close();
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
else if (c === "\u007f" || c === "\b") {
|
|
264
|
+
// Backspace
|
|
265
|
+
if (password.length > 0) {
|
|
266
|
+
password = password.slice(0, -1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
password += c;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
stdin.on("data", onData);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Non-TTY: readline으로 한 줄 읽기
|
|
277
|
+
rl.question(prompt, (answer) => {
|
|
278
|
+
rl.close();
|
|
279
|
+
resolve(answer);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
function parseSinceOption(value) {
|
|
285
|
+
if (!value || !value.trim()) {
|
|
286
|
+
console.error(" ❌ --since 값이 비어 있습니다.");
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
// "09:00" 형태 — 오늘 해당 시간으로 해석
|
|
290
|
+
const timeMatch = value.match(/^(\d{1,2}):(\d{2})$/);
|
|
291
|
+
if (timeMatch) {
|
|
292
|
+
const now = new Date();
|
|
293
|
+
now.setHours(parseInt(timeMatch[1]), parseInt(timeMatch[2]), 0, 0);
|
|
294
|
+
return now.getTime();
|
|
295
|
+
}
|
|
296
|
+
// 일반 날짜/시간 문자열
|
|
297
|
+
const parsed = new Date(value);
|
|
298
|
+
if (!isNaN(parsed.getTime())) {
|
|
299
|
+
return parsed.getTime();
|
|
300
|
+
}
|
|
301
|
+
console.error(` ❌ 시간 형식을 인식할 수 없습니다: ${value}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
program.parse();
|
|
305
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EACL,kBAAkB,EAClB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,uBAAuB,CAAC;KACpC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,aAAa;AACb,OAAO;KACJ,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,6CAA6C;KACzE,MAAM,CAAC,gBAAgB,EAAE,4CAA4C,CAAC;KACtE,MAAM,CAAC,kBAAkB,EAAE,gDAAgD,CAAC;KAC5E,MAAM,CAAC,UAAU,EAAE,2BAA2B,EAAE,IAAI,CAAC;KACrD,MAAM,CAAC,aAAa,EAAE,2BAA2B,CAAC;KAClD,MAAM,CAAC,QAAQ,EAAE,yBAAyB,CAAC;KAC3C,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE;IAC3B,8BAA8B;IAC9B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK;QAAE,OAAO;IAEtC,IAAI,cAAkC,CAAC;IACvC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,cAAc,GAAG,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,kBAAkB;IAClB,IAAI,WAA+B,CAAC;IACpC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,8BAA8B,WAAW,EAAE,CAAC,CAAC;YAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE;YACnC,cAAc;YACd,WAAW;YACX,eAAe,EAAE,OAAO,CAAC,MAAM,KAAK,KAAK;SAC1C,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;QAExC,gCAAgC;QAChC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACpD,IAAI,UAAU,EAAE,CAAC;gBACf,IAAI,CAAC;oBACH,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;gBACjC,CAAC;gBAAC,OAAO,QAAQ,EAAE,CAAC;oBAClB,OAAO,CAAC,KAAK,CACX,gBAAgB,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAClF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CACX,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC1D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,uBAAuB;AACvB,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,cAAc,CAAC;KAC3B,MAAM,CAAC,eAAe,EAAE,8BAA8B,EAAE,OAAO,CAAC;KAChE,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC;KAC5B,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAE3B,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;YACvD,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAC9C,OAAO,CAAC,OAAO,EAAE,EACjB,GAAG,CAAC,OAAO,EAAE,CACd,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GACX,OAAO,CAAC,IAAI,KAAK,OAAO;gBACtB,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;gBACvC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;YACnB,WAAW;YACX,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;gBACvC,OAAO,CAAC,KAAK,CAAC,yBAAyB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;gBACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CACX,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC1D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,sBAAsB;AACtB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,OAAO,CAAC;IACrB,uCAAuC;KACtC,MAAM,CAAC,kBAAkB,EAAE,mBAAmB,CAAC;KAC/C,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAAC;KAC3C,MAAM,CAAC,oBAAoB,EAAE,+BAA+B,CAAC;KAC7D,MAAM,CAAC,oBAAoB,EAAE,mBAAmB,CAAC;KACjD,MAAM,CAAC,oBAAoB,EAAE,mBAAmB,CAAC;KACjD,MAAM,CAAC,aAAa,EAAE,0BAA0B,CAAC;KACjD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3B,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CACT,4CAA4C,CAC7C,CAAC;QACJ,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,aAAa,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;YAC1C,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,oBAAoB;YACpB,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,iBAAiB,CAAC,CAAC;YACzD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;gBAC/C,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;gBACtC,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAED,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,8BAA8B;AAC9B,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,gBAAgB,CAAC;KAC7B,MAAM,CAAC,eAAe,EAAE,8BAA8B,CAAC;KACvD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAE3B,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,aAAa;YACb,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,sBAAsB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,uBAAuB,CAAC,CAAC;gBACtD,OAAO;YACT,CAAC;YACD,uBAAuB;YACvB,wCAAwC;YACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe;YAC1C,MAAM,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CACX,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC1D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,gCAAgC;AAChC,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,eAAe,CAAC;KAC5B,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC;KACxC,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC;KAC/B,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC;KAC/B,MAAM,CAAC,eAAe,EAAE,kBAAkB,EAAE,OAAO,CAAC;KACpD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,iBAAiB,EAAE,CAAC;QAC5B,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,MAAM,iBAAiB,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,EAAE,CAAC,CAAC;QACrC,CAAC;aAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;YAC7D,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CACX,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC1D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,SAAS,cAAc,CAAC,MAAc;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,eAAe,CAAC;YACzB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,mBAAmB;QACnB,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;YAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;YAC3B,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACvB,IAAI,QAAQ,GAAG,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,EAAE;gBAC5B,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC9B,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;oBAC7B,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;oBAClC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC3B,EAAE,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACpB,CAAC;qBAAM,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;oBAC1B,SAAS;oBACT,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;oBAClC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBACrC,EAAE,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;qBAAM,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;oBACxC,YAAY;oBACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACxB,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;oBACnC,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,QAAQ,IAAI,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC,CAAC;YACF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,6BAA6B;YAC7B,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;gBAC7B,EAAE,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,6BAA6B;IAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACrD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACnE,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;IAED,eAAe;IACf,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 갈무리 핵심 로직 — 파싱 → 그룹핑 → 저장 → 리포트
|
|
3
|
+
*/
|
|
4
|
+
import { type SessionGroup } from "../grouper/session.js";
|
|
5
|
+
import type { StorageAdapter } from "../storage/adapter.js";
|
|
6
|
+
export interface GatherOptions {
|
|
7
|
+
sinceTimestamp?: number;
|
|
8
|
+
historyPath?: string;
|
|
9
|
+
/** 세션 JSONL 파서 사용 여부 (v0.2) — 없으면 history.jsonl만 사용 */
|
|
10
|
+
useSessionJsonl?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface GatherResult {
|
|
13
|
+
sessions: SessionGroup[];
|
|
14
|
+
fromTimestamp: number;
|
|
15
|
+
toTimestamp: number;
|
|
16
|
+
isFirstRun: boolean;
|
|
17
|
+
reportId?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function gather(storage: StorageAdapter, options?: GatherOptions): Promise<GatherResult>;
|