sincenety 0.1.1 → 0.2.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.ko.md +73 -25
- package/README.md +53 -19
- package/dist/cli.js +237 -4
- package/dist/cli.js.map +1 -1
- package/dist/core/gatherer.js +69 -25
- package/dist/core/gatherer.js.map +1 -1
- package/dist/core/summarizer.d.ts +20 -0
- package/dist/core/summarizer.js +215 -0
- package/dist/core/summarizer.js.map +1 -0
- package/dist/email/sender.js +33 -24
- package/dist/email/sender.js.map +1 -1
- package/dist/email/template.d.ts +1 -0
- package/dist/email/template.js +42 -13
- package/dist/email/template.js.map +1 -1
- package/dist/grouper/session.d.ts +11 -0
- package/dist/grouper/session.js.map +1 -1
- package/dist/parser/session-jsonl.d.ts +11 -0
- package/dist/parser/session-jsonl.js +57 -8
- package/dist/parser/session-jsonl.js.map +1 -1
- package/dist/report/terminal.d.ts +1 -1
- package/dist/report/terminal.js +202 -42
- package/dist/report/terminal.js.map +1 -1
- package/dist/storage/adapter.d.ts +21 -0
- package/dist/storage/sqljs-adapter.d.ts +7 -1
- package/dist/storage/sqljs-adapter.js +110 -2
- package/dist/storage/sqljs-adapter.js.map +1 -1
- package/package.json +2 -1
package/README.ko.md
CHANGED
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
> **[English Documentation (README.md)](./README.md)**
|
|
4
4
|
|
|
5
|
-
**Claude Code 작업 갈무리 도구** — `sincenety` 한 번 실행으로
|
|
5
|
+
**Claude Code 작업 갈무리 도구** — `sincenety` 한 번 실행으로 오늘 하루의 모든 Claude Code 작업을 자동 분석하여 구조화된 기록을 생성합니다.
|
|
6
6
|
|
|
7
|
-
start/stop 없이,
|
|
7
|
+
start/stop 없이, 오늘 00:00부터 현재까지의 모든 작업을 소급 정리합니다 (upsert로 중복 방지).
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
$ sincenety
|
|
11
11
|
|
|
12
|
-
📋 2026년 4월 7일 화요일 작업 갈무리 (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
📋 2026년 4월 7일 화요일 작업 갈무리 (00:00 ~ 18:05)
|
|
13
|
+
|
|
14
|
+
┌─────────────────────────────────────────────────┐
|
|
15
|
+
│ 총 세션: 4개 │ 메시지: 1,605개 │ 토큰: 221.7K │
|
|
16
|
+
└─────────────────────────────────────────────────┘
|
|
17
|
+
|
|
18
|
+
┌──────────────┬───────────────┬────────┬─────────┬───────────────────────────────┐
|
|
19
|
+
│ 프로젝트 │ 시간 │ 메시지 │ 토큰 │ 작업 내용 │
|
|
20
|
+
├──────────────┼───────────────┼────────┼─────────┼───────────────────────────────┤
|
|
21
|
+
│ claudflare │ 11:22 ~ 14:49│ 445 │ 66.4K │ 보안 강화 + 웹 분석 구축 │
|
|
22
|
+
│ sincenety │ 15:01 ~ 18:05│ 312 │ 48.2K │ AI 요약 일일보고 시스템 │
|
|
23
|
+
│ ... │ │ │ │ │
|
|
24
|
+
└──────────────┴───────────────┴────────┴─────────┴───────────────────────────────┘
|
|
25
|
+
|
|
19
26
|
✅ 갈무리 완료. 기록이 저장되었습니다.
|
|
20
27
|
```
|
|
21
28
|
|
|
@@ -25,9 +32,10 @@ $ sincenety
|
|
|
25
32
|
|
|
26
33
|
### 소급 갈무리
|
|
27
34
|
|
|
28
|
-
별도 기록 행위 없이, `sincenety` 실행 시
|
|
35
|
+
별도 기록 행위 없이, `sincenety` 실행 시 오늘 00:00부터 현재까지의 `~/.claude/` 데이터를 분석하여 프로젝트별/세션별 작업 내용을 자동 재구성합니다. 하루 전체를 수집하되 upsert로 중복을 방지합니다.
|
|
29
36
|
|
|
30
|
-
-
|
|
37
|
+
- **대화 턴 분석** — 사용자 입력 + 어시스턴트 응답 쌍(`conversationTurns`)을 함께 수집하여 정밀한 작업 내용 파악
|
|
38
|
+
- **세션 JSONL 파싱** — `~/.claude/projects/[project]/[sessionId].jsonl`에서 토큰 사용량, 모델명, 정밀 타임스탬프, 대화 턴 추출
|
|
31
39
|
- **history.jsonl 보조 인덱스** — 빠른 세션 목록 조회용
|
|
32
40
|
- **갈무리 포인트** — 매 실행 시 "여기까지 정리했음" 마커를 저장하여 중복 없이 이어서 정리
|
|
33
41
|
|
|
@@ -42,9 +50,16 @@ $ sincenety
|
|
|
42
50
|
| 사용 모델 | assistant 응답에서 모델명 추출 |
|
|
43
51
|
| 카테고리 | 프로젝트 경로 기반 자동 분류 |
|
|
44
52
|
|
|
53
|
+
### AI 요약 일일보고
|
|
54
|
+
|
|
55
|
+
Claude Code 세션 자체를 요약 엔진으로 활용합니다. `--json` 플래그로 대화 턴 포함 구조화 JSON을 출력하면, SKILL.md가 Claude Code에게 직접 요약 생성을 지시합니다. 외부 API 키 없이도 동작하며, `ANTHROPIC_API_KEY`가 있으면 Claude API 요약도 가능합니다.
|
|
56
|
+
|
|
57
|
+
- **`save-daily`** — AI가 생성한 일일보고를 DB에 저장
|
|
58
|
+
- **`report`** — 일일/주간/월간 보고 조회 (주간/월간은 일일보고를 종합)
|
|
59
|
+
|
|
45
60
|
### 이메일 리포트
|
|
46
61
|
|
|
47
|
-
Gmail SMTP를 통해 갈무리 리포트를 이메일로 발송합니다. 세션별 컬러 코딩, 토큰 대시보드, 갈무리 요약이 포함된 HTML 이메일입니다.
|
|
62
|
+
Gmail SMTP를 통해 갈무리 리포트를 이메일로 발송합니다. 세션별 컬러 코딩, 토큰 대시보드, 갈무리 요약이 포함된 HTML 이메일입니다. wrapUp 데이터(outcome, flow, significance)를 반영하며, XML/시스템 태그가 정리된 깔끔한 이메일을 생성합니다.
|
|
48
63
|
|
|
49
64
|
### 자동 스케줄링
|
|
50
65
|
|
|
@@ -75,13 +90,16 @@ npm link # 글로벌 등록
|
|
|
75
90
|
### 기본 사용
|
|
76
91
|
|
|
77
92
|
```bash
|
|
78
|
-
# 갈무리 (
|
|
93
|
+
# 갈무리 (오늘 00:00부터 현재까지)
|
|
79
94
|
sincenety
|
|
80
95
|
|
|
81
96
|
# 특정 시점부터 갈무리
|
|
82
97
|
sincenety --since "09:00"
|
|
83
98
|
sincenety --since "2026-04-07 09:00"
|
|
84
99
|
|
|
100
|
+
# 구조화 JSON 출력 (대화 턴 포함, AI 요약 파이프라인용)
|
|
101
|
+
sincenety --json
|
|
102
|
+
|
|
85
103
|
# 빠른 모드 (토큰 추출 없이 history.jsonl만 사용)
|
|
86
104
|
sincenety --no-detail
|
|
87
105
|
|
|
@@ -89,6 +107,15 @@ sincenety --no-detail
|
|
|
89
107
|
sincenety log
|
|
90
108
|
sincenety log --date 2026-04-06
|
|
91
109
|
sincenety log --week
|
|
110
|
+
|
|
111
|
+
# AI 요약 일일보고 저장 (stdin으로 JSON 입력)
|
|
112
|
+
sincenety save-daily < daily_report.json
|
|
113
|
+
|
|
114
|
+
# 보고 조회
|
|
115
|
+
sincenety report # 오늘 일일보고
|
|
116
|
+
sincenety report --week # 주간 보고
|
|
117
|
+
sincenety report --month # 월간 보고
|
|
118
|
+
sincenety report --date 2026-04-06 # 특정일 보고
|
|
92
119
|
```
|
|
93
120
|
|
|
94
121
|
### 이메일 설정
|
|
@@ -133,12 +160,13 @@ Claude Code 안에서 `/sincenety`로 직접 호출 가능합니다. `~/.claude/
|
|
|
133
160
|
```
|
|
134
161
|
sincenety/
|
|
135
162
|
├── src/
|
|
136
|
-
│ ├── cli.ts # CLI 진입점 (commander,
|
|
163
|
+
│ ├── cli.ts # CLI 진입점 (commander, 7개 서브커맨드)
|
|
137
164
|
│ ├── core/
|
|
138
|
-
│ │
|
|
165
|
+
│ │ ├── gatherer.ts # 갈무리 핵심 로직 (파싱→그룹핑→저장→리포트)
|
|
166
|
+
│ │ └── summarizer.ts # AI 요약 (Claude API + 휴리스틱 fallback)
|
|
139
167
|
│ ├── parser/
|
|
140
168
|
│ │ ├── history.ts # ~/.claude/history.jsonl 스트리밍 파서
|
|
141
|
-
│ │ └── session-jsonl.ts # 세션 JSONL 파서 (
|
|
169
|
+
│ │ └── session-jsonl.ts # 세션 JSONL 파서 (토큰/모델/타이밍/대화턴 추출)
|
|
142
170
|
│ ├── grouper/
|
|
143
171
|
│ │ └── session.ts # sessionId+project 기준 그룹핑
|
|
144
172
|
│ ├── storage/
|
|
@@ -148,7 +176,7 @@ sincenety/
|
|
|
148
176
|
│ │ ├── key.ts # PBKDF2 키 파생 (머신 바운드 + passphrase)
|
|
149
177
|
│ │ └── crypto.ts # AES-256-GCM encrypt/decrypt
|
|
150
178
|
│ ├── report/
|
|
151
|
-
│ │ ├── terminal.ts # 터미널 출력
|
|
179
|
+
│ │ ├── terminal.ts # 터미널 테이블 출력 (유니코드 박스 드로잉, 한글 폭 계산)
|
|
152
180
|
│ │ └── markdown.ts # 마크다운 리포트 생성
|
|
153
181
|
│ ├── email/
|
|
154
182
|
│ │ ├── sender.ts # nodemailer 이메일 발송
|
|
@@ -171,24 +199,33 @@ sincenety/
|
|
|
171
199
|
~/.claude/history.jsonl ──→ 세션 목록 추출 (sessionId + project)
|
|
172
200
|
│
|
|
173
201
|
▼
|
|
174
|
-
~/.claude/projects/[project]/[sessionId].jsonl ──→
|
|
202
|
+
~/.claude/projects/[project]/[sessionId].jsonl ──→ 토큰/모델/타이밍/대화턴 추출
|
|
175
203
|
│
|
|
176
204
|
▼
|
|
177
205
|
그룹핑 + 요약 생성
|
|
178
206
|
│
|
|
179
|
-
|
|
180
|
-
▼
|
|
181
|
-
터미널 출력
|
|
207
|
+
┌──────────┬───┼───────┬──────────────┐
|
|
208
|
+
▼ ▼ ▼ ▼ ▼
|
|
209
|
+
터미널 출력 --json DB 저장 이메일 발송 Claude Code
|
|
210
|
+
(테이블) (구조화) (암호화) AI 요약
|
|
211
|
+
│
|
|
212
|
+
▼
|
|
213
|
+
save-daily
|
|
214
|
+
(일일보고 DB 저장)
|
|
215
|
+
│
|
|
216
|
+
▼
|
|
217
|
+
report (일일/주간/월간)
|
|
182
218
|
```
|
|
183
219
|
|
|
184
220
|
### DB 스키마
|
|
185
221
|
|
|
186
|
-
**
|
|
222
|
+
**5개 테이블:**
|
|
187
223
|
|
|
188
224
|
| 테이블 | 설명 |
|
|
189
225
|
|--------|------|
|
|
190
226
|
| `sessions` | 세션별 작업 기록 (22개 컬럼 — 토큰, 시간, 타이틀, 설명, 모델 등) |
|
|
191
227
|
| `gather_reports` | 갈무리 실행마다 리포트 저장 (마크다운 + JSON) |
|
|
228
|
+
| `daily_reports` | AI 요약 일일/주간/월간 보고 (UNIQUE(report_date, report_type)) |
|
|
192
229
|
| `checkpoints` | 갈무리 포인트 (마지막 처리 timestamp) |
|
|
193
230
|
| `config` | 설정 (이메일, SMTP 등) |
|
|
194
231
|
|
|
@@ -303,13 +340,24 @@ npx . # 현재 디렉토리를 npx로 실행
|
|
|
303
340
|
- **자동 스케줄링**: launchd (macOS) / crontab (Linux) 자동 설치
|
|
304
341
|
- **`--auto` 플래그**: 갈무리 + 이메일 자동 발송 (스케줄러용)
|
|
305
342
|
|
|
343
|
+
### v0.3 (2026-04-07) — AI 요약 일일보고 + 주간/월간 보고
|
|
344
|
+
|
|
345
|
+
- **기본 갈무리 범위 변경**: 항상 오늘 00:00부터 (upsert로 중복 방지)
|
|
346
|
+
- **대화 턴 수집**: `session-jsonl.ts`에서 사용자 입력 + 어시스턴트 응답 쌍(`conversationTurns`) 추출
|
|
347
|
+
- **터미널 테이블 출력**: 요약 테이블 + 세션 상세 테이블 (유니코드 박스 드로잉, 한글 fullwidth 폭 계산)
|
|
348
|
+
- **AI 요약 아키텍처**: `--json` 플래그로 대화 턴 포함 구조화 JSON 출력, SKILL.md가 Claude Code에게 직접 요약 지시
|
|
349
|
+
- **summarizer.ts**: Claude API 요약 (`ANTHROPIC_API_KEY` 있을 때) + 휴리스틱 fallback
|
|
350
|
+
- **일일보고 시스템**: `save-daily` (stdin JSON → DB 저장), `report` (일일/주간/월간 조회)
|
|
351
|
+
- **DB 스키마 v3**: `daily_reports` 테이블 추가 (UNIQUE(report_date, report_type))
|
|
352
|
+
- **이메일 개선**: XML/시스템 태그 정리 (cleanText, esc 함수 강화), wrapUp 데이터 반영, Section 04 작업 흐름 표시
|
|
353
|
+
|
|
306
354
|
### 향후 계획
|
|
307
355
|
|
|
308
356
|
- [ ] npm publish → `npx sincenety@latest` 배포
|
|
309
357
|
- [ ] passphrase 설정 기능 완성
|
|
310
358
|
- [ ] 유사 작업 매칭 (TF-IDF 기반)
|
|
311
359
|
- [ ] MariaDB/PostgreSQL 외부 DB 연결 (현재 비활성화)
|
|
312
|
-
- [
|
|
360
|
+
- [x] 주간/월간 요약 리포트
|
|
313
361
|
- [ ] ccusage 연동 (토큰 비용 자동 계산)
|
|
314
362
|
|
|
315
363
|
---
|
|
@@ -321,8 +369,8 @@ npx . # 현재 디렉토리를 npx로 실행
|
|
|
321
369
|
| TypeScript 소스 파일 | 15개 |
|
|
322
370
|
| 총 코드 라인 | 3,013줄 |
|
|
323
371
|
| 암호화 테스트 | 26/26 통과 |
|
|
324
|
-
| CLI 명령어 |
|
|
325
|
-
| DB 테이블 |
|
|
372
|
+
| CLI 명령어 | 7개 (갈무리, log, config, email, schedule, save-daily, report) |
|
|
373
|
+
| DB 테이블 | 5개 |
|
|
326
374
|
| 의존성 (production) | 3개 (commander, nodemailer, sql.js) |
|
|
327
375
|
| 보안 이슈 발견/수정 | 8/8 |
|
|
328
376
|
|
package/README.md
CHANGED
|
@@ -9,13 +9,17 @@ No start/stop needed. Just run it when you're done.
|
|
|
9
9
|
```
|
|
10
10
|
$ sincenety
|
|
11
11
|
|
|
12
|
-
📋 2026-04-07 (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
📋 2026-04-07 (Mon) Work Gather (00:00 ~ 18:05)
|
|
13
|
+
┌─────────────────────────────────────────────────────────┐
|
|
14
|
+
│ Sessions: 4 │ Messages: 1605 │ Tokens: 9.0Ki / 212.7Ko │
|
|
15
|
+
└─────────────────────────────────────────────────────────┘
|
|
16
|
+
┌──────────────┬───────────────┬────────┬────────┬────────┐
|
|
17
|
+
│ Project │ Time │ Msgs │ Tokens │ Model │
|
|
18
|
+
├──────────────┼───────────────┼────────┼────────┼────────┤
|
|
19
|
+
│ claudflare │ 11:22 ~ 14:49 │ 445 │ 66.4K │ opus │
|
|
20
|
+
│ sincenety │ 14:55 ~ 18:05 │ 860 │ 98.2K │ opus │
|
|
21
|
+
│ ... │ │ │ │ │
|
|
22
|
+
└──────────────┴───────────────┴────────┴────────┴────────┘
|
|
19
23
|
✅ Gather complete. Records saved.
|
|
20
24
|
```
|
|
21
25
|
|
|
@@ -27,8 +31,8 @@ $ sincenety
|
|
|
27
31
|
|
|
28
32
|
No need to remember to start/stop tracking. `sincenety` parses `~/.claude/` data at runtime and reconstructs everything:
|
|
29
33
|
|
|
30
|
-
- **Session JSONL parsing** — Extracts token usage, model names,
|
|
31
|
-
- **
|
|
34
|
+
- **Session JSONL parsing** — Extracts token usage, model names, millisecond-precision timestamps, and conversation turns (user input + assistant output pairs) from `~/.claude/projects/[project]/[sessionId].jsonl`
|
|
35
|
+
- **Full-day default** — Always gathers from today 00:00 by default; upsert logic prevents duplicates across runs
|
|
32
36
|
|
|
33
37
|
### Rich Work Records
|
|
34
38
|
|
|
@@ -41,9 +45,15 @@ No need to remember to start/stop tracking. `sincenety` parses `~/.claude/` data
|
|
|
41
45
|
| Model | Extracted from assistant responses |
|
|
42
46
|
| Category | Auto-classified from project path |
|
|
43
47
|
|
|
48
|
+
### AI-Powered Daily Reports
|
|
49
|
+
|
|
50
|
+
Generate summaries powered by Claude Code itself — no external API key needed. The CLI outputs structured JSON with conversation turns, and the Claude Code skill (SKILL.md) instructs the session to generate summaries directly. Summaries are saved to the `daily_reports` table and can be viewed as daily, weekly, or monthly reports.
|
|
51
|
+
|
|
52
|
+
When `ANTHROPIC_API_KEY` is set, the `summarizer.ts` module can also call the Claude API directly for turn-based analysis with heuristic fallback.
|
|
53
|
+
|
|
44
54
|
### Email Reports
|
|
45
55
|
|
|
46
|
-
Send beautiful HTML email reports via Gmail SMTP. Color-coded sessions, token dashboard, and
|
|
56
|
+
Send beautiful HTML email reports via Gmail SMTP. Color-coded sessions, token dashboard, work flow, and outcome/significance data included. XML/system tag cleanup ensures clean output.
|
|
47
57
|
|
|
48
58
|
### Auto-Scheduling
|
|
49
59
|
|
|
@@ -76,13 +86,16 @@ npm link
|
|
|
76
86
|
### Basic Gathering
|
|
77
87
|
|
|
78
88
|
```bash
|
|
79
|
-
# Gather
|
|
89
|
+
# Gather today's work (default: from 00:00, upsert prevents duplicates)
|
|
80
90
|
sincenety
|
|
81
91
|
|
|
82
92
|
# Gather from specific time
|
|
83
93
|
sincenety --since "09:00"
|
|
84
94
|
sincenety --since "2026-04-07 09:00"
|
|
85
95
|
|
|
96
|
+
# JSON output with conversation turns (for AI summary pipeline)
|
|
97
|
+
sincenety --json
|
|
98
|
+
|
|
86
99
|
# Fast mode (history.jsonl only, no token extraction)
|
|
87
100
|
sincenety --no-detail
|
|
88
101
|
|
|
@@ -92,6 +105,19 @@ sincenety log --date 2026-04-06
|
|
|
92
105
|
sincenety log --week
|
|
93
106
|
```
|
|
94
107
|
|
|
108
|
+
### Daily Reports
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Save an AI-generated summary to the DB (accepts JSON from stdin)
|
|
112
|
+
sincenety save-daily < summary.json
|
|
113
|
+
|
|
114
|
+
# View daily/weekly/monthly reports
|
|
115
|
+
sincenety report # Today's report
|
|
116
|
+
sincenety report --date 2026-04-06
|
|
117
|
+
sincenety report --week # Weekly aggregate
|
|
118
|
+
sincenety report --month # Monthly aggregate
|
|
119
|
+
```
|
|
120
|
+
|
|
95
121
|
### Email Setup
|
|
96
122
|
|
|
97
123
|
```bash
|
|
@@ -128,11 +154,13 @@ Use `/sincenety` directly inside Claude Code sessions.
|
|
|
128
154
|
```
|
|
129
155
|
sincenety/
|
|
130
156
|
├── src/
|
|
131
|
-
│ ├── cli.ts # CLI entry (commander,
|
|
132
|
-
│ ├── core/
|
|
157
|
+
│ ├── cli.ts # CLI entry (commander, 7 subcommands)
|
|
158
|
+
│ ├── core/
|
|
159
|
+
│ │ ├── gatherer.ts # Core logic (parse → group → store → report)
|
|
160
|
+
│ │ └── summarizer.ts # Claude API summarization + heuristic fallback
|
|
133
161
|
│ ├── parser/
|
|
134
162
|
│ │ ├── history.ts # ~/.claude/history.jsonl streaming parser
|
|
135
|
-
│ │ └── session-jsonl.ts # Session JSONL parser (tokens/model/timing)
|
|
163
|
+
│ │ └── session-jsonl.ts # Session JSONL parser (tokens/model/timing/conversationTurns)
|
|
136
164
|
│ ├── grouper/session.ts # Session grouping by sessionId + project
|
|
137
165
|
│ ├── storage/
|
|
138
166
|
│ │ ├── adapter.ts # StorageAdapter interface
|
|
@@ -158,14 +186,17 @@ sincenety/
|
|
|
158
186
|
~/.claude/history.jsonl ──→ Extract session list (sessionId + project)
|
|
159
187
|
│
|
|
160
188
|
▼
|
|
161
|
-
~/.claude/projects/[project]/[sessionId].jsonl ──→ Extract tokens/model/timing
|
|
189
|
+
~/.claude/projects/[project]/[sessionId].jsonl ──→ Extract tokens/model/timing/turns
|
|
162
190
|
│
|
|
163
191
|
▼
|
|
164
192
|
Group + summarize
|
|
165
193
|
│
|
|
166
|
-
|
|
167
|
-
▼
|
|
168
|
-
Terminal
|
|
194
|
+
┌──────────┬───┼───────┬──────────────┐
|
|
195
|
+
▼ ▼ ▼ ▼ ▼
|
|
196
|
+
Terminal table DB save Email --json output AI summary
|
|
197
|
+
(box-drawing) (encrypted) │ (Claude Code
|
|
198
|
+
+ CJK-aware ▼ or API)
|
|
199
|
+
save-daily ──→ daily_reports
|
|
169
200
|
```
|
|
170
201
|
|
|
171
202
|
### Encryption
|
|
@@ -182,6 +213,7 @@ sincenety/
|
|
|
182
213
|
|-------|-------------|
|
|
183
214
|
| `sessions` | Per-session work records (22 columns — tokens, timing, title, description, model, etc.) |
|
|
184
215
|
| `gather_reports` | Report per gather run (markdown + JSON) |
|
|
216
|
+
| `daily_reports` | AI-generated daily/weekly/monthly summaries (UNIQUE(report_date, report_type)) |
|
|
185
217
|
| `checkpoints` | Last processed timestamp |
|
|
186
218
|
| `config` | Settings (email, SMTP, etc.) |
|
|
187
219
|
|
|
@@ -215,11 +247,13 @@ node dist/cli.js # Direct execution
|
|
|
215
247
|
|
|
216
248
|
## Roadmap
|
|
217
249
|
|
|
250
|
+
- [x] Weekly/monthly summary reports
|
|
218
251
|
- [ ] Passphrase encryption option
|
|
219
252
|
- [ ] Similar task matching (TF-IDF)
|
|
220
253
|
- [ ] External DB connectors (MariaDB/PostgreSQL)
|
|
221
|
-
- [ ] Weekly/monthly summary reports
|
|
222
254
|
- [ ] ccusage integration (automatic cost calculation)
|
|
255
|
+
- [ ] Multi-language report output (EN/KO toggle)
|
|
256
|
+
- [ ] Report export (PDF/HTML standalone)
|
|
223
257
|
|
|
224
258
|
---
|
|
225
259
|
|
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ program
|
|
|
21
21
|
.option("--detail", "세션 JSONL 상세 모드 (토큰/시간 추출)", true)
|
|
22
22
|
.option("--no-detail", "history.jsonl만 사용 (빠른 모드)")
|
|
23
23
|
.option("--auto", "자동 갈무리 + 이메일 발송 (스케줄러용)")
|
|
24
|
+
.option("--json", "구조화 JSON 출력 (스킬 연동용, 대화 턴 포함)")
|
|
24
25
|
.action(async (_, options) => {
|
|
25
26
|
// "log" 서브커맨드가 아닌 경우에만 갈무리 실행
|
|
26
27
|
if (program.args[0] === "log")
|
|
@@ -46,11 +47,45 @@ program
|
|
|
46
47
|
historyPath,
|
|
47
48
|
useSessionJsonl: options.detail !== false,
|
|
48
49
|
});
|
|
50
|
+
// --json: 대화 턴 포함 구조화 JSON 출력 (Claude Code 스킬 연동용)
|
|
51
|
+
if (options.json) {
|
|
52
|
+
const jsonOutput = {
|
|
53
|
+
fromTimestamp: result.fromTimestamp,
|
|
54
|
+
toTimestamp: result.toTimestamp,
|
|
55
|
+
isFirstRun: result.isFirstRun,
|
|
56
|
+
sessions: result.sessions.map((s) => ({
|
|
57
|
+
sessionId: s.sessionId,
|
|
58
|
+
projectName: s.projectName,
|
|
59
|
+
startedAt: s.startedAt,
|
|
60
|
+
endedAt: s.endedAt,
|
|
61
|
+
durationMinutes: s.durationMinutes ?? 0,
|
|
62
|
+
messageCount: s.messageCount,
|
|
63
|
+
userMessageCount: s.userMessageCount ?? 0,
|
|
64
|
+
assistantMessageCount: s.assistantMessageCount ?? 0,
|
|
65
|
+
toolCallCount: s.toolCallCount ?? 0,
|
|
66
|
+
inputTokens: s.inputTokens ?? 0,
|
|
67
|
+
outputTokens: s.outputTokens ?? 0,
|
|
68
|
+
totalTokens: (s.inputTokens ?? 0) + (s.outputTokens ?? 0),
|
|
69
|
+
model: s.model ?? "",
|
|
70
|
+
title: s.title ?? s.summary,
|
|
71
|
+
description: s.description ?? "",
|
|
72
|
+
// 대화 턴: 사용자 입력 + 어시스턴트 응답 쌍
|
|
73
|
+
conversationTurns: (s.conversationTurns ?? []).map((t) => ({
|
|
74
|
+
timestamp: t.timestamp,
|
|
75
|
+
userInput: t.userInput,
|
|
76
|
+
assistantOutput: t.assistantOutput,
|
|
77
|
+
})),
|
|
78
|
+
})),
|
|
79
|
+
};
|
|
80
|
+
console.log(JSON.stringify(jsonOutput));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
49
83
|
console.log(formatGatherReport(result));
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
// 이메일 설정 상태 확인
|
|
85
|
+
const emailConfigured = await isEmailConfigured(storage);
|
|
86
|
+
if (emailConfigured) {
|
|
87
|
+
// --auto 또는 세션이 있을 때 자동 발송
|
|
88
|
+
if (options.auto && result.sessions.length > 0) {
|
|
54
89
|
try {
|
|
55
90
|
await sendGatherEmail(storage);
|
|
56
91
|
}
|
|
@@ -59,6 +94,24 @@ program
|
|
|
59
94
|
}
|
|
60
95
|
}
|
|
61
96
|
}
|
|
97
|
+
else if (result.isFirstRun) {
|
|
98
|
+
// 첫 실행 시 이메일 설정 안내
|
|
99
|
+
console.log(" ────────────────────────────────────────────────────────");
|
|
100
|
+
console.log(" 📧 이메일 발송을 설정하면 갈무리 리포트를 메일로 받을 수 있습니다.");
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(" 1. Google 앱 비밀번호 생성 (2단계 인증 필요):");
|
|
103
|
+
console.log(" https://myaccount.google.com/apppasswords");
|
|
104
|
+
console.log("");
|
|
105
|
+
console.log(" 2. sincenety에 이메일 설정:");
|
|
106
|
+
console.log(" sincenety config --email you@gmail.com");
|
|
107
|
+
console.log(" sincenety config --smtp-user you@gmail.com");
|
|
108
|
+
console.log(" sincenety config --smtp-pass");
|
|
109
|
+
console.log("");
|
|
110
|
+
console.log(" 설정 후 'sincenety email'로 발송하거나,");
|
|
111
|
+
console.log(" 'sincenety --auto'로 갈무리+발송을 한 번에 실행할 수 있습니다.");
|
|
112
|
+
console.log(" (이메일 없이도 터미널 출력은 항상 동작합니다)");
|
|
113
|
+
console.log("");
|
|
114
|
+
}
|
|
62
115
|
}
|
|
63
116
|
catch (err) {
|
|
64
117
|
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -117,6 +170,7 @@ program
|
|
|
117
170
|
.option("--smtp-port <port>", "SMTP 포트 (기본: 587)")
|
|
118
171
|
.option("--smtp-user <user>", "SMTP 사용자 (발신 이메일)")
|
|
119
172
|
.option("--smtp-pass", "SMTP 앱 비밀번호 설정 (프롬프트 입력)")
|
|
173
|
+
// .option("--api-key", "Anthropic API 키 설정 (프롬프트 입력, 세션 요약용)") // 비활성화: 내부 실행 전용
|
|
120
174
|
.action(async (options) => {
|
|
121
175
|
const storage = new SqlJsAdapter();
|
|
122
176
|
try {
|
|
@@ -231,6 +285,185 @@ program
|
|
|
231
285
|
process.exit(1);
|
|
232
286
|
}
|
|
233
287
|
});
|
|
288
|
+
// save-daily 서브커맨드: AI 요약 일일보고 저장
|
|
289
|
+
program
|
|
290
|
+
.command("save-daily")
|
|
291
|
+
.description("AI 요약 일일보고를 DB에 저장 (stdin으로 JSON 입력)")
|
|
292
|
+
.option("--type <type>", "보고 유형: daily | weekly | monthly", "daily")
|
|
293
|
+
.action(async (options) => {
|
|
294
|
+
// stdin 읽기
|
|
295
|
+
const chunks = [];
|
|
296
|
+
for await (const chunk of process.stdin) {
|
|
297
|
+
chunks.push(chunk);
|
|
298
|
+
}
|
|
299
|
+
const input = Buffer.concat(chunks).toString("utf-8").trim();
|
|
300
|
+
if (!input) {
|
|
301
|
+
console.error(" ❌ 입력 데이터가 없습니다. stdin으로 JSON을 전달해 주세요.");
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
let data;
|
|
305
|
+
try {
|
|
306
|
+
data = JSON.parse(input);
|
|
307
|
+
if (!data.date || !Array.isArray(data.sessions)) {
|
|
308
|
+
throw new Error("date와 sessions 필드가 필요합니다");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
console.error(` ❌ JSON 파싱 실패: ${err instanceof Error ? err.message : String(err)}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
const storage = new SqlJsAdapter();
|
|
316
|
+
try {
|
|
317
|
+
await storage.initialize();
|
|
318
|
+
// 세션 통계 보완: DB에서 해당 날짜 세션 데이터 가져오기
|
|
319
|
+
const dbSessions = await storage.getSessionsByDate(data.date);
|
|
320
|
+
let totalMessages = 0;
|
|
321
|
+
let totalTokens = 0;
|
|
322
|
+
// 각 요약에 DB 통계 병합
|
|
323
|
+
for (const summary of data.sessions) {
|
|
324
|
+
const dbSession = dbSessions.find((s) => s.id === summary.sessionId);
|
|
325
|
+
if (dbSession) {
|
|
326
|
+
summary.startedAt ??= dbSession.startedAt;
|
|
327
|
+
summary.endedAt ??= dbSession.endedAt;
|
|
328
|
+
summary.durationMinutes ??= dbSession.durationMinutes;
|
|
329
|
+
summary.messageCount ??= dbSession.messageCount;
|
|
330
|
+
summary.totalTokens ??= dbSession.totalTokens;
|
|
331
|
+
summary.projectName ??= dbSession.projectName;
|
|
332
|
+
}
|
|
333
|
+
totalMessages += summary.messageCount ?? 0;
|
|
334
|
+
totalTokens += summary.totalTokens ?? 0;
|
|
335
|
+
}
|
|
336
|
+
// 기간 계산
|
|
337
|
+
const dateObj = new Date(data.date);
|
|
338
|
+
const periodFrom = dateObj.getTime();
|
|
339
|
+
const periodTo = periodFrom + 86400000; // +1일
|
|
340
|
+
await storage.saveDailyReport({
|
|
341
|
+
reportDate: data.date,
|
|
342
|
+
reportType: options.type,
|
|
343
|
+
periodFrom,
|
|
344
|
+
periodTo,
|
|
345
|
+
sessionCount: data.sessions.length,
|
|
346
|
+
totalMessages,
|
|
347
|
+
totalTokens,
|
|
348
|
+
summaryJson: JSON.stringify(data.sessions),
|
|
349
|
+
overview: data.overview ?? null,
|
|
350
|
+
reportMarkdown: null,
|
|
351
|
+
createdAt: Date.now(),
|
|
352
|
+
emailedAt: null,
|
|
353
|
+
emailTo: null,
|
|
354
|
+
});
|
|
355
|
+
console.log(` ✅ ${options.type === "daily" ? "일일" : options.type === "weekly" ? "주간" : "월간"}보고 저장 완료: ${data.date} (${data.sessions.length}세션)`);
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
await storage.close();
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
// report 서브커맨드: 주간/월간 보고 조회
|
|
366
|
+
program
|
|
367
|
+
.command("report")
|
|
368
|
+
.description("일일보고 조회/집계")
|
|
369
|
+
.option("--date <date>", "특정 날짜 일일보고 조회")
|
|
370
|
+
.option("--week", "이번 주 일일보고 집계")
|
|
371
|
+
.option("--month", "이번 달 일일보고 집계")
|
|
372
|
+
.option("--from <date>", "시작 날짜 (YYYY-MM-DD)")
|
|
373
|
+
.option("--to <date>", "종료 날짜 (YYYY-MM-DD)")
|
|
374
|
+
.option("--json", "JSON 출력 (스킬 연동용)")
|
|
375
|
+
.action(async (options) => {
|
|
376
|
+
const storage = new SqlJsAdapter();
|
|
377
|
+
try {
|
|
378
|
+
await storage.initialize();
|
|
379
|
+
let from;
|
|
380
|
+
let to;
|
|
381
|
+
let label;
|
|
382
|
+
if (options.date) {
|
|
383
|
+
// 단일 날짜
|
|
384
|
+
const report = await storage.getDailyReport(options.date);
|
|
385
|
+
if (!report) {
|
|
386
|
+
console.log(` ${options.date}에 일일보고가 없습니다.`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (options.json) {
|
|
390
|
+
console.log(JSON.stringify(report));
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
console.log(`\n 📋 ${options.date} 일일보고 (${report.sessionCount}세션)`);
|
|
394
|
+
if (report.overview)
|
|
395
|
+
console.log(` ${report.overview}`);
|
|
396
|
+
const sessions = JSON.parse(report.summaryJson || "[]");
|
|
397
|
+
for (const s of sessions) {
|
|
398
|
+
console.log(`\n [${s.projectName}] ${s.topic ?? ""}`);
|
|
399
|
+
if (s.outcome)
|
|
400
|
+
console.log(` 결과: ${s.outcome}`);
|
|
401
|
+
if (s.flow)
|
|
402
|
+
console.log(` 흐름: ${s.flow}`);
|
|
403
|
+
}
|
|
404
|
+
console.log("");
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (options.week) {
|
|
409
|
+
const now = new Date();
|
|
410
|
+
const day = now.getDay();
|
|
411
|
+
const monday = new Date(now);
|
|
412
|
+
monday.setDate(now.getDate() - (day === 0 ? 6 : day - 1));
|
|
413
|
+
from = monday.toISOString().slice(0, 10);
|
|
414
|
+
to = now.toISOString().slice(0, 10);
|
|
415
|
+
label = `이번 주 (${from} ~ ${to})`;
|
|
416
|
+
}
|
|
417
|
+
else if (options.month) {
|
|
418
|
+
const now = new Date();
|
|
419
|
+
from = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
|
|
420
|
+
to = now.toISOString().slice(0, 10);
|
|
421
|
+
label = `이번 달 (${from} ~ ${to})`;
|
|
422
|
+
}
|
|
423
|
+
else if (options.from && options.to) {
|
|
424
|
+
from = options.from;
|
|
425
|
+
to = options.to;
|
|
426
|
+
label = `${from} ~ ${to}`;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
// 기본: 오늘
|
|
430
|
+
from = new Date().toISOString().slice(0, 10);
|
|
431
|
+
to = from;
|
|
432
|
+
label = `오늘 (${from})`;
|
|
433
|
+
}
|
|
434
|
+
const reports = await storage.getDailyReportsByRange(from, to);
|
|
435
|
+
if (reports.length === 0) {
|
|
436
|
+
console.log(` ${label}에 일일보고가 없습니다.`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (options.json) {
|
|
440
|
+
console.log(JSON.stringify(reports));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// 터미널 출력
|
|
444
|
+
const totalSessions = reports.reduce((s, r) => s + r.sessionCount, 0);
|
|
445
|
+
const totalTokens = reports.reduce((s, r) => s + r.totalTokens, 0);
|
|
446
|
+
console.log(`\n 📋 ${label} — ${reports.length}일, ${totalSessions}세션`);
|
|
447
|
+
console.log(" " + "─".repeat(56));
|
|
448
|
+
for (const r of reports) {
|
|
449
|
+
console.log(`\n 📅 ${r.reportDate} (${r.sessionCount}세션)`);
|
|
450
|
+
if (r.overview)
|
|
451
|
+
console.log(` ${r.overview}`);
|
|
452
|
+
const sessions = JSON.parse(r.summaryJson || "[]");
|
|
453
|
+
for (const s of sessions) {
|
|
454
|
+
console.log(` • [${s.projectName}] ${s.topic ?? ""}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
console.log("");
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
await storage.close();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
234
467
|
function promptPassword(prompt) {
|
|
235
468
|
return new Promise((resolve) => {
|
|
236
469
|
const rl = createInterface({
|