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,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,5 @@
1
+ /**
2
+ * 마크다운 리포트 생성 — 갈무리 결과를 저장/이메일 가능한 마크다운으로 변환
3
+ */
4
+ import type { SessionGroup } from "../grouper/session.js";
5
+ export declare function generateMarkdownReport(sessions: SessionGroup[], fromTimestamp: number, toTimestamp: number): string;
@@ -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":"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"}
@@ -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>;