feat(V2-1b): packages/core 추출 — 공유 타입/에러/채널/유틸 분리

packages/core (@d3ro/core) 신규 생성:
- types.ts, errors.ts, ipc-channels.ts, constants.ts (shared에서 이동)
- utils/meeting-markdown.ts, utils/markdown-to-docx.ts (main/utils에서 이동)
- subpath exports 정의 (./types, ./errors, ./ipc-channels, ./constants,
  ./utils/meeting-markdown, ./utils/markdown-to-docx)
- src/index.ts barrel export 추가
- docx를 core 자체 dependency로 선언

apps/desktop 연결:
- package.json에 @d3ro/core: '*' dep 추가
- tsconfig.node/web.json paths에 @d3ro/core/* 추가
- electron.vite.config.ts 3개 섹션 alias 추가 (main/preload/renderer)
- externalizeDepsPlugin exclude에 @d3ro/core (workspace 소스 번들 대상)
- vitest.config.ts alias 추가

일괄 치환 (79 파일, 167건):
- @shared/{types,errors,ipc-channels,constants} → @d3ro/core/*
- static/dynamic import + type expression import 모두 포함
- MeetingModeService.ts의 ../utils/* 상대 경로 → @d3ro/core/utils/*
- @shared/theme-vars는 V2-1c 범위로 남김 (WindowManager만 사용)

M1 수정 포함:
- electron.vite.config.ts의 resolve('src/shared') → resolve(__dirname, ...)
  CWD 독립적으로 동작하도록 견고화

검증:
- typecheck 통과
- build 통과 (main+preload+renderer)
- dev 런타임 → DB/핫키/Ollama 모두 정상, 기존 데이터 연속성 유지
This commit is contained in:
yunchan8804 2026-04-08 14:39:46 +09:00
parent a4cb4c0805
commit 3b0eb3393b
95 changed files with 315 additions and 212 deletions

View file

@ -157,7 +157,7 @@ async function initPopupWindows(): Promise<void> {
unregisterPopupNavKeys()
} else {
const instructions = getCustomInstructionService().getAll()
const activeId = configGet('activeInstructionId' as keyof import('@shared/types').AppConfig) as unknown as string
const activeId = configGet('activeInstructionId' as keyof import('@d3ro/core/types').AppConfig) as unknown as string
showCommandPopup(instructions as unknown as Array<Record<string, unknown>>, activeId || null)
registerPopupNavKeys('command')
}
@ -345,12 +345,12 @@ function setupCommandPopupIPC(): void {
if (data.id) {
// 명령어 선택 → 활성 명령어로 설정
configSet('activeInstructionId' as keyof import('@shared/types').AppConfig, data.id as never)
configSet('activeInstructionId' as keyof import('@d3ro/core/types').AppConfig, data.id as never)
configSet('defaultLLMAction', 'custom')
logger.info(`Active command set: ${data.name} (${data.id})`)
} else {
// 선택 해제 → 명령어 없음 (원본 삽입)
configSet('activeInstructionId' as keyof import('@shared/types').AppConfig, '' as never)
configSet('activeInstructionId' as keyof import('@d3ro/core/types').AppConfig, '' as never)
configSet('defaultLLMAction', 'none')
logger.info('Active command cleared (none)')
}

View file

@ -1,13 +1,13 @@
// src/main/ipc/audio-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getAudioCaptureService, calculateRMS } from '../services/AudioCaptureService'
import { getMainWindow } from '../windows/WindowManager'
import { configGet, configSet } from '../services/ConfigService'
import { getLogger } from '../services/LoggerService'
import type { SetDeviceParams } from '@shared/types'
import type { SetDeviceParams } from '@d3ro/core/types'
const logger = getLogger('audio-handlers')

View file

@ -2,10 +2,10 @@
// Phase 10.1: Live Caption IPC 핸들러
import { ipcMain, session, desktopCapturer } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getCaptionService } from '../services/CaptionService'
import type { CaptionConfig } from '@shared/types'
import type { CaptionConfig } from '@d3ro/core/types'
export function registerCaptionHandlers(): void {
// 시스템 오디오 루프백: setDisplayMediaRequestHandler로 audio: 'loopback' 설정

View file

@ -2,15 +2,15 @@
// Phase 10.4: Multi-LLM Chain IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode, D3ROError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode, D3ROError } from '@d3ro/core/errors'
import { getChainService } from '../services/ChainService'
import type {
CreateChainParams,
UpdateChainParams,
DeleteChainParams,
ExecuteChainParams
} from '@shared/types'
} from '@d3ro/core/types'
export function registerChainHandlers(): void {
ipcMain.handle(IPC_CHANNELS.CHAIN.GET_ALL, async () => {

View file

@ -1,8 +1,8 @@
// src/main/ipc/config-handlers.ts
import { ipcMain, BrowserWindow } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { configGet, configSet, configGetAll, configReset } from '../services/ConfigService'
import { getAutoLaunchService } from '../services/AutoLaunchService'
import { reapplyThemeToAllPopups } from '../windows/WindowManager'
@ -22,7 +22,7 @@ import type {
SetAutoLaunchParams,
SetCloseToTrayParams,
AppConfig
} from '@shared/types'
} from '@d3ro/core/types'
export function registerConfigHandlers(): void {
ipcMain.handle(IPC_CHANNELS.CONFIG.GET, async (_event, params: ConfigGetParams) => {

View file

@ -2,8 +2,8 @@
// Phase 10.2 스크린 컨텍스트 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getScreenContextService } from '../services/ScreenContextService'
export function registerContextHandlers(): void {

View file

@ -1,8 +1,8 @@
// src/main/ipc/dictionary-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getDictionaryService } from '../services/DictionaryService'
import type {
DictionaryQueryParams,
@ -10,7 +10,7 @@ import type {
DictionaryUpdateParams,
DictionaryDeleteParams,
DictionarySearchParams
} from '@shared/types'
} from '@d3ro/core/types'
export function registerDictionaryHandlers(): void {
ipcMain.handle(IPC_CHANNELS.DICTIONARY.GET_ALL, async (_event, params: DictionaryQueryParams) => {

View file

@ -2,11 +2,11 @@
// Phase 12.1: 파일 전사 IPC 핸들러
import { ipcMain, dialog } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getFileTranscriptionService } from '../services/FileTranscriptionService'
import { getLogger } from '../services/LoggerService'
import type { FileTranscriptionStartParams } from '@shared/types'
import type { FileTranscriptionStartParams } from '@d3ro/core/types'
const logger = getLogger('file-transcription-handlers')

View file

@ -1,15 +1,15 @@
// src/main/ipc/history-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getHistoryService } from '../services/HistoryService'
import type {
HistoryQueryParams,
HistoryGetByIdParams,
HistoryDeleteParams,
HistorySearchParams
} from '@shared/types'
} from '@d3ro/core/types'
export function registerHistoryHandlers(): void {
ipcMain.handle(IPC_CHANNELS.HISTORY.GET_ALL, async (_event, params: HistoryQueryParams) => {

View file

@ -1,11 +1,11 @@
// src/main/ipc/hotkey-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getHotkeyService } from '../services/HotkeyService'
import { configGet, configSet } from '../services/ConfigService'
import type { SetHotkeyParams, SetEnabledParams } from '@shared/types'
import type { SetHotkeyParams, SetEnabledParams } from '@d3ro/core/types'
export function registerHotkeyHandlers(): void {
ipcMain.handle(IPC_CHANNELS.HOTKEY.GET_DICTATION_SHORTCUT, async () => {

View file

@ -1,7 +1,7 @@
// src/main/ipc/instruction-handlers.ts
import { ipcMain } from 'electron'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getCustomInstructionService } from '../services/CustomInstructionService'
import type { CustomInstruction } from '../services/CustomInstructionService'

View file

@ -2,15 +2,15 @@
// Phase 11: 라이센스 IPC 핸들러
import { ipcMain, BrowserWindow } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getLicenseService } from '../services/LicenseService'
import type {
ActivateLicenseParams,
UpgradePromptEvent,
LicenseInfo,
} from '@shared/types'
import { Feature } from '@shared/types'
} from '@d3ro/core/types'
import { Feature } from '@d3ro/core/types'
/** 업그레이드 유도 이벤트를 모든 렌더러에 broadcast */
function broadcastUpgradePrompt(event: UpgradePromptEvent): void {

View file

@ -1,12 +1,12 @@
// src/main/ipc/llm-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getLocalLLMService } from '../services/LocalLLMService'
import { configGet, configSet } from '../services/ConfigService'
import { getMainWindow } from '../windows/WindowManager'
import type { SetLLMModelParams, SetServerUrlParams, LLMProcessParams } from '@shared/types'
import type { SetLLMModelParams, SetServerUrlParams, LLMProcessParams } from '@d3ro/core/types'
export function registerLLMHandlers(): void {
// LLM 가용성 변경 시 렌더러에 상태 전파

View file

@ -2,15 +2,15 @@
// Phase 14.5: Meeting Document Template IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getMeetingDocTemplateService } from '../services/MeetingDocTemplateService'
import { getLogger } from '../services/LoggerService'
import type {
CreateMeetingDocTemplateParams,
UpdateMeetingDocTemplateParams,
DeleteMeetingDocTemplateParams,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('meeting-doc-template-handlers')

View file

@ -2,8 +2,8 @@
// Phase 14 / 14.5: Meeting Mode IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getMeetingModeService } from '../services/MeetingModeService'
import { getLogger } from '../services/LoggerService'
import type {
@ -15,7 +15,7 @@ import type {
MeetingGenerateDocParams,
MeetingExportFormat,
MeetingChatSendParams,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('meeting-mode-handlers')

View file

@ -2,11 +2,11 @@
// Phase 12.2: 회의록 요약 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getMeetingSummaryService } from '../services/MeetingSummaryService'
import { getLogger } from '../services/LoggerService'
import type { MeetingSummarizeParams, MeetingSummaryGetParams, MeetingSummaryExportParams } from '@shared/types'
import type { MeetingSummarizeParams, MeetingSummaryGetParams, MeetingSummaryExportParams } from '@d3ro/core/types'
const logger = getLogger('meeting-summary-handlers')

View file

@ -2,8 +2,8 @@
// Phase 10.3: 음성 메모 태그 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode, D3ROError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode, D3ROError } from '@d3ro/core/errors'
import { getMemoService } from '../services/MemoService'
import type {
GetTagsParams,
@ -11,7 +11,7 @@ import type {
RemoveTagParams,
SearchByTagParams,
ExportMemoParams
} from '@shared/types'
} from '@d3ro/core/types'
export function registerMemoHandlers(): void {
ipcMain.handle(IPC_CHANNELS.MEMO.GET_TAGS, async (_event, params: GetTagsParams) => {

View file

@ -2,11 +2,11 @@
// Phase 13.2: 로컬 RAG IPC 핸들러
import { ipcMain, dialog } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getRAGService } from '../services/RAGService'
import { getLogger } from '../services/LoggerService'
import type { RAGQueryParams, RAGRemoveDocumentParams } from '@shared/types'
import type { RAGQueryParams, RAGRemoveDocumentParams } from '@d3ro/core/types'
const logger = getLogger('rag-handlers')

View file

@ -1,11 +1,11 @@
// src/main/ipc/stt-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getLocalSTTService } from '../services/LocalSTTService'
import { configGet, configSet } from '../services/ConfigService'
import type { SetSTTModelParams, SetSTTLanguageParams } from '@shared/types'
import type { SetSTTModelParams, SetSTTLanguageParams } from '@d3ro/core/types'
export function registerSTTHandlers(): void {
ipcMain.handle(IPC_CHANNELS.STT.GET_STATUS, async () => {

View file

@ -1,9 +1,9 @@
// src/main/ipc/system-handlers.ts
import { ipcMain, app, systemPreferences } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess } from '@shared/errors'
import type { PermissionStatus, PlaySoundParams, SetSoundEnabledParams } from '@shared/types'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess } from '@d3ro/core/errors'
import type { PermissionStatus, PlaySoundParams, SetSoundEnabledParams } from '@d3ro/core/types'
import { getSoundEffectService } from '../services/SoundEffectService'
export function registerSystemHandlers(): void {

View file

@ -2,8 +2,8 @@
// Phase 12.3: 딕테이션 템플릿 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getDictationTemplateService } from '../services/DictationTemplateService'
import { getLogger } from '../services/LoggerService'
import type {
@ -12,7 +12,7 @@ import type {
DeleteTemplateParams,
StartTemplateSessionParams,
SetFieldValueParams,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('template-handlers')
@ -33,7 +33,7 @@ export function registerTemplateHandlers(): void {
// 라이센스 체크
try {
const { getLicenseService } = await import('../services/LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.DICTATION_TEMPLATE)
if (!access.allowed) {

View file

@ -2,11 +2,11 @@
// Phase 13.3: OS 자동화 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getVoiceActionService } from '../services/VoiceActionService'
import { getLogger } from '../services/LoggerService'
import type { VoiceActionExecuteParams } from '@shared/types'
import type { VoiceActionExecuteParams } from '@d3ro/core/types'
const logger = getLogger('voice-action-handlers')

View file

@ -2,10 +2,10 @@
// Phase 10.5: 음성 단축키 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getVoiceCommandService } from '../services/VoiceCommandService'
import type { SetVoiceCommandKeywordsParams, SetVoiceCommandEnabledParams } from '@shared/types'
import type { SetVoiceCommandKeywordsParams, SetVoiceCommandEnabledParams } from '@d3ro/core/types'
export function registerVoiceCommandHandlers(): void {
const CH = IPC_CHANNELS.VOICE_COMMAND

View file

@ -2,11 +2,11 @@
// Phase 13.1: 음성 대화 모드 IPC 핸들러
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ErrorCode, ipcSuccess, ipcError } from '@d3ro/core/errors'
import { getVoiceConversationService } from '../services/VoiceConversationService'
import { getLogger } from '../services/LoggerService'
import type { ConversationSendParams } from '@shared/types'
import type { ConversationSendParams } from '@d3ro/core/types'
const logger = getLogger('voice-conversation-handlers')

View file

@ -1,10 +1,10 @@
// src/main/ipc/voice-handlers.ts
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
import { getVoiceModeService } from '../services/VoiceModeService'
import type { StartRecordingParams, StopRecordingParams, CancelRecordingParams, SetVoiceModeParams } from '@shared/types'
import type { StartRecordingParams, StopRecordingParams, CancelRecordingParams, SetVoiceModeParams } from '@d3ro/core/types'
export function registerVoiceHandlers(): void {
ipcMain.handle(IPC_CHANNELS.VOICE.START_RECORDING, async (_event, params: StartRecordingParams) => {

View file

@ -1,8 +1,8 @@
// src/main/ipc/window-handlers.ts
import { ipcMain, shell } from 'electron'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { ipcSuccess } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess } from '@d3ro/core/errors'
import { getMainWindow, hideResultPopup, hideRecordingTip } from '../windows/WindowManager'
export function registerWindowHandlers(): void {

View file

@ -10,9 +10,9 @@ import type { Readable } from 'stream'
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { getSoxPath } from '../utils/paths'
import type { AudioDevice } from '@shared/types'
import { AUDIO_FORMAT, TIMING } from '@shared/constants'
import { D3ROError, ErrorCode } from '@shared/errors'
import type { AudioDevice } from '@d3ro/core/types'
import { AUDIO_FORMAT, TIMING } from '@d3ro/core/constants'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
const logger = getLogger('AudioCaptureService')

View file

@ -15,14 +15,14 @@ import {
hideCaptionOverlay,
sendToCaptionOverlay,
} from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
CaptionState,
CaptionSegment,
CaptionConfig,
CaptionSessionSummary,
} from '@shared/types'
} from '@d3ro/core/types'
import { getMainWindow } from '../windows/WindowManager'
const logger = getLogger('CaptionService')
@ -139,7 +139,7 @@ class CaptionService extends EventEmitter {
this._totalFrameCount = 0
// ConfigService에서 저장된 오디오 소스 읽기
const savedSource = configGet('captionAudioSource' as keyof import('@shared/types').AppConfig) as unknown as string
const savedSource = configGet('captionAudioSource' as keyof import('@d3ro/core/types').AppConfig) as unknown as string
if (savedSource && (savedSource === 'mic' || savedSource === 'system' || savedSource === 'both')) {
this._config.audioSource = savedSource as 'mic' | 'system' | 'both'
}

View file

@ -8,8 +8,8 @@ import { configGet } from './ConfigService'
import { getCustomInstructionService } from './CustomInstructionService'
import { getLocalLLMService } from './LocalLLMService'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
LLMChain,
ChainStep,
@ -17,7 +17,7 @@ import type {
UpdateChainParams,
ChainProgress,
ChainExecutionResult
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('chain-service')

View file

@ -2,7 +2,7 @@
// electron-store 기반 설정 관리. 설계서 02의 AppConfig 타입 사용.
import { EventEmitter } from 'events'
import type { AppConfig, ConfigChangedEvent } from '@shared/types'
import type { AppConfig, ConfigChangedEvent } from '@d3ro/core/types'
import { getLogger } from './LoggerService'
const logger = getLogger('ConfigService')

View file

@ -7,8 +7,8 @@ import { nanoid } from 'nanoid'
import Store from 'electron-store'
import { getLogger } from './LoggerService'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
DictationTemplate,
TemplateField,
@ -18,7 +18,7 @@ import type {
TemplateSessionCompletedEvent,
CreateTemplateParams,
UpdateTemplateParams,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('DictationTemplateService')

View file

@ -14,7 +14,7 @@ import type {
DictionaryAddParams,
DictionaryUpdateParams,
DictionarySearchParams
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('DictionaryService')

View file

@ -13,15 +13,15 @@ import { getHistoryService } from './HistoryService'
import { configGet } from './ConfigService'
import { getFfmpegPath } from '../utils/paths'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
FileTranscriptionState,
FileTranscriptionProgress,
FileTranscriptionResult,
FileTranscriptionSegment,
FileTranscriptionStateInfo,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('FileTranscriptionService')
@ -75,7 +75,7 @@ class FileTranscriptionService extends EventEmitter {
// 라이센스 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.FILE_TRANSCRIPTION)
if (!access.allowed) {

View file

@ -13,7 +13,7 @@ import type {
HistoryPage,
HistorySearchParams,
StatsSummary
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('HistoryService')

View file

@ -7,9 +7,9 @@ import { uIOhook, UiohookKey } from 'uiohook-napi'
import type { UiohookKeyboardEvent } from 'uiohook-napi'
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { D3ROError, ErrorCode } from '@shared/errors'
import { TIMING } from '@shared/constants'
import type { HotkeyBinding } from '@shared/types'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import { TIMING } from '@d3ro/core/constants'
import type { HotkeyBinding } from '@d3ro/core/types'
const logger = getLogger('HotkeyService')

View file

@ -8,7 +8,7 @@ import { eq, and } from 'drizzle-orm'
import { getLogger } from './LoggerService'
import { getDatabase } from '../db'
import { dailyUsage } from '../db/schema'
import { D3ROError, ErrorCode } from '@shared/errors'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
LicenseTier,
LicenseInfo,
@ -17,8 +17,8 @@ import type {
UpgradePromptEvent,
ActivateLicenseResult,
TierComparison,
} from '@shared/types'
import { Feature } from '@shared/types'
} from '@d3ro/core/types'
import { Feature } from '@d3ro/core/types'
const logger = getLogger('license')

View file

@ -8,8 +8,8 @@ import * as fs from 'fs'
import * as path from 'path'
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { D3ROError, ErrorCode } from '@shared/errors'
import type { LLMStatus, LLMModel, LLMAction, LLMConnectionState } from '@shared/types'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type { LLMStatus, LLMModel, LLMAction, LLMConnectionState } from '@d3ro/core/types'
const logger = getLogger('LocalLLMService')
@ -410,7 +410,7 @@ class LocalLLMService extends EventEmitter {
// Phase 11: LLM 처리 쿼터 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.LLM_PROCESS)
if (!access.allowed) {

View file

@ -8,8 +8,8 @@ import { type ChildProcess, spawn } from 'child_process'
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { getSidecarCommand } from '../utils/paths'
import { D3ROError, ErrorCode } from '@shared/errors'
import type { STTModel, STTStatus, STTEngineState } from '@shared/types'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type { STTModel, STTStatus, STTEngineState } from '@d3ro/core/types'
// ── 내부 타입 정의 ────────────────────────────────────────

View file

@ -5,13 +5,13 @@ import { EventEmitter } from 'events'
import { nanoid } from 'nanoid'
import Store from 'electron-store'
import { getLogger } from './LoggerService'
import { D3ROError, ErrorCode } from '@shared/errors'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
MeetingDocTemplate,
CreateMeetingDocTemplateParams,
UpdateMeetingDocTemplateParams,
MeetingDocTemplateType,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('MeetingDocTemplateService')

View file

@ -12,8 +12,8 @@ import { configGet, configSet } from './ConfigService'
import { getMainWindow } from '../windows/WindowManager'
import { getDatabase } from '../db'
import { meetingSessions, meetingMemos, meetingDocuments } from '../db/schema'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import {
parseMinutes,
buildExportMarkdown,
@ -21,8 +21,8 @@ import {
formatTime,
formatDateFile,
formatDateRange,
} from '../utils/meeting-markdown'
import { markdownToDocx } from '../utils/markdown-to-docx'
} from '@d3ro/core/utils/meeting-markdown'
import { markdownToDocx } from '@d3ro/core/utils/markdown-to-docx'
import type {
MeetingModeState,
MeetingMemo,
@ -39,7 +39,7 @@ import type {
MeetingExportFormat,
MeetingDocGeneratingProgress,
CaptionSegment,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('MeetingModeService')
@ -145,7 +145,7 @@ class MeetingModeService extends EventEmitter {
// 회의 모드는 마이크 캡처 강제 — ConfigService 값도 임시 변경
// (CaptionService.start()가 ConfigService에서 다시 읽기 때문)
type AppConfigKey = keyof import('@shared/types').AppConfig
type AppConfigKey = keyof import('@d3ro/core/types').AppConfig
const prevAudioSource = configGet('captionAudioSource' as AppConfigKey) as unknown as string
configSet('captionAudioSource' as AppConfigKey, 'mic' as never)

View file

@ -12,9 +12,9 @@ import { getLocalLLMService } from './LocalLLMService'
import { getDatabase } from '../db'
import { history } from '../db/schema'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import type { MeetingSummaryResult, MeetingSummaryProgress } from '@shared/types'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type { MeetingSummaryResult, MeetingSummaryProgress } from '@d3ro/core/types'
const logger = getLogger('MeetingSummaryService')
@ -41,7 +41,7 @@ class MeetingSummaryService extends EventEmitter {
// 라이센스 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.MEETING_SUMMARY)
if (!access.allowed) {

View file

@ -10,7 +10,7 @@ import { getDatabase } from '../db'
import { memoTags, history } from '../db/schema'
import type { MemoTagRow } from '../db/schema'
import { getLogger } from './LoggerService'
import { D3ROError, ErrorCode } from '@shared/errors'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
MemoTag,
TagCount,
@ -18,7 +18,7 @@ import type {
ExportMemoParams,
HistoryEntry,
HistoryPage
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('MemoService')

View file

@ -13,15 +13,15 @@ import { configGet } from './ConfigService'
import { getDatabase } from '../db'
import { ragDocuments, ragChunks } from '../db/schema'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
RAGDocument,
RAGQueryResult,
RAGState,
RAGStateInfo,
RAGIndexProgress,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('RAGService')
@ -74,7 +74,7 @@ class RAGService extends EventEmitter {
// 라이센스 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.LOCAL_RAG)
if (!access.allowed) {

View file

@ -5,8 +5,8 @@
import { clipboard } from 'electron'
import { getLogger } from './LoggerService'
import { configGet, configSet } from './ConfigService'
import { D3ROError, ErrorCode } from '@shared/errors'
import type { ScreenContext, CaptureContextResult } from '@shared/types'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type { ScreenContext, CaptureContextResult } from '@d3ro/core/types'
const logger = getLogger('screen-context')

View file

@ -7,7 +7,7 @@ import { EventEmitter } from 'events'
import { spawn, type ChildProcess } from 'child_process'
import { getLogger } from './LoggerService'
import { configGet } from './ConfigService'
import { D3ROError, ErrorCode } from '@shared/errors'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
const logger = getLogger('TTSPlaybackService')

View file

@ -6,7 +6,7 @@
import { EventEmitter } from 'events'
import { clipboard } from 'electron'
import { getLogger } from './LoggerService'
import { D3ROError, ErrorCode } from '@shared/errors'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
const logger = getLogger('TextInsertService')

View file

@ -10,8 +10,8 @@ import { getLogger } from './LoggerService'
import { getLocalLLMService } from './LocalLLMService'
import { configGet } from './ConfigService'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
VoiceActionPlan,
VoiceActionPreset,
@ -19,7 +19,7 @@ import type {
VoiceActionPlannedEvent,
VoiceActionExecutedEvent,
VoiceActionErrorEvent,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('VoiceActionService')
@ -112,7 +112,7 @@ class VoiceActionService extends EventEmitter {
// 라이센스 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.OS_AUTOMATION)
if (!access.allowed) {

View file

@ -10,7 +10,7 @@ import type {
VoiceCommandKeyword,
VoiceCommandMatch,
KeywordMatchMode
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('voice-command')

View file

@ -11,8 +11,8 @@ import { getAudioCaptureService } from './AudioCaptureService'
import { getTTSPlaybackService } from './TTSPlaybackService'
import { configGet } from './ConfigService'
import { getMainWindow } from '../windows/WindowManager'
import { IPC_CHANNELS } from '@shared/ipc-channels'
import { D3ROError, ErrorCode } from '@shared/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type {
ConversationState,
ConversationMessage,
@ -20,7 +20,7 @@ import type {
ConversationAssistantDelta,
ConversationAssistantMessage,
ConversationError,
} from '@shared/types'
} from '@d3ro/core/types'
const logger = getLogger('VoiceConversationService')
@ -67,7 +67,7 @@ class VoiceConversationService extends EventEmitter {
// 라이센스 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.VOICE_CONVERSATION)
if (!access.allowed) {

View file

@ -17,10 +17,10 @@ import type { HotkeyConfig } from './HotkeyService'
import { configGet } from './ConfigService'
import { getTextInsertService } from './TextInsertService'
import { getLocalLLMService } from './LocalLLMService'
import { D3ROError, ErrorCode } from '@shared/errors'
import { TIMING } from '@shared/constants'
import { RecognitionState, AudioState } from '@shared/types'
import type { VoiceMode, VoiceState } from '@shared/types'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import { TIMING } from '@d3ro/core/constants'
import { RecognitionState, AudioState } from '@d3ro/core/types'
import type { VoiceMode, VoiceState } from '@d3ro/core/types'
import {
showRecordingTip,
hideRecordingTip,
@ -28,7 +28,7 @@ import {
sendAudioLevelToTip,
showResultPopup,
} from '../windows/WindowManager'
import type { ScreenContext } from '@shared/types'
import type { ScreenContext } from '@d3ro/core/types'
const logger = getLogger('VoiceModeService')
@ -189,7 +189,7 @@ class VoiceModeService extends EventEmitter {
// Phase 11: 라이센스 쿼터 체크
try {
const { getLicenseService } = await import('./LicenseService')
const { Feature } = await import('@shared/types')
const { Feature } = await import('@d3ro/core/types')
const license = getLicenseService()
const access = license.canUse(Feature.DICTATION)
if (!access.allowed) {
@ -220,7 +220,7 @@ class VoiceModeService extends EventEmitter {
const { getScreenContextService } = await import('./ScreenContextService')
const ctx = getScreenContextService()
if (ctx.isEnabled()) {
const captureSelected = configGet('screenContextEnabled' as keyof import('@shared/types').AppConfig) as unknown as boolean
const captureSelected = configGet('screenContextEnabled' as keyof import('@d3ro/core/types').AppConfig) as unknown as boolean
const result = await ctx.captureContext(captureSelected)
screenContext = result.context
logger.info(`Screen context captured: ${screenContext.appName ?? 'unknown'}`)
@ -555,7 +555,7 @@ class VoiceModeService extends EventEmitter {
if (action === 'chain') {
try {
const { getChainService } = await import('./ChainService')
const activeChainId = configGet('activeChainId' as keyof import('@shared/types').AppConfig) as unknown as string
const activeChainId = configGet('activeChainId' as keyof import('@d3ro/core/types').AppConfig) as unknown as string
if (activeChainId) {
const chainResult = await getChainService().execute(activeChainId, contextPrefix + transcribedText)
if (this._isInTerminalState()) return
@ -572,7 +572,7 @@ class VoiceModeService extends EventEmitter {
// 음성 단축키 오버라이드 또는 활성 명령어
const effectiveInstructionId = overrideInstructionId
?? (configGet('activeInstructionId' as keyof import('@shared/types').AppConfig) as unknown as string)
?? (configGet('activeInstructionId' as keyof import('@d3ro/core/types').AppConfig) as unknown as string)
if (action === 'custom' || overrideInstructionId) {
let customPrompt = contextPrefix + transcribedText

View file

@ -3,9 +3,9 @@
// 서비스에서 기능 사용 전 호출하여 접근 권한 확인 + 쿼터 차단
import { getLicenseService } from '../services/LicenseService'
import { D3ROError, ErrorCode } from '@shared/errors'
import type { FeatureAccess } from '@shared/types'
import { Feature } from '@shared/types'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import type { FeatureAccess } from '@d3ro/core/types'
import { Feature } from '@d3ro/core/types'
/**
* , D3ROError를 throw한다.

View file

@ -1,31 +0,0 @@
// src/main/utils/markdown-to-docx.ts
// Phase 14.5: 마크다운 → DOCX 변환 유틸리티
import { Document, Packer, Paragraph, TextRun, HeadingLevel } from 'docx'
export async function markdownToDocx(markdown: string, title: string): Promise<Buffer> {
const paragraphs: Paragraph[] = []
// 타이틀 추가
paragraphs.push(new Paragraph({ text: title, heading: HeadingLevel.TITLE }))
// 줄 단위 파싱
for (const line of markdown.split('\n')) {
if (line.startsWith('## ')) {
paragraphs.push(new Paragraph({ text: line.slice(3), heading: HeadingLevel.HEADING_2 }))
} else if (line.startsWith('### ')) {
paragraphs.push(new Paragraph({ text: line.slice(4), heading: HeadingLevel.HEADING_3 }))
} else if (line.startsWith('# ')) {
paragraphs.push(new Paragraph({ text: line.slice(2), heading: HeadingLevel.HEADING_1 }))
} else if (line.startsWith('- [ ] ')) {
paragraphs.push(new Paragraph({ text: `\u2610 ${line.slice(6)}`, bullet: { level: 0 } }))
} else if (line.startsWith('- ') || line.startsWith('* ')) {
paragraphs.push(new Paragraph({ text: line.slice(2), bullet: { level: 0 } }))
} else if (line.trim()) {
paragraphs.push(new Paragraph({ children: [new TextRun(line)] }))
}
}
const doc = new Document({ sections: [{ children: paragraphs }] })
return Buffer.from(await Packer.toBuffer(doc))
}

View file

@ -1,116 +0,0 @@
// src/main/utils/meeting-markdown.ts
// Phase 14.5: MeetingModeService에서 추출한 마크다운 유틸리티
import type { MeetingMinutes, MeetingSessionDetail } from '@shared/types'
export function formatTime(ms: number): string {
const totalSeconds = Math.floor(ms / 1000)
const minutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
}
export function formatDateFile(epochMs: number): string {
const d = new Date(epochMs)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}_${String(d.getHours()).padStart(2, '0')}${String(d.getMinutes()).padStart(2, '0')}`
}
export function formatDateRange(startedAt: number, endedAt: number | null): string {
const fmt = (ts: number): string => {
const d = new Date(ts)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
}
if (!endedAt) return fmt(startedAt)
return `${fmt(startedAt)} ~ ${new Date(endedAt).getHours()}:${String(new Date(endedAt).getMinutes()).padStart(2, '0')}`
}
export function parseMinutes(markdown: string): MeetingMinutes {
const minutes: MeetingMinutes = {
summary: '',
decisions: [],
actionItems: [],
timeline: [],
}
const sections = markdown.split(/^## /m)
for (const section of sections) {
const lines = section.trim().split('\n')
const heading = lines[0]?.toLowerCase() ?? ''
const body = lines.slice(1).join('\n').trim()
if (heading.includes('요약') || heading.includes('summary')) {
minutes.summary = body
} else if (heading.includes('결정') || heading.includes('decision')) {
minutes.decisions = body
.split('\n')
.filter((l) => l.startsWith('-') || l.startsWith('*'))
.map((l) => l.replace(/^[-*]\s*/, '').trim())
} else if (heading.includes('할 일') || heading.includes('action')) {
minutes.actionItems = body
.split('\n')
.filter((l) => l.startsWith('-') || l.startsWith('*'))
.map((l) => {
const text = l.replace(/^[-*]\s*(\[.\]\s*)?/, '').trim()
return { task: text }
})
} else if (heading.includes('타임라인') || heading.includes('timeline')) {
const tableRows = body
.split('\n')
.filter((l) => l.includes('|') && !l.includes('---'))
for (const row of tableRows) {
const cells = row.split('|').map((c) => c.trim()).filter(Boolean)
if (cells.length >= 2) {
minutes.timeline.push({
time: cells[0],
content: cells[1],
type: cells[1].includes('📝') ? 'memo' : 'transcript',
})
}
}
}
}
return minutes
}
export function buildExportMarkdown(session: MeetingSessionDetail): string {
const durationMin = session.durationMs ? Math.round(session.durationMs / 60000) : 0
const memoTable = session.memos.length > 0
? `## 참석자 메모\n\n| 시간 | 메모 |\n|------|------|\n${session.memos.map((m) => `| ${formatTime(m.timestampMs)} | ${m.content} |`).join('\n')}`
: ''
return `# 회의록 — ${session.title ?? '무제 회의'}
- ****: ${formatDateRange(session.startedAt, session.endedAt)}
- ** **: ${durationMin}
- **STT **: ${session.sttModel ?? '-'}
- **LLM **: ${session.llmModel ?? '-'}
---
${session.minutesMarkdown ?? '회의록이 생성되지 않았습니다.'}
---
${memoTable}
---
##
${session.rawTranscript ?? '(없음)'}
`
}
/** 간단한 마크다운→HTML 변환 (PDF 생성용) */
export function markdownToSimpleHtml(md: string): string {
return md
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/^\- \[.\] (.+)$/gm, '<li>$1</li>')
.replace(/^\- (.+)$/gm, '<li>$1</li>')
.replace(/^\* (.+)$/gm, '<li>$1</li>')
.replace(/\n{2,}/g, '</p><p>')
.replace(/^(?!<[h|l|t|p])/gm, '')
}

View file

@ -4,7 +4,7 @@
import { BrowserWindow, shell, screen, ipcMain, Menu } from 'electron'
import { join } from 'path'
import { is } from '@electron-toolkit/utils'
import { WINDOW_SIZE } from '@shared/constants'
import { WINDOW_SIZE } from '@d3ro/core/constants'
import { getLogger } from '../services/LoggerService'
import { getIsQuitting } from '../lifecycle'
import { configGet } from '../services/ConfigService'