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,40 @@
|
|
|
1
|
+
export interface SessionDetail {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
project: string;
|
|
4
|
+
projectName: string;
|
|
5
|
+
startedAt: number;
|
|
6
|
+
endedAt: number;
|
|
7
|
+
durationMinutes: number;
|
|
8
|
+
userMessageCount: number;
|
|
9
|
+
assistantMessageCount: number;
|
|
10
|
+
toolCallCount: number;
|
|
11
|
+
messageCount: number;
|
|
12
|
+
inputTokens: number;
|
|
13
|
+
outputTokens: number;
|
|
14
|
+
cacheCreationTokens: number;
|
|
15
|
+
cacheReadTokens: number;
|
|
16
|
+
totalTokens: number;
|
|
17
|
+
title: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
description: string;
|
|
20
|
+
category: string;
|
|
21
|
+
model: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function getSessionJsonlPath(project: string, sessionId: string): string;
|
|
24
|
+
export declare function parseSessionJsonl(project: string, sessionId: string): Promise<SessionDetail | null>;
|
|
25
|
+
/**
|
|
26
|
+
* history.jsonl 기반 SessionGroup을 세션 JSONL 데이터로 보강.
|
|
27
|
+
* 세션 JSONL이 있으면 토큰/상세 데이터를 덮어쓰고, 없으면 원본 유지.
|
|
28
|
+
*/
|
|
29
|
+
export declare function enrichSessionsFromJsonl(baseSessions: Array<{
|
|
30
|
+
sessionId: string;
|
|
31
|
+
project: string;
|
|
32
|
+
startedAt?: number;
|
|
33
|
+
endedAt?: number;
|
|
34
|
+
messageCount?: number;
|
|
35
|
+
summary?: string;
|
|
36
|
+
}>): Promise<SessionDetail[]>;
|
|
37
|
+
export declare function findSessionFiles(sinceTimestamp: number): Promise<Array<{
|
|
38
|
+
project: string;
|
|
39
|
+
sessionId: string;
|
|
40
|
+
}>>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createReadStream, existsSync } from "node:fs";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { parseHistory } from "../parser/history.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function encodeProjectPath(project) {
|
|
10
|
+
// Claude Code encodes: / → -, _ → - (leading - is KEPT)
|
|
11
|
+
return project.replace(/[/_]/g, "-");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Fallback: scan ~/.claude/projects/ for a directory matching the sessionId.
|
|
15
|
+
* Handles cases where encoding doesn't match exactly.
|
|
16
|
+
*/
|
|
17
|
+
function findProjectDir(project, sessionId) {
|
|
18
|
+
const projectsDir = join(homedir(), ".claude", "projects");
|
|
19
|
+
// Try exact encoding first
|
|
20
|
+
const exactPath = join(projectsDir, encodeProjectPath(project), `${sessionId}.jsonl`);
|
|
21
|
+
if (existsSync(exactPath))
|
|
22
|
+
return exactPath;
|
|
23
|
+
// Fallback: try with only / → - (no _ replacement)
|
|
24
|
+
const altEncoded = project.replace(/\//g, "-").replace(/^-/, "");
|
|
25
|
+
const altPath = join(projectsDir, altEncoded, `${sessionId}.jsonl`);
|
|
26
|
+
if (existsSync(altPath))
|
|
27
|
+
return altPath;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function extractTextContent(content) {
|
|
31
|
+
if (typeof content === "string")
|
|
32
|
+
return content;
|
|
33
|
+
if (Array.isArray(content)) {
|
|
34
|
+
return content
|
|
35
|
+
.filter((block) => typeof block === "object" &&
|
|
36
|
+
block !== null &&
|
|
37
|
+
"text" in block &&
|
|
38
|
+
typeof block.text === "string")
|
|
39
|
+
.map((block) => block.text)
|
|
40
|
+
.join("\n");
|
|
41
|
+
}
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
function truncate(str, max) {
|
|
45
|
+
return str.length > max ? str.slice(0, max) : str;
|
|
46
|
+
}
|
|
47
|
+
function isSlashCommand(text) {
|
|
48
|
+
const cleaned = text.replace(/<[^>]+>/g, "").trimStart();
|
|
49
|
+
return cleaned.startsWith("/");
|
|
50
|
+
}
|
|
51
|
+
function cleanTitle(text) {
|
|
52
|
+
return text
|
|
53
|
+
.replace(/<[^>]+>/g, "") // XML/HTML 태그 제거
|
|
54
|
+
.replace(/\n/g, " ")
|
|
55
|
+
.trim();
|
|
56
|
+
}
|
|
57
|
+
function mostCommon(values) {
|
|
58
|
+
if (values.length === 0)
|
|
59
|
+
return "";
|
|
60
|
+
const counts = new Map();
|
|
61
|
+
for (const v of values) {
|
|
62
|
+
counts.set(v, (counts.get(v) ?? 0) + 1);
|
|
63
|
+
}
|
|
64
|
+
let best = "";
|
|
65
|
+
let bestCount = 0;
|
|
66
|
+
for (const [v, c] of counts) {
|
|
67
|
+
if (c > bestCount) {
|
|
68
|
+
best = v;
|
|
69
|
+
bestCount = c;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return best;
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Public API
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
export function getSessionJsonlPath(project, sessionId) {
|
|
78
|
+
const encoded = encodeProjectPath(project);
|
|
79
|
+
return join(homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
80
|
+
}
|
|
81
|
+
export async function parseSessionJsonl(project, sessionId) {
|
|
82
|
+
// Try multiple encoding strategies
|
|
83
|
+
let filePath = getSessionJsonlPath(project, sessionId);
|
|
84
|
+
if (!existsSync(filePath)) {
|
|
85
|
+
const found = findProjectDir(project, sessionId);
|
|
86
|
+
if (!found)
|
|
87
|
+
return null;
|
|
88
|
+
filePath = found;
|
|
89
|
+
}
|
|
90
|
+
let startedAt = Infinity;
|
|
91
|
+
let endedAt = -Infinity;
|
|
92
|
+
let userMessageCount = 0;
|
|
93
|
+
let assistantMessageCount = 0;
|
|
94
|
+
let toolCallCount = 0;
|
|
95
|
+
let messageCount = 0;
|
|
96
|
+
let inputTokens = 0;
|
|
97
|
+
let outputTokens = 0;
|
|
98
|
+
let cacheCreationTokens = 0;
|
|
99
|
+
let cacheReadTokens = 0;
|
|
100
|
+
const userTexts = [];
|
|
101
|
+
const models = [];
|
|
102
|
+
const rl = createInterface({
|
|
103
|
+
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
104
|
+
crlfDelay: Infinity,
|
|
105
|
+
});
|
|
106
|
+
for await (const line of rl) {
|
|
107
|
+
if (!line.trim())
|
|
108
|
+
continue;
|
|
109
|
+
let entry;
|
|
110
|
+
try {
|
|
111
|
+
entry = JSON.parse(line);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const type = entry.type;
|
|
117
|
+
if (!type)
|
|
118
|
+
continue;
|
|
119
|
+
messageCount++;
|
|
120
|
+
// Timestamp bookkeeping
|
|
121
|
+
if (entry.timestamp) {
|
|
122
|
+
const ts = new Date(entry.timestamp).getTime();
|
|
123
|
+
if (ts < startedAt)
|
|
124
|
+
startedAt = ts;
|
|
125
|
+
if (ts > endedAt)
|
|
126
|
+
endedAt = ts;
|
|
127
|
+
}
|
|
128
|
+
// Tool call counting
|
|
129
|
+
if (type.includes("tool")) {
|
|
130
|
+
toolCallCount++;
|
|
131
|
+
}
|
|
132
|
+
if (type === "user" && entry.message?.role === "user") {
|
|
133
|
+
userMessageCount++;
|
|
134
|
+
const text = extractTextContent(entry.message.content);
|
|
135
|
+
if (text)
|
|
136
|
+
userTexts.push(text);
|
|
137
|
+
}
|
|
138
|
+
if (type === "assistant" && entry.message?.role === "assistant") {
|
|
139
|
+
assistantMessageCount++;
|
|
140
|
+
if (entry.message.model) {
|
|
141
|
+
models.push(entry.message.model);
|
|
142
|
+
}
|
|
143
|
+
const usage = entry.message.usage;
|
|
144
|
+
if (usage) {
|
|
145
|
+
inputTokens += usage.input_tokens ?? 0;
|
|
146
|
+
outputTokens += usage.output_tokens ?? 0;
|
|
147
|
+
cacheCreationTokens += usage.cache_creation_input_tokens ?? 0;
|
|
148
|
+
cacheReadTokens += usage.cache_read_input_tokens ?? 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Edge case: empty or no messages
|
|
153
|
+
if (startedAt === Infinity)
|
|
154
|
+
startedAt = 0;
|
|
155
|
+
if (endedAt === -Infinity)
|
|
156
|
+
endedAt = 0;
|
|
157
|
+
const durationMinutes = (endedAt - startedAt) / 60000;
|
|
158
|
+
const totalTokens = inputTokens + outputTokens;
|
|
159
|
+
// Title: first non-slash user message, truncated to 100 chars
|
|
160
|
+
const firstNonSlash = userTexts.find((t) => !isSlashCommand(t)) ?? userTexts[0] ?? "";
|
|
161
|
+
const title = truncate(cleanTitle(firstNonSlash), 100);
|
|
162
|
+
const summary = title;
|
|
163
|
+
// Description: first 3-5 user messages joined, truncated to 500 chars
|
|
164
|
+
const descSlice = userTexts.slice(0, 5);
|
|
165
|
+
const description = truncate(descSlice.map((t) => t.replace(/\n/g, " ").trim()).join(" | "), 500);
|
|
166
|
+
const projectName = project.split("/").filter(Boolean).pop() ?? project;
|
|
167
|
+
const model = mostCommon(models);
|
|
168
|
+
return {
|
|
169
|
+
sessionId,
|
|
170
|
+
project,
|
|
171
|
+
projectName,
|
|
172
|
+
startedAt,
|
|
173
|
+
endedAt,
|
|
174
|
+
durationMinutes,
|
|
175
|
+
userMessageCount,
|
|
176
|
+
assistantMessageCount,
|
|
177
|
+
toolCallCount,
|
|
178
|
+
messageCount,
|
|
179
|
+
inputTokens,
|
|
180
|
+
outputTokens,
|
|
181
|
+
cacheCreationTokens,
|
|
182
|
+
cacheReadTokens,
|
|
183
|
+
totalTokens,
|
|
184
|
+
title,
|
|
185
|
+
summary,
|
|
186
|
+
description,
|
|
187
|
+
category: projectName,
|
|
188
|
+
model,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* history.jsonl 기반 SessionGroup을 세션 JSONL 데이터로 보강.
|
|
193
|
+
* 세션 JSONL이 있으면 토큰/상세 데이터를 덮어쓰고, 없으면 원본 유지.
|
|
194
|
+
*/
|
|
195
|
+
export async function enrichSessionsFromJsonl(baseSessions) {
|
|
196
|
+
const results = [];
|
|
197
|
+
for (const base of baseSessions) {
|
|
198
|
+
const detail = await parseSessionJsonl(base.project, base.sessionId);
|
|
199
|
+
if (detail) {
|
|
200
|
+
results.push(detail);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// 세션 JSONL 없으면 기본값으로 채움
|
|
204
|
+
results.push({
|
|
205
|
+
sessionId: base.sessionId,
|
|
206
|
+
project: base.project,
|
|
207
|
+
projectName: base.project.split("/").filter(Boolean).pop() ?? base.project,
|
|
208
|
+
startedAt: base.startedAt ?? 0,
|
|
209
|
+
endedAt: base.endedAt ?? 0,
|
|
210
|
+
durationMinutes: 0,
|
|
211
|
+
userMessageCount: 0,
|
|
212
|
+
assistantMessageCount: 0,
|
|
213
|
+
toolCallCount: 0,
|
|
214
|
+
messageCount: base.messageCount ?? 0,
|
|
215
|
+
inputTokens: 0,
|
|
216
|
+
outputTokens: 0,
|
|
217
|
+
cacheCreationTokens: 0,
|
|
218
|
+
cacheReadTokens: 0,
|
|
219
|
+
totalTokens: 0,
|
|
220
|
+
title: base.summary ?? "",
|
|
221
|
+
summary: base.summary ?? "",
|
|
222
|
+
description: "",
|
|
223
|
+
category: base.project.split("/").filter(Boolean).pop() ?? base.project,
|
|
224
|
+
model: "",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
}
|
|
230
|
+
export async function findSessionFiles(sinceTimestamp) {
|
|
231
|
+
const seen = new Set();
|
|
232
|
+
const results = [];
|
|
233
|
+
for await (const entry of parseHistory({ sinceTimestamp })) {
|
|
234
|
+
const key = `${entry.project}::${entry.sessionId}`;
|
|
235
|
+
if (seen.has(key))
|
|
236
|
+
continue;
|
|
237
|
+
seen.add(key);
|
|
238
|
+
results.push({ project: entry.project, sessionId: entry.sessionId });
|
|
239
|
+
}
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=session-jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-jsonl.js","sourceRoot":"","sources":["../../src/parser/session-jsonl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 마크다운 리포트 생성 — 갈무리 결과를 저장/이메일 가능한 마크다운으로 변환
|
|
3
|
+
*/
|
|
4
|
+
function formatTime(epochMs) {
|
|
5
|
+
return new Date(epochMs).toLocaleTimeString("ko-KR", {
|
|
6
|
+
hour: "2-digit",
|
|
7
|
+
minute: "2-digit",
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function formatDate(epochMs) {
|
|
11
|
+
return new Date(epochMs).toLocaleDateString("ko-KR", {
|
|
12
|
+
year: "numeric",
|
|
13
|
+
month: "2-digit",
|
|
14
|
+
day: "2-digit",
|
|
15
|
+
weekday: "short",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function formatDuration(minutes) {
|
|
19
|
+
if (minutes < 60)
|
|
20
|
+
return `${Math.round(minutes)}분`;
|
|
21
|
+
const h = Math.floor(minutes / 60);
|
|
22
|
+
const m = Math.round(minutes % 60);
|
|
23
|
+
return m > 0 ? `${h}시간 ${m}분` : `${h}시간`;
|
|
24
|
+
}
|
|
25
|
+
function formatTokens(n) {
|
|
26
|
+
if (n >= 1_000_000)
|
|
27
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
28
|
+
if (n >= 1_000)
|
|
29
|
+
return `${(n / 1_000).toFixed(1)}K`;
|
|
30
|
+
return String(n);
|
|
31
|
+
}
|
|
32
|
+
export function generateMarkdownReport(sessions, fromTimestamp, toTimestamp) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
const dateStr = formatDate(toTimestamp);
|
|
35
|
+
const fromTime = formatTime(fromTimestamp);
|
|
36
|
+
const toTime = formatTime(toTimestamp);
|
|
37
|
+
const totalMessages = sessions.reduce((s, g) => s + g.messageCount, 0);
|
|
38
|
+
const totalInput = sessions.reduce((s, g) => s + (g.inputTokens ?? 0), 0);
|
|
39
|
+
const totalOutput = sessions.reduce((s, g) => s + (g.outputTokens ?? 0), 0);
|
|
40
|
+
const totalDuration = sessions.reduce((s, g) => s + (g.durationMinutes ?? (g.endedAt - g.startedAt) / 60000), 0);
|
|
41
|
+
lines.push(`# 작업 갈무리 — ${dateStr} ${toTime}`);
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("## 요약");
|
|
44
|
+
lines.push(`- **기간**: ${fromTime} ~ ${toTime}`);
|
|
45
|
+
lines.push(`- **세션**: ${sessions.length}개`);
|
|
46
|
+
lines.push(`- **메시지**: ${totalMessages}개`);
|
|
47
|
+
if (totalInput + totalOutput > 0) {
|
|
48
|
+
lines.push(`- **토큰**: 입력 ${formatTokens(totalInput)} / 출력 ${formatTokens(totalOutput)} (합계 ${formatTokens(totalInput + totalOutput)})`);
|
|
49
|
+
}
|
|
50
|
+
lines.push(`- **작업 시간**: ${formatDuration(totalDuration)}`);
|
|
51
|
+
lines.push("");
|
|
52
|
+
lines.push("## 세션별 상세");
|
|
53
|
+
lines.push("");
|
|
54
|
+
for (const session of sessions) {
|
|
55
|
+
const time = `${formatTime(session.startedAt)} ~ ${formatTime(session.endedAt)}`;
|
|
56
|
+
const dur = session.durationMinutes ?? (session.endedAt - session.startedAt) / 60000;
|
|
57
|
+
const title = session.title ?? session.summary;
|
|
58
|
+
lines.push(`### [${session.projectName}] ${time} (${formatDuration(dur)})`);
|
|
59
|
+
lines.push(`- **타이틀**: ${title}`);
|
|
60
|
+
if (session.description) {
|
|
61
|
+
lines.push(`- **설명**: ${session.description}`);
|
|
62
|
+
}
|
|
63
|
+
if ((session.inputTokens ?? 0) + (session.outputTokens ?? 0) > 0) {
|
|
64
|
+
lines.push(`- **토큰**: 입력 ${formatTokens(session.inputTokens ?? 0)} / 출력 ${formatTokens(session.outputTokens ?? 0)}`);
|
|
65
|
+
}
|
|
66
|
+
lines.push(`- **메시지**: 사용자 ${session.userMessageCount ?? "?"} / AI ${session.assistantMessageCount ?? "?"} (총 ${session.messageCount})`);
|
|
67
|
+
if (session.model) {
|
|
68
|
+
lines.push(`- **모델**: ${session.model}`);
|
|
69
|
+
}
|
|
70
|
+
lines.push(`- **카테고리**: ${session.category ?? session.projectName}`);
|
|
71
|
+
lines.push("");
|
|
72
|
+
}
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/report/markdown.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACnD,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACnD,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,SAAS;QACd,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CAAC,OAAe;IACrC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;IACnD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,IAAI,CAAC,IAAI,SAAS;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5D,IAAI,CAAC,IAAI,KAAK;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACpD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,QAAwB,EACxB,aAAqB,EACrB,WAAmB;IAEnB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAEvC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CACnC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAC1E,CAAC;IAEF,KAAK,CAAC,IAAI,CAAC,cAAc,OAAO,IAAI,MAAM,EAAE,CAAC,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,MAAM,MAAM,EAAE,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC,cAAc,aAAa,GAAG,CAAC,CAAC;IAC3C,IAAI,UAAU,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CACR,gBAAgB,YAAY,CAAC,UAAU,CAAC,SAAS,YAAY,CAAC,WAAW,CAAC,QAAQ,YAAY,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,CAC5H,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,gBAAgB,cAAc,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjF,MAAM,GAAG,GAAG,OAAO,CAAC,eAAe,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;QACrF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;QAE/C,KAAK,CAAC,IAAI,CAAC,QAAQ,OAAO,CAAC,WAAW,KAAK,IAAI,KAAK,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,cAAc,KAAK,EAAE,CAAC,CAAC;QAClC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YACjE,KAAK,CAAC,IAAI,CACR,gBAAgB,YAAY,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,SAAS,YAAY,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CACzG,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CACR,kBAAkB,OAAO,CAAC,gBAAgB,IAAI,GAAG,SAAS,OAAO,CAAC,qBAAqB,IAAI,GAAG,OAAO,OAAO,CAAC,YAAY,GAAG,CAC7H,CAAC;QACF,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,eAAe,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 터미널 리포트 포매터
|
|
3
|
+
*/
|
|
4
|
+
import type { GatherResult } from "../core/gatherer.js";
|
|
5
|
+
import type { SessionRecord } from "../storage/adapter.js";
|
|
6
|
+
export declare function formatGatherReport(result: GatherResult): string;
|
|
7
|
+
export declare function formatLogReport(records: SessionRecord[], dateLabel: string): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 터미널 리포트 포매터
|
|
3
|
+
*/
|
|
4
|
+
function formatTime(epochMs) {
|
|
5
|
+
return new Date(epochMs).toLocaleTimeString("ko-KR", {
|
|
6
|
+
hour: "2-digit",
|
|
7
|
+
minute: "2-digit",
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function formatDate(epochMs) {
|
|
11
|
+
return new Date(epochMs).toLocaleDateString("ko-KR", {
|
|
12
|
+
year: "numeric",
|
|
13
|
+
month: "2-digit",
|
|
14
|
+
day: "2-digit",
|
|
15
|
+
weekday: "short",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function formatDuration(startMs, endMs) {
|
|
19
|
+
const minutes = Math.floor((endMs - startMs) / 60000);
|
|
20
|
+
if (minutes < 60)
|
|
21
|
+
return `${minutes}분`;
|
|
22
|
+
const h = Math.floor(minutes / 60);
|
|
23
|
+
const m = minutes % 60;
|
|
24
|
+
return m > 0 ? `${h}시간 ${m}분` : `${h}시간`;
|
|
25
|
+
}
|
|
26
|
+
function fmtTokens(n) {
|
|
27
|
+
if (n >= 1_000_000)
|
|
28
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
29
|
+
if (n >= 1_000)
|
|
30
|
+
return `${(n / 1_000).toFixed(1)}K`;
|
|
31
|
+
return String(n);
|
|
32
|
+
}
|
|
33
|
+
function truncate(str, maxLen) {
|
|
34
|
+
if (str.length <= maxLen)
|
|
35
|
+
return str;
|
|
36
|
+
return str.slice(0, maxLen - 1) + "…";
|
|
37
|
+
}
|
|
38
|
+
export function formatGatherReport(result) {
|
|
39
|
+
const lines = [];
|
|
40
|
+
const { sessions, fromTimestamp, toTimestamp, isFirstRun } = result;
|
|
41
|
+
if (sessions.length === 0) {
|
|
42
|
+
const since = formatTime(fromTimestamp);
|
|
43
|
+
lines.push(`\n 갈무리할 작업이 없습니다. (${since} 이후 활동 없음)\n`);
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
const dateStr = formatDate(toTimestamp);
|
|
47
|
+
const fromTime = formatTime(fromTimestamp);
|
|
48
|
+
const toTime = formatTime(toTimestamp);
|
|
49
|
+
const totalMessages = sessions.reduce((s, g) => s + g.messageCount, 0);
|
|
50
|
+
const totalInput = sessions.reduce((s, g) => s + (g.inputTokens ?? 0), 0);
|
|
51
|
+
const totalOutput = sessions.reduce((s, g) => s + (g.outputTokens ?? 0), 0);
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(` 📋 ${dateStr} 작업 갈무리 (${fromTime} ~ ${toTime})${isFirstRun ? " [첫 실행]" : ""}`);
|
|
54
|
+
let headerLine = ` 총 ${sessions.length}개 세션, ${totalMessages}개 메시지`;
|
|
55
|
+
if (totalInput + totalOutput > 0) {
|
|
56
|
+
headerLine += ` | 토큰: ${fmtTokens(totalInput)}in / ${fmtTokens(totalOutput)}out`;
|
|
57
|
+
}
|
|
58
|
+
lines.push(headerLine);
|
|
59
|
+
lines.push(" " + "─".repeat(56));
|
|
60
|
+
for (const session of sessions) {
|
|
61
|
+
const time = `${formatTime(session.startedAt)} ~ ${formatTime(session.endedAt)}`;
|
|
62
|
+
const duration = formatDuration(session.startedAt, session.endedAt);
|
|
63
|
+
const title = truncate(session.title ?? session.summary, 60);
|
|
64
|
+
const tokens = (session.inputTokens ?? 0) + (session.outputTokens ?? 0);
|
|
65
|
+
lines.push("");
|
|
66
|
+
let sessionLine = ` [${session.projectName}] ${time} (${duration}, ${session.messageCount}msg`;
|
|
67
|
+
if (tokens > 0)
|
|
68
|
+
sessionLine += `, ${fmtTokens(tokens)}tok`;
|
|
69
|
+
sessionLine += ")";
|
|
70
|
+
lines.push(sessionLine);
|
|
71
|
+
lines.push(` ${title}`);
|
|
72
|
+
if (session.model) {
|
|
73
|
+
lines.push(` 모델: ${session.model}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push(" " + "─".repeat(56));
|
|
78
|
+
lines.push(` ✅ 갈무리 완료. 기록이 저장되었습니다.`);
|
|
79
|
+
lines.push("");
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
export function formatLogReport(records, dateLabel) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
if (records.length === 0) {
|
|
85
|
+
lines.push(`\n ${dateLabel}에 기록된 작업이 없습니다.\n`);
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
const totalMessages = records.reduce((s, r) => s + r.messageCount, 0);
|
|
89
|
+
const totalTokens = records.reduce((s, r) => s + r.inputTokens + r.outputTokens, 0);
|
|
90
|
+
lines.push("");
|
|
91
|
+
let header = ` 📋 ${dateLabel} 작업 기록 — ${records.length}개 세션, ${totalMessages}msg`;
|
|
92
|
+
if (totalTokens > 0)
|
|
93
|
+
header += `, ${fmtTokens(totalTokens)}tok`;
|
|
94
|
+
lines.push(header);
|
|
95
|
+
lines.push(" " + "─".repeat(56));
|
|
96
|
+
for (const r of records) {
|
|
97
|
+
const time = `${formatTime(r.startedAt)} ~ ${formatTime(r.endedAt)}`;
|
|
98
|
+
const duration = formatDuration(r.startedAt, r.endedAt);
|
|
99
|
+
const title = truncate(r.title || r.summary, 60);
|
|
100
|
+
const tokens = r.inputTokens + r.outputTokens;
|
|
101
|
+
lines.push("");
|
|
102
|
+
let line = ` [${r.projectName}] ${time} (${duration}, ${r.messageCount}msg`;
|
|
103
|
+
if (tokens > 0)
|
|
104
|
+
line += `, ${fmtTokens(tokens)}tok`;
|
|
105
|
+
line += ")";
|
|
106
|
+
lines.push(line);
|
|
107
|
+
lines.push(` ${title}`);
|
|
108
|
+
if (r.model)
|
|
109
|
+
lines.push(` 모델: ${r.model}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push("");
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/report/terminal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 플랫폼별 스케줄러 설치/해제 — macOS LaunchAgent 또는 crontab
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 스케줄 설치
|
|
6
|
+
*/
|
|
7
|
+
export declare function installSchedule(options?: {
|
|
8
|
+
time?: string;
|
|
9
|
+
nodePath?: string;
|
|
10
|
+
cliPath?: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* 스케줄 해제
|
|
14
|
+
*/
|
|
15
|
+
export declare function uninstallSchedule(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* 스케줄 상태 확인
|
|
18
|
+
*/
|
|
19
|
+
export declare function getScheduleStatus(): Promise<string>;
|