seogitan 1.0.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/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +63 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +10 -0
- package/dist/commands/meetings.d.ts +2 -0
- package/dist/commands/meetings.js +160 -0
- package/dist/commands/record.d.ts +2 -0
- package/dist/commands/record.js +212 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +50 -0
- package/dist/commands/summarize.d.ts +2 -0
- package/dist/commands/summarize.js +69 -0
- package/dist/commands/upload.d.ts +2 -0
- package/dist/commands/upload.js +186 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -0
- package/dist/exit-codes.d.ts +8 -0
- package/dist/exit-codes.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +32 -0
- package/dist/lib/audio.d.ts +18 -0
- package/dist/lib/audio.js +185 -0
- package/dist/lib/auth.d.ts +21 -0
- package/dist/lib/auth.js +76 -0
- package/dist/lib/calendar.d.ts +6 -0
- package/dist/lib/calendar.js +37 -0
- package/dist/lib/n8n.d.ts +10 -0
- package/dist/lib/n8n.js +24 -0
- package/dist/lib/output.d.ts +4 -0
- package/dist/lib/output.js +94 -0
- package/dist/lib/skills.d.ts +1 -0
- package/dist/lib/skills.js +11 -0
- package/dist/lib/supabase.d.ts +2 -0
- package/dist/lib/supabase.js +7 -0
- package/package.json +28 -0
- package/skills/meetings/SKILL.md +32 -0
- package/skills/record/SKILL.md +37 -0
- package/skills/summary/SKILL.md +35 -0
- package/skills/upload/SKILL.md +43 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
function formatDate(dateStr) {
|
|
2
|
+
return new Date(dateStr).toLocaleDateString("ko-KR", {
|
|
3
|
+
year: "numeric",
|
|
4
|
+
month: "short",
|
|
5
|
+
day: "numeric",
|
|
6
|
+
weekday: "short",
|
|
7
|
+
hour: "2-digit",
|
|
8
|
+
minute: "2-digit",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function formatDuration(seconds) {
|
|
12
|
+
if (!seconds)
|
|
13
|
+
return "";
|
|
14
|
+
const mins = Math.floor(seconds / 60);
|
|
15
|
+
const secs = seconds % 60;
|
|
16
|
+
if (mins === 0)
|
|
17
|
+
return `${secs}초`;
|
|
18
|
+
return secs > 0 ? `${mins}분 ${secs}초` : `${mins}분`;
|
|
19
|
+
}
|
|
20
|
+
export function formatMeetingsList(meetings, json) {
|
|
21
|
+
if (json)
|
|
22
|
+
return JSON.stringify(meetings, null, 2);
|
|
23
|
+
if (meetings.length === 0)
|
|
24
|
+
return "조회된 회의가 없습니다.";
|
|
25
|
+
const lines = meetings.map((m) => {
|
|
26
|
+
const date = formatDate(m.date);
|
|
27
|
+
const dur = formatDuration(m.duration_seconds);
|
|
28
|
+
return `- **${m.title}** (${date}${dur ? ` · ${dur}` : ""}) [${m.status}]\n ID: \`${m.id}\``;
|
|
29
|
+
});
|
|
30
|
+
return [`# 회의 목록 (${meetings.length}건)`, "", ...lines].join("\n");
|
|
31
|
+
}
|
|
32
|
+
export function formatMeetingDetail(meeting, summary, transcripts, json, full = false) {
|
|
33
|
+
if (json)
|
|
34
|
+
return JSON.stringify({ meeting, summary, transcripts }, null, 2);
|
|
35
|
+
const date = formatDate(meeting.date);
|
|
36
|
+
const dur = formatDuration(meeting.duration_seconds);
|
|
37
|
+
const parts = [];
|
|
38
|
+
parts.push(`# ${meeting.title}`);
|
|
39
|
+
parts.push(`> ${date}${dur ? ` · ${dur}` : ""} · [${meeting.status}]`);
|
|
40
|
+
parts.push(`> ID: \`${meeting.id}\``);
|
|
41
|
+
parts.push("");
|
|
42
|
+
if (summary) {
|
|
43
|
+
if (summary.key_decisions.length > 0) {
|
|
44
|
+
parts.push("## 핵심 결정사항");
|
|
45
|
+
parts.push(...summary.key_decisions.map((d) => `- ${d}`));
|
|
46
|
+
parts.push("");
|
|
47
|
+
}
|
|
48
|
+
if (summary.action_items.length > 0) {
|
|
49
|
+
parts.push("## 액션아이템");
|
|
50
|
+
parts.push(...summary.action_items.map((a) => `- ${a}`));
|
|
51
|
+
parts.push("");
|
|
52
|
+
}
|
|
53
|
+
if (summary.topics.length > 0) {
|
|
54
|
+
parts.push("## 주요 주제");
|
|
55
|
+
parts.push(...summary.topics.map((t) => `- ${t}`));
|
|
56
|
+
parts.push("");
|
|
57
|
+
}
|
|
58
|
+
parts.push("## 전체 요약");
|
|
59
|
+
parts.push(summary.content);
|
|
60
|
+
parts.push("");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
parts.push("_요약이 아직 생성되지 않았습니다._");
|
|
64
|
+
parts.push("");
|
|
65
|
+
}
|
|
66
|
+
if (full && transcripts.length > 0) {
|
|
67
|
+
parts.push("## 전사록");
|
|
68
|
+
parts.push(...transcripts.map((t) => `[${t.speaker_label}]: ${t.text}`));
|
|
69
|
+
parts.push("");
|
|
70
|
+
}
|
|
71
|
+
else if (transcripts.length > 0) {
|
|
72
|
+
parts.push(`_전사록: ${transcripts.length}건 (--full 옵션으로 전체 보기)_`);
|
|
73
|
+
}
|
|
74
|
+
return parts.join("\n");
|
|
75
|
+
}
|
|
76
|
+
export function formatSearchResults(results, query, json) {
|
|
77
|
+
if (json)
|
|
78
|
+
return JSON.stringify(results, null, 2);
|
|
79
|
+
if (results.length === 0)
|
|
80
|
+
return `"${query}" 검색 결과가 없습니다.`;
|
|
81
|
+
const lines = results.map((m) => {
|
|
82
|
+
const date = new Date(m.date).toLocaleDateString("ko-KR", {
|
|
83
|
+
year: "numeric",
|
|
84
|
+
month: "short",
|
|
85
|
+
day: "numeric",
|
|
86
|
+
});
|
|
87
|
+
return `- **${m.title}** (${date}) [${m.status}] -- ${m.match} 일치\n ID: \`${m.id}\``;
|
|
88
|
+
});
|
|
89
|
+
return [
|
|
90
|
+
`# "${query}" 검색 결과 (${results.length}건)`,
|
|
91
|
+
"",
|
|
92
|
+
...lines,
|
|
93
|
+
].join("\n");
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkSkillsInstalled(): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
const SKILLS_DIR = join(homedir(), ".claude", "skills");
|
|
5
|
+
const SEOGITAN_SKILLS = ["seogitan-record", "seogitan-meetings", "seogitan-summary", "seogitan-upload"];
|
|
6
|
+
export function checkSkillsInstalled() {
|
|
7
|
+
const missing = SEOGITAN_SKILLS.filter((s) => !existsSync(join(SKILLS_DIR, s, "SKILL.md")));
|
|
8
|
+
if (missing.length > 0) {
|
|
9
|
+
console.log("\n💡 Claude Code 스킬이 설치되지 않았습니다. `seogitan setup`을 실행하세요.\n");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createAuthenticatedClient } from "seogitan-core";
|
|
2
|
+
import { getValidAccessToken } from "./auth.js";
|
|
3
|
+
import { SUPABASE_URL, SUPABASE_ANON_KEY } from "../config.js";
|
|
4
|
+
export async function getSupabaseClient() {
|
|
5
|
+
const accessToken = await getValidAccessToken();
|
|
6
|
+
return createAuthenticatedClient(SUPABASE_URL, SUPABASE_ANON_KEY, accessToken);
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seogitan",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"seogitan": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"skills"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"start": "node dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"seogitan-core": "^1.0.0",
|
|
19
|
+
"@supabase/supabase-js": "^2.49.4",
|
|
20
|
+
"commander": "^13.0.0",
|
|
21
|
+
"open": "^10.0.0",
|
|
22
|
+
"zod": "^3.24.3"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.15.3",
|
|
26
|
+
"typescript": "^5.8.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: meetings
|
|
3
|
+
description: "회의 목록 조회, 상세 보기, 검색을 수행합니다. '회의 목록', '회의 조회', '미팅 리스트', 'meetings', '회의 검색', '회의 찾기' 등의 요청 시 사용."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /meetings — 회의 관리
|
|
7
|
+
|
|
8
|
+
## 목록 조회
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
seogitan meetings list --json $ARGUMENTS
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
옵션: `--status completed`, `--from 2026-04-01`, `--to 2026-04-03`, `--limit 20`
|
|
15
|
+
|
|
16
|
+
## 상세 조회
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
seogitan meetings show <meeting-id> --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
전체 전사록 포함: `--full`
|
|
23
|
+
|
|
24
|
+
## 검색
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
seogitan meetings search "키워드" --json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 결과 처리
|
|
31
|
+
|
|
32
|
+
JSON 결과에서 각 회의의 ID, 제목, 날짜, 상태를 추출하여 보기 좋게 정리해서 보여준다.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: record
|
|
3
|
+
description: "터미널에서 회의 녹음을 시작합니다. ffmpeg로 마이크를 캡처하고 5분마다 자동으로 청크를 업로드/전사합니다. '녹음 시작', '회의 녹음', 'record', '녹음해줘' 등의 요청 시 사용."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /record — 회의 녹음 시작
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS가 제공되면 회의 제목으로 사용합니다.
|
|
9
|
+
|
|
10
|
+
## 실행
|
|
11
|
+
|
|
12
|
+
녹음은 백그라운드로 실행합니다. 녹음이 매우 길 수 있으므로 Claude가 프로세스를 폴링하거나 기다리지 마세요.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
nohup seogitan record "$ARGUMENTS" > /tmp/seogitan-record.log 2>&1 &
|
|
16
|
+
echo "PID: $!"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
seogitan이 설치되지 않은 경우:
|
|
20
|
+
```bash
|
|
21
|
+
nohup node packages/cli/dist/index.js record "$ARGUMENTS" > /tmp/seogitan-record.log 2>&1 &
|
|
22
|
+
echo "PID: $!"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
실행 후 사용자에게 알려줄 내용:
|
|
26
|
+
- "녹음이 백그라운드에서 시작되었습니다"
|
|
27
|
+
- "종료하려면 별도 터미널에서 `kill -INT <PID>` 실행"
|
|
28
|
+
- "로그 확인: `tail -f /tmp/seogitan-record.log`"
|
|
29
|
+
- 녹음 중에도 대화를 계속할 수 있습니다
|
|
30
|
+
|
|
31
|
+
## 녹음 종료 후 확인
|
|
32
|
+
|
|
33
|
+
사용자가 녹음을 종료했다고 하면:
|
|
34
|
+
```bash
|
|
35
|
+
tail -5 /tmp/seogitan-record.log
|
|
36
|
+
```
|
|
37
|
+
로그에서 Meeting ID를 찾아 `/summary` 스킬로 요약을 조회합니다.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: summary
|
|
3
|
+
description: "회의 요약을 조회하거나 생성합니다. 요약에서 더 자세한 내용이 필요하면 전사록을 자동으로 불러와 맥락을 파악합니다. '회의 요약', '요약 보여줘', '미팅 요약', 'summary', '요약 생성', '자세히 알려줘', '더 알려줘' 등의 요청 시 사용."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /summary — 회의 요약 조회/심화
|
|
7
|
+
|
|
8
|
+
## 1단계: 요약 조회
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
seogitan meetings show "$ARGUMENTS" --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
결과에서 핵심 결정사항, 액션아이템, 주요 주제를 구분하여 보여준다.
|
|
15
|
+
|
|
16
|
+
요약이 없으면 자동 생성:
|
|
17
|
+
```bash
|
|
18
|
+
seogitan summarize "$ARGUMENTS" --wait --timeout 120
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 2단계: 심화 (사용자가 "자세히", "더 알려줘", "구체적으로" 등 요청 시)
|
|
22
|
+
|
|
23
|
+
요약만으로 부족할 때 전사록을 불러와서 맥락을 파악한다:
|
|
24
|
+
```bash
|
|
25
|
+
seogitan meetings show "$ARGUMENTS" --full --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
전사록의 `transcripts` 배열에서 화자별 발언을 읽고, 사용자가 궁금해하는 부분에 대해 전사록 원문을 인용하며 구체적으로 설명한다.
|
|
29
|
+
|
|
30
|
+
## 사용 패턴
|
|
31
|
+
|
|
32
|
+
- "최근 회의 요약해줘" → 1단계 (요약만)
|
|
33
|
+
- "레퍼럴 관련 결정사항 자세히 알려줘" → 1단계 후 2단계 (전사록에서 관련 부분 찾기)
|
|
34
|
+
- "누가 뭐라고 했는지 알려줘" → 바로 2단계 (전사록 필요)
|
|
35
|
+
- "액션아이템 담당자 누구야?" → 2단계 (전사록에서 맥락 파악)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upload
|
|
3
|
+
description: "녹음된 오디오 파일을 업로드하여 전사/요약합니다. 캘린더 일정 검색으로 회의를 자동 연결할 수 있습니다. '파일 업로드', '녹음 파일', '음성 파일 올려', 'upload', '회의 파일' 등의 요청 시 사용."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /upload — 오디오 파일 업로드
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS에 파일 경로가 포함되어야 합니다.
|
|
9
|
+
|
|
10
|
+
## 기본 업로드
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
seogitan upload "$ARGUMENTS"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 캘린더 연결 (비인터랙티브 — Claude 권장)
|
|
17
|
+
|
|
18
|
+
제목으로 캘린더 검색 후 자동 연결:
|
|
19
|
+
```bash
|
|
20
|
+
seogitan upload "<파일경로>" --calendar-search "<검색어>" --calendar-index 1
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
특정 날짜의 캘린더에서 선택:
|
|
24
|
+
```bash
|
|
25
|
+
seogitan upload "<파일경로>" --calendar-date 2026-03-27 --calendar-index 1
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 옵션
|
|
29
|
+
|
|
30
|
+
- `-t, --title <제목>` — 회의 제목 (캘린더 미연결 시)
|
|
31
|
+
- `--calendar-search <검색어>` — 최근 30일 캘린더에서 검색
|
|
32
|
+
- `--calendar-date <YYYY-MM-DD>` — 특정 날짜 일정 조회
|
|
33
|
+
- `--calendar-index <n>` — N번째 결과 자동 선택
|
|
34
|
+
- `--calendar` — 오늘 캘린더에서 인터랙티브 선택
|
|
35
|
+
|
|
36
|
+
## 사용 패턴
|
|
37
|
+
|
|
38
|
+
사용자가 "이 파일은 지난주 레퍼럴 회의야"라고 하면:
|
|
39
|
+
```bash
|
|
40
|
+
seogitan upload "<파일>" --calendar-search "레퍼럴" --calendar-index 1
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
검색 결과가 여러 개면 목록을 보여주고 사용자에게 번호를 물어본 후 `--calendar-index`로 재실행.
|