seogitan 1.2.0 → 1.4.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.js +1 -3
- package/dist/commands/meetings.js +60 -1
- package/dist/commands/record.js +3 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/lib/audio.js +32 -6
- package/package.json +2 -2
package/dist/commands/login.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as http from "node:http";
|
|
2
2
|
import { saveTokens } from "../lib/auth.js";
|
|
3
|
-
|
|
4
|
-
// 또는 환경변수로 직접 지정 가능
|
|
5
|
-
const WEB_URL = process.env.SEOGITAN_WEB_URL || "https://seogitan.vercel.app";
|
|
3
|
+
import { WEB_URL } from "../config.js";
|
|
6
4
|
export function registerLoginCommand(program) {
|
|
7
5
|
program
|
|
8
6
|
.command("login")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listMeetings, getMeeting, getMeetingSummary, getMeetingTranscripts, searchMeetings, } from "seogitan-core";
|
|
1
|
+
import { listMeetings, getMeeting, getMeetingSummary, getMeetingTranscripts, searchMeetings, deleteMeeting, updateMeetingTags, } from "seogitan-core";
|
|
2
2
|
import { getSupabaseClient } from "../lib/supabase.js";
|
|
3
3
|
import { formatMeetingsList, formatMeetingDetail, formatSearchResults, } from "../lib/output.js";
|
|
4
4
|
import { EXIT_CODES } from "../exit-codes.js";
|
|
@@ -14,15 +14,23 @@ export function registerMeetingsCommand(program) {
|
|
|
14
14
|
.option("--from <date>", "조회 시작 날짜 (ISO 8601)")
|
|
15
15
|
.option("--to <date>", "조회 종료 날짜 (ISO 8601)")
|
|
16
16
|
.option("--limit <n>", "조회할 최대 개수", "10")
|
|
17
|
+
.option("--sort <order>", "정렬 (newest|name)", "newest")
|
|
18
|
+
.option("--filter <type>", "필터 (mine|shared)")
|
|
19
|
+
.option("--tag <tag>", "태그로 필터링")
|
|
17
20
|
.option("--json", "JSON 형식으로 출력", false)
|
|
18
21
|
.action(async (opts) => {
|
|
19
22
|
try {
|
|
20
23
|
const supabase = await getSupabaseClient();
|
|
24
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
21
25
|
const data = await listMeetings(supabase, {
|
|
22
26
|
limit: parseInt(opts.limit, 10),
|
|
23
27
|
status: opts.status,
|
|
24
28
|
date_from: opts.from,
|
|
25
29
|
date_to: opts.to,
|
|
30
|
+
sort: opts.sort,
|
|
31
|
+
ownership: opts.filter,
|
|
32
|
+
userId: user?.id,
|
|
33
|
+
tag: opts.tag,
|
|
26
34
|
});
|
|
27
35
|
console.log(formatMeetingsList(data, opts.json));
|
|
28
36
|
}
|
|
@@ -157,4 +165,55 @@ export function registerMeetingsCommand(program) {
|
|
|
157
165
|
process.exit(EXIT_CODES.ERROR);
|
|
158
166
|
}
|
|
159
167
|
});
|
|
168
|
+
// delete
|
|
169
|
+
meetings
|
|
170
|
+
.command("delete <id>")
|
|
171
|
+
.description("회의를 삭제합니다 (생성자만 가능)")
|
|
172
|
+
.option("--force", "확인 없이 삭제", false)
|
|
173
|
+
.action(async (id, opts) => {
|
|
174
|
+
try {
|
|
175
|
+
const supabase = await getSupabaseClient();
|
|
176
|
+
const meeting = await getMeeting(supabase, id);
|
|
177
|
+
if (!meeting) {
|
|
178
|
+
console.error(`회의를 찾을 수 없습니다: ${id}`);
|
|
179
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
180
|
+
}
|
|
181
|
+
if (!opts.force) {
|
|
182
|
+
const { createInterface } = await import("node:readline");
|
|
183
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
184
|
+
const answer = await new Promise((resolve) => rl.question(`"${meeting.title}" 회의를 삭제할까요? (y/N) `, resolve));
|
|
185
|
+
rl.close();
|
|
186
|
+
if (answer.toLowerCase() !== "y") {
|
|
187
|
+
console.log("삭제가 취소되었습니다.");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
await deleteMeeting(supabase, id);
|
|
192
|
+
console.log(`회의가 삭제되었습니다: ${meeting.title}`);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
console.error(`오류: ${err instanceof Error ? err.message : String(err)}`);
|
|
196
|
+
process.exit(EXIT_CODES.ERROR);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// tag
|
|
200
|
+
meetings
|
|
201
|
+
.command("tag <id> [tags...]")
|
|
202
|
+
.description("회의에 태그를 설정합니다")
|
|
203
|
+
.action(async (id, tags) => {
|
|
204
|
+
try {
|
|
205
|
+
const supabase = await getSupabaseClient();
|
|
206
|
+
const meeting = await getMeeting(supabase, id);
|
|
207
|
+
if (!meeting) {
|
|
208
|
+
console.error(`회의를 찾을 수 없습니다: ${id}`);
|
|
209
|
+
process.exit(EXIT_CODES.NOT_FOUND);
|
|
210
|
+
}
|
|
211
|
+
await updateMeetingTags(supabase, id, tags);
|
|
212
|
+
console.log(`태그 설정됨: ${tags.length > 0 ? tags.join(", ") : "(태그 삭제)"}`);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error(`오류: ${err instanceof Error ? err.message : String(err)}`);
|
|
216
|
+
process.exit(EXIT_CODES.ERROR);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
160
219
|
}
|
package/dist/commands/record.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getValidGoogleToken } from "../lib/calendar.js";
|
|
|
5
5
|
import { checkFfmpegInstalled, listAudioDevices, createAudioRecorder, createTempOutputDir, } from "../lib/audio.js";
|
|
6
6
|
import { triggerChunkTranscription } from "../lib/n8n.js";
|
|
7
7
|
import { EXIT_CODES } from "../exit-codes.js";
|
|
8
|
+
import { WEB_URL } from "../config.js";
|
|
8
9
|
import { fetchTodayEvents, linkCalendarEvent } from "seogitan-core";
|
|
9
10
|
export function registerRecordCommand(program) {
|
|
10
11
|
program
|
|
@@ -205,6 +206,7 @@ export function registerRecordCommand(program) {
|
|
|
205
206
|
catch { }
|
|
206
207
|
}
|
|
207
208
|
console.log(`녹음 완료. Meeting ID: ${meetingId}`);
|
|
209
|
+
console.log(`웹에서 보기: ${WEB_URL}/meetings/${meetingId}`);
|
|
208
210
|
}
|
|
209
211
|
catch (err) {
|
|
210
212
|
console.error(`정리 중 오류: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -216,6 +218,7 @@ export function registerRecordCommand(program) {
|
|
|
216
218
|
const chunkMinutes = Math.floor(chunkDurationSeconds / 60);
|
|
217
219
|
console.log(`녹음 시작: ${tempTitle} (${chunkMinutes}분마다 자동 전사, Ctrl+C로 종료)`);
|
|
218
220
|
console.log(`Meeting ID: ${meetingId}`);
|
|
221
|
+
console.log(`웹에서 보기: ${WEB_URL}/meetings/${meetingId}`);
|
|
219
222
|
}
|
|
220
223
|
catch (err) {
|
|
221
224
|
console.error(`오류: ${err instanceof Error ? err.message : String(err)}`);
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -6,6 +6,8 @@ export const SUPABASE_URL = process.env.SUPABASE_URL ||
|
|
|
6
6
|
export const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY ||
|
|
7
7
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
|
|
8
8
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBuZWR3cWJidXF2aXlrbmdwZGJpIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQ1NTc2NDYsImV4cCI6MjA5MDEzMzY0Nn0.cbLANAeWNy6XxL-dhOIdnGRV_C0c68lLM94H1-nk-fg";
|
|
9
|
+
// Web App URL
|
|
10
|
+
export const WEB_URL = process.env.SEOGITAN_WEB_URL || "https://seogitan.vercel.app";
|
|
9
11
|
// Supabase Edge Functions URL
|
|
10
12
|
export const SUPABASE_FUNCTIONS_URL = process.env.SUPABASE_FUNCTIONS_URL ||
|
|
11
13
|
"https://pnedwqbbuqviykngpdbi.supabase.co/functions/v1";
|
package/dist/lib/audio.js
CHANGED
|
@@ -26,13 +26,32 @@ export function checkFfmpegInstalled() {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
export function listAudioDevices() {
|
|
29
|
+
const ffmpeg = getFfmpegPath();
|
|
30
|
+
let cmd;
|
|
31
|
+
if (process.platform === "darwin") {
|
|
32
|
+
cmd = `"${ffmpeg}" -f avfoundation -list_devices true -i dummy`;
|
|
33
|
+
}
|
|
34
|
+
else if (process.platform === "win32") {
|
|
35
|
+
cmd = `"${ffmpeg}" -f dshow -list_devices true -i dummy`;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Linux: use pactl
|
|
39
|
+
try {
|
|
40
|
+
const out = execSync("pactl list short sources", { encoding: "utf-8" });
|
|
41
|
+
return out.split("\n").filter(Boolean).map((line, i) => ({
|
|
42
|
+
index: i,
|
|
43
|
+
name: line.split("\t")[1] || line,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
29
50
|
try {
|
|
30
|
-
|
|
31
|
-
execSync(`"${getFfmpegPath()}" -f avfoundation -list_devices true -i dummy`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
51
|
+
execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
32
52
|
return [];
|
|
33
53
|
}
|
|
34
54
|
catch (err) {
|
|
35
|
-
// ffmpeg writes device list to stderr even on "error" exit
|
|
36
55
|
if (err && typeof err === "object" && "stderr" in err) {
|
|
37
56
|
return parseAudioDevices(err.stderr);
|
|
38
57
|
}
|
|
@@ -72,9 +91,16 @@ export function createAudioRecorder(options) {
|
|
|
72
91
|
let watcher = null;
|
|
73
92
|
// Build ffmpeg args based on platform
|
|
74
93
|
function buildFfmpegArgs() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
let inputArgs;
|
|
95
|
+
if (process.platform === "darwin") {
|
|
96
|
+
inputArgs = ["-f", "avfoundation", "-i", `:${deviceIndex}`];
|
|
97
|
+
}
|
|
98
|
+
else if (process.platform === "win32") {
|
|
99
|
+
inputArgs = ["-f", "dshow", "-i", `audio=${deviceIndex}`];
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
inputArgs = ["-f", "pulse", "-i", "default"];
|
|
103
|
+
}
|
|
78
104
|
return [
|
|
79
105
|
...inputArgs,
|
|
80
106
|
"-ar",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "seogitan",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"seogitan": "dist/index.js"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"commander": "^13.0.0",
|
|
20
20
|
"ffmpeg-static": "^5.3.0",
|
|
21
21
|
"open": "^10.0.0",
|
|
22
|
-
"seogitan-core": "^1.
|
|
22
|
+
"seogitan-core": "^1.1.0",
|
|
23
23
|
"zod": "^3.24.3"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|