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.
Files changed (47) hide show
  1. package/README.md +331 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +305 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/core/gatherer.d.ts +19 -0
  6. package/dist/core/gatherer.js +125 -0
  7. package/dist/core/gatherer.js.map +1 -0
  8. package/dist/email/sender.d.ts +14 -0
  9. package/dist/email/sender.js +137 -0
  10. package/dist/email/sender.js.map +1 -0
  11. package/dist/email/template.d.ts +47 -0
  12. package/dist/email/template.js +342 -0
  13. package/dist/email/template.js.map +1 -0
  14. package/dist/encryption/crypto.d.ts +13 -0
  15. package/dist/encryption/crypto.js +44 -0
  16. package/dist/encryption/crypto.js.map +1 -0
  17. package/dist/encryption/key.d.ts +8 -0
  18. package/dist/encryption/key.js +42 -0
  19. package/dist/encryption/key.js.map +1 -0
  20. package/dist/grouper/session.d.ts +33 -0
  21. package/dist/grouper/session.js +63 -0
  22. package/dist/grouper/session.js.map +1 -0
  23. package/dist/parser/history.d.ts +12 -0
  24. package/dist/parser/history.js +29 -0
  25. package/dist/parser/history.js.map +1 -0
  26. package/dist/parser/session-jsonl.d.ts +40 -0
  27. package/dist/parser/session-jsonl.js +242 -0
  28. package/dist/parser/session-jsonl.js.map +1 -0
  29. package/dist/report/markdown.d.ts +5 -0
  30. package/dist/report/markdown.js +75 -0
  31. package/dist/report/markdown.js.map +1 -0
  32. package/dist/report/terminal.d.ts +7 -0
  33. package/dist/report/terminal.js +114 -0
  34. package/dist/report/terminal.js.map +1 -0
  35. package/dist/scheduler/install.d.ts +19 -0
  36. package/dist/scheduler/install.js +231 -0
  37. package/dist/scheduler/install.js.map +1 -0
  38. package/dist/storage/adapter.d.ts +57 -0
  39. package/dist/storage/adapter.js +5 -0
  40. package/dist/storage/adapter.js.map +1 -0
  41. package/dist/storage/mariadb-adapter.d.ts +38 -0
  42. package/dist/storage/mariadb-adapter.js +327 -0
  43. package/dist/storage/mariadb-adapter.js.map +1 -0
  44. package/dist/storage/sqljs-adapter.d.ts +29 -0
  45. package/dist/storage/sqljs-adapter.js +379 -0
  46. package/dist/storage/sqljs-adapter.js.map +1 -0
  47. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -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>;