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
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 갈무리 핵심 로직 — 파싱 → 그룹핑 → 저장 → 리포트
3
+ */
4
+ import { parseHistory, getDefaultHistoryPath } from "../parser/history.js";
5
+ import { groupFromStream } from "../grouper/session.js";
6
+ function sessionGroupToRecord(group) {
7
+ return {
8
+ id: group.sessionId,
9
+ project: group.project,
10
+ projectName: group.projectName,
11
+ startedAt: group.startedAt,
12
+ endedAt: group.endedAt,
13
+ durationMinutes: group.durationMinutes ?? 0,
14
+ messageCount: group.messageCount,
15
+ userMessageCount: group.userMessageCount ?? 0,
16
+ assistantMessageCount: group.assistantMessageCount ?? 0,
17
+ toolCallCount: group.toolCallCount ?? 0,
18
+ inputTokens: group.inputTokens ?? 0,
19
+ outputTokens: group.outputTokens ?? 0,
20
+ cacheCreationTokens: group.cacheCreationTokens ?? 0,
21
+ cacheReadTokens: group.cacheReadTokens ?? 0,
22
+ totalTokens: group.totalTokens ?? 0,
23
+ title: group.title ?? group.summary,
24
+ summary: group.summary,
25
+ description: group.description ?? "",
26
+ category: group.category ?? group.projectName,
27
+ tags: (group.tags ?? []).join(","),
28
+ model: group.model ?? "",
29
+ createdAt: Date.now(),
30
+ };
31
+ }
32
+ function buildGatherReport(sessions, fromTimestamp, toTimestamp, markdownReport) {
33
+ const totalMessages = sessions.reduce((s, g) => s + g.messageCount, 0);
34
+ const totalInputTokens = sessions.reduce((s, g) => s + (g.inputTokens ?? 0), 0);
35
+ const totalOutputTokens = sessions.reduce((s, g) => s + (g.outputTokens ?? 0), 0);
36
+ return {
37
+ gatheredAt: toTimestamp,
38
+ fromTimestamp,
39
+ toTimestamp,
40
+ sessionCount: sessions.length,
41
+ totalMessages,
42
+ totalInputTokens,
43
+ totalOutputTokens,
44
+ reportMarkdown: markdownReport,
45
+ reportJson: JSON.stringify(sessions.map((s) => ({
46
+ sessionId: s.sessionId,
47
+ projectName: s.projectName,
48
+ title: s.title ?? s.summary,
49
+ startedAt: s.startedAt,
50
+ endedAt: s.endedAt,
51
+ durationMinutes: s.durationMinutes ?? (s.endedAt - s.startedAt) / 60000,
52
+ messageCount: s.messageCount,
53
+ userMessageCount: s.userMessageCount ?? 0,
54
+ assistantMessageCount: s.assistantMessageCount ?? 0,
55
+ inputTokens: s.inputTokens ?? 0,
56
+ outputTokens: s.outputTokens ?? 0,
57
+ totalTokens: (s.inputTokens ?? 0) + (s.outputTokens ?? 0),
58
+ model: s.model ?? "",
59
+ category: s.category ?? s.projectName,
60
+ }))),
61
+ emailedAt: null,
62
+ emailTo: null,
63
+ };
64
+ }
65
+ export async function gather(storage, options = {}) {
66
+ const now = Date.now();
67
+ const historyPath = options.historyPath ?? getDefaultHistoryPath();
68
+ // 갈무리 시작점 결정
69
+ let fromTimestamp;
70
+ let isFirstRun = false;
71
+ if (options.sinceTimestamp != null) {
72
+ fromTimestamp = options.sinceTimestamp;
73
+ }
74
+ else {
75
+ const lastCheckpoint = await storage.getLastCheckpoint();
76
+ if (lastCheckpoint != null) {
77
+ fromTimestamp = lastCheckpoint;
78
+ }
79
+ else {
80
+ const today = new Date();
81
+ today.setHours(0, 0, 0, 0);
82
+ fromTimestamp = today.getTime();
83
+ isFirstRun = true;
84
+ }
85
+ }
86
+ // 파싱 + 그룹핑
87
+ let sessions;
88
+ if (options.useSessionJsonl) {
89
+ // v0.2: 세션 JSONL 기반 (토큰/상세 데이터 포함)
90
+ const { enrichSessionsFromJsonl } = await import("../parser/session-jsonl.js");
91
+ const entries = parseHistory({ historyPath, sinceTimestamp: fromTimestamp });
92
+ const baseSessions = await groupFromStream(entries);
93
+ const details = await enrichSessionsFromJsonl(baseSessions);
94
+ // SessionDetail → SessionGroup 호환 (messages 필드 추가)
95
+ sessions = details.map((d) => ({
96
+ ...d,
97
+ messages: [],
98
+ tags: [],
99
+ }));
100
+ }
101
+ else {
102
+ // v0.1 호환: history.jsonl만 사용
103
+ const entries = parseHistory({ historyPath, sinceTimestamp: fromTimestamp });
104
+ sessions = await groupFromStream(entries);
105
+ }
106
+ let reportId;
107
+ if (sessions.length > 0) {
108
+ const records = sessions.map(sessionGroupToRecord);
109
+ await storage.upsertSessions(records);
110
+ // 갈무리 리포트 저장
111
+ const { generateMarkdownReport } = await import("../report/markdown.js");
112
+ const markdown = generateMarkdownReport(sessions, fromTimestamp, now);
113
+ const report = buildGatherReport(sessions, fromTimestamp, now, markdown);
114
+ reportId = await storage.saveGatherReport(report);
115
+ await storage.saveCheckpoint(now);
116
+ }
117
+ return {
118
+ sessions,
119
+ fromTimestamp,
120
+ toTimestamp: now,
121
+ isFirstRun,
122
+ reportId,
123
+ };
124
+ }
125
+ //# sourceMappingURL=gatherer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatherer.js","sourceRoot":"","sources":["../../src/core/gatherer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 이메일 발송 — nodemailer + Gmail SMTP
3
+ */
4
+ import type { StorageAdapter } from "../storage/adapter.js";
5
+ /**
6
+ * 갈무리 리포트를 이메일로 발송
7
+ * @param storage - StorageAdapter 인스턴스 (초기화 완료 상태)
8
+ * @param reportId - 특정 리포트 ID (없으면 최신)
9
+ */
10
+ export declare function sendGatherEmail(storage: StorageAdapter, reportId?: number): Promise<void>;
11
+ /**
12
+ * 이메일 설정 여부만 확인 (--auto 모드에서 사용)
13
+ */
14
+ export declare function isEmailConfigured(storage: StorageAdapter): Promise<boolean>;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * 이메일 발송 — nodemailer + Gmail SMTP
3
+ */
4
+ import { createTransport } from "nodemailer";
5
+ import { renderEmailHtml } from "./template.js";
6
+ /** 이메일 설정을 storage에서 읽어옴 */
7
+ async function getEmailConfig(storage) {
8
+ const [email, smtpHost, smtpPort, smtpUser, smtpPass] = await Promise.all([
9
+ storage.getConfig("email"),
10
+ storage.getConfig("smtp_host"),
11
+ storage.getConfig("smtp_port"),
12
+ storage.getConfig("smtp_user"),
13
+ storage.getConfig("smtp_pass"),
14
+ ]);
15
+ return {
16
+ email,
17
+ smtpHost: smtpHost ?? "smtp.gmail.com",
18
+ smtpPort: parseInt(smtpPort ?? "587", 10),
19
+ smtpUser,
20
+ smtpPass,
21
+ };
22
+ }
23
+ /**
24
+ * 갈무리 리포트를 이메일로 발송
25
+ * @param storage - StorageAdapter 인스턴스 (초기화 완료 상태)
26
+ * @param reportId - 특정 리포트 ID (없으면 최신)
27
+ */
28
+ export async function sendGatherEmail(storage, reportId) {
29
+ const config = await getEmailConfig(storage);
30
+ if (!config.email || !config.smtpUser || !config.smtpPass) {
31
+ console.log(" 이메일이 설정되지 않았습니다. 아래 명령으로 설정해 주세요:\n" +
32
+ "\n" +
33
+ " sincenety config --email 수신자@gmail.com\n" +
34
+ " sincenety config --smtp-user 발신자@gmail.com\n" +
35
+ " sincenety config --smtp-pass (프롬프트에서 앱 비밀번호 입력)\n" +
36
+ "\n" +
37
+ " Gmail 앱 비밀번호 생성: https://myaccount.google.com/apppasswords");
38
+ return;
39
+ }
40
+ // 리포트 가져오기
41
+ let report;
42
+ if (reportId != null) {
43
+ // getGatherReportsByDate로는 ID로 조회 불가 → 날짜 범위 없이 latest 사용 후 필터
44
+ const latest = await storage.getLatestGatherReport();
45
+ if (latest && latest.id === reportId) {
46
+ report = latest;
47
+ }
48
+ else {
49
+ throw new Error(`리포트 ID ${reportId}를 찾을 수 없습니다.`);
50
+ }
51
+ }
52
+ else {
53
+ report = await storage.getLatestGatherReport();
54
+ }
55
+ if (!report) {
56
+ console.log(" 발송할 갈무리 리포트가 없습니다. 먼저 갈무리를 실행해 주세요.");
57
+ return;
58
+ }
59
+ // 이미 발송된 리포트인지 확인
60
+ if (report.emailedAt) {
61
+ const sentDate = new Date(report.emailedAt).toLocaleString("ko-KR");
62
+ console.log(` 이 리포트는 이미 ${sentDate}에 ${report.emailTo}로 발송되었습니다.`);
63
+ return;
64
+ }
65
+ // 제목 구성
66
+ const dateStr = new Date(report.gatheredAt).toISOString().slice(0, 10);
67
+ const totalTokensK = Math.round((report.totalInputTokens + report.totalOutputTokens) / 1000);
68
+ const subject = `[sincenety] ${dateStr} 작업 갈무리 — ${report.sessionCount}세션, ${report.totalMessages}msg, ${totalTokensK}Ktok`;
69
+ // HTML 생성 — 리포트 JSON에서 세션 데이터 복원하여 풍부한 템플릿 렌더링
70
+ let html;
71
+ try {
72
+ const sessionsJson = JSON.parse(report.reportJson || "[]");
73
+ const emailData = {
74
+ sessions: sessionsJson.map((s) => ({
75
+ sessionId: s.sessionId ?? "",
76
+ projectName: s.projectName ?? "",
77
+ startedAt: s.startedAt ?? report.fromTimestamp,
78
+ endedAt: s.endedAt ?? report.toTimestamp,
79
+ durationMinutes: s.durationMinutes ?? 0,
80
+ messageCount: s.messageCount ?? 0,
81
+ userMessageCount: s.userMessageCount ?? 0,
82
+ assistantMessageCount: s.assistantMessageCount ?? 0,
83
+ inputTokens: s.inputTokens ?? 0,
84
+ outputTokens: s.outputTokens ?? 0,
85
+ totalTokens: s.totalTokens ?? 0,
86
+ title: s.title ?? "",
87
+ summary: s.title ?? "",
88
+ description: s.description ?? "",
89
+ model: s.model ?? "",
90
+ category: s.category ?? "",
91
+ actions: (s.actions ?? []).map((a) => ({
92
+ time: a.time ?? "",
93
+ input: a.input ?? "",
94
+ result: a.result ?? "",
95
+ significance: a.significance ?? "",
96
+ })),
97
+ })),
98
+ fromTimestamp: report.fromTimestamp,
99
+ toTimestamp: report.toTimestamp,
100
+ gatheredAt: report.gatheredAt,
101
+ };
102
+ html = renderEmailHtml(emailData);
103
+ }
104
+ catch {
105
+ // JSON 파싱 실패 시 마크다운을 plain text로
106
+ html = `<pre style="font-family:monospace;white-space:pre-wrap">${report.reportMarkdown.replace(/</g, "&lt;")}</pre>`;
107
+ }
108
+ // 전송
109
+ const transporter = createTransport({
110
+ host: config.smtpHost,
111
+ port: config.smtpPort,
112
+ secure: config.smtpPort === 465,
113
+ auth: {
114
+ user: config.smtpUser,
115
+ pass: config.smtpPass,
116
+ },
117
+ });
118
+ await transporter.sendMail({
119
+ from: config.smtpUser,
120
+ to: config.email,
121
+ subject,
122
+ text: report.reportMarkdown,
123
+ html,
124
+ });
125
+ // 발송 기록 업데이트
126
+ await storage.updateReportEmail(report.id, Date.now(), config.email);
127
+ console.log(` 이메일 발송 완료: ${config.email}`);
128
+ console.log(` 제목: ${subject}`);
129
+ }
130
+ /**
131
+ * 이메일 설정 여부만 확인 (--auto 모드에서 사용)
132
+ */
133
+ export async function isEmailConfigured(storage) {
134
+ const config = await getEmailConfig(storage);
135
+ return !!(config.email && config.smtpUser && config.smtpPass);
136
+ }
137
+ //# sourceMappingURL=sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sender.js","sourceRoot":"","sources":["../../src/email/sender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * sincenety 이메일 HTML 템플릿 v5
3
+ *
4
+ * 디자인: "Bright Color-Coded Dashboard"
5
+ * — 밝은 배경에 세션별 고유 컬러로 영역 구분
6
+ * — 갈무리 요약 최상단 배치
7
+ * — 섹션별 배경색 차별화
8
+ */
9
+ export interface UserAction {
10
+ time: string;
11
+ input: string;
12
+ result: string;
13
+ significance: string;
14
+ }
15
+ export interface SessionData {
16
+ sessionId: string;
17
+ projectName: string;
18
+ startedAt: number;
19
+ endedAt: number;
20
+ durationMinutes: number;
21
+ messageCount: number;
22
+ userMessageCount: number;
23
+ assistantMessageCount: number;
24
+ inputTokens: number;
25
+ outputTokens: number;
26
+ totalTokens: number;
27
+ title: string;
28
+ summary: string;
29
+ description: string;
30
+ model: string;
31
+ category: string;
32
+ actions: UserAction[];
33
+ wrapUp?: {
34
+ outcome: string;
35
+ significance: string;
36
+ nextSteps?: string;
37
+ };
38
+ }
39
+ export interface EmailData {
40
+ sessions: SessionData[];
41
+ fromTimestamp: number;
42
+ toTimestamp: number;
43
+ gatheredAt: number;
44
+ totalCostUsd?: number;
45
+ totalCacheTokens?: number;
46
+ }
47
+ export declare function renderEmailHtml(data: EmailData): string;