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
|
@@ -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, "<")}</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;
|