eprec 1.4.0 → 1.6.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 (31) hide show
  1. package/README.md +1 -1
  2. package/app/assets/styles.css +319 -182
  3. package/app/components/style-system-sample.tsx +197 -0
  4. package/app/styles/tokens.ts +99 -0
  5. package/package.json +8 -11
  6. package/process-course/chapter-processor.ts +6 -3
  7. package/process-course/cli.ts +1 -1
  8. package/process-course/edits/cli.ts +13 -5
  9. package/process-course/edits/combined-video-editor.ts +2 -2
  10. package/process-course/edits/edit-workspace.ts +1 -1
  11. package/process-course/edits/regenerate-transcript.ts +2 -2
  12. package/process-course/edits/remove-ranges.ts +1 -1
  13. package/process-course/edits/timestamp-refinement.ts +1 -1
  14. package/process-course/edits/transcript-output.test.ts +1 -1
  15. package/process-course/edits/transcript-output.ts +1 -1
  16. package/process-course/ffmpeg.ts +1 -1
  17. package/process-course/jarvis-commands/parser.test.ts +1 -1
  18. package/process-course/jarvis-commands/parser.ts +1 -1
  19. package/process-course/jarvis-commands/windows.ts +3 -3
  20. package/process-course/logging.ts +1 -1
  21. package/process-course/summary.ts +1 -1
  22. package/process-course/utils/filename.ts +1 -1
  23. package/process-course/utils/transcript.test.ts +1 -1
  24. package/process-course/utils/transcript.ts +1 -1
  25. package/{app-server.ts → src/app-server.ts} +13 -9
  26. package/{cli.ts → src/cli.ts} +30 -14
  27. package/{process-course-video.ts → src/process-course-video.ts} +9 -9
  28. package/{speech-detection.ts → src/speech-detection.ts} +4 -4
  29. package/src/utils.test.ts +71 -0
  30. /package/{utils.ts → src/utils.ts} +0 -0
  31. /package/{whispercpp-transcribe.ts → src/whispercpp-transcribe.ts} +0 -0
@@ -0,0 +1,197 @@
1
+ import type { Handle } from 'remix/component'
2
+ import {
3
+ colors,
4
+ mq,
5
+ radius,
6
+ responsive,
7
+ shadows,
8
+ spacing,
9
+ transitions,
10
+ typography,
11
+ } from '../styles/tokens.ts'
12
+
13
+ const sectionStyle = {
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ gap: spacing.lg,
17
+ marginTop: responsive.spacingSection,
18
+ }
19
+
20
+ const headerStyle = {
21
+ display: 'flex',
22
+ alignItems: 'center',
23
+ justifyContent: 'space-between',
24
+ gap: spacing.md,
25
+ flexWrap: 'wrap',
26
+ }
27
+
28
+ const gridStyle = {
29
+ display: 'grid',
30
+ gap: spacing.lg,
31
+ gridTemplateColumns: `repeat(auto-fit, minmax(${responsive.cardMinWidth}, 1fr))`,
32
+ [mq.mobile]: {
33
+ gridTemplateColumns: '1fr',
34
+ },
35
+ }
36
+
37
+ const cardStyle = {
38
+ padding: spacing.xl,
39
+ backgroundColor: colors.surface,
40
+ borderRadius: radius.lg,
41
+ border: `1px solid ${colors.border}`,
42
+ boxShadow: shadows.sm,
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ gap: spacing.md,
46
+ transition: `box-shadow ${transitions.fast}, transform ${transitions.fast}`,
47
+ '&:hover': {
48
+ boxShadow: shadows.md,
49
+ transform: 'translateY(-1px)',
50
+ },
51
+ [mq.mobile]: {
52
+ padding: spacing.lg,
53
+ },
54
+ }
55
+
56
+ const primaryButtonStyle = {
57
+ display: 'inline-flex',
58
+ alignItems: 'center',
59
+ justifyContent: 'center',
60
+ gap: spacing.sm,
61
+ padding: `${spacing.sm} ${spacing.lg}`,
62
+ borderRadius: radius.md,
63
+ border: `1px solid ${colors.primaryActive}`,
64
+ backgroundColor: colors.primary,
65
+ color: colors.onPrimary,
66
+ fontSize: typography.fontSize.base,
67
+ fontWeight: typography.fontWeight.semibold,
68
+ cursor: 'pointer',
69
+ transition: `background-color ${transitions.fast}, box-shadow ${transitions.fast}, transform ${transitions.fast}`,
70
+ '&:hover': {
71
+ backgroundColor: colors.primaryHover,
72
+ boxShadow: shadows.sm,
73
+ },
74
+ '&:active': {
75
+ backgroundColor: colors.primaryActive,
76
+ transform: 'translateY(1px)',
77
+ },
78
+ }
79
+
80
+ const pillStyle = {
81
+ padding: `${spacing.xs} ${spacing.sm}`,
82
+ borderRadius: radius.pill,
83
+ backgroundColor: colors.infoSurface,
84
+ color: colors.infoText,
85
+ fontSize: typography.fontSize.xs,
86
+ fontWeight: typography.fontWeight.semibold,
87
+ }
88
+
89
+ const swatchStyle = (color: string) => ({
90
+ width: spacing.sm,
91
+ height: spacing.sm,
92
+ borderRadius: radius.xl,
93
+ backgroundColor: color,
94
+ boxShadow: `0 0 0 1px ${colors.border}`,
95
+ })
96
+
97
+ export function StyleSystemSample(handle: Handle) {
98
+ return () => (
99
+ <section css={sectionStyle}>
100
+ <header css={headerStyle}>
101
+ <div>
102
+ <h2
103
+ css={{
104
+ margin: 0,
105
+ fontSize: typography.fontSize.xl,
106
+ fontWeight: typography.fontWeight.semibold,
107
+ color: colors.text,
108
+ }}
109
+ >
110
+ Design tokens
111
+ </h2>
112
+ <p
113
+ css={{
114
+ margin: 0,
115
+ color: colors.textMuted,
116
+ fontSize: typography.fontSize.base,
117
+ lineHeight: 1.6,
118
+ }}
119
+ >
120
+ Shared CSS variables and TypeScript helpers for consistent theming.
121
+ </p>
122
+ </div>
123
+ <span css={pillStyle}>Auto dark mode</span>
124
+ </header>
125
+
126
+ <div css={gridStyle}>
127
+ <div css={cardStyle}>
128
+ <h3
129
+ css={{
130
+ margin: 0,
131
+ fontSize: typography.fontSize.lg,
132
+ fontWeight: typography.fontWeight.semibold,
133
+ color: colors.text,
134
+ }}
135
+ >
136
+ Surface card
137
+ </h3>
138
+ <p
139
+ css={{
140
+ margin: 0,
141
+ color: colors.textMuted,
142
+ fontSize: typography.fontSize.base,
143
+ lineHeight: 1.5,
144
+ }}
145
+ >
146
+ Spacing, radius, and shadows come from tokens with responsive
147
+ overrides.
148
+ </p>
149
+ <button type="button" css={primaryButtonStyle}>
150
+ Primary action
151
+ </button>
152
+ </div>
153
+
154
+ <div css={cardStyle}>
155
+ <h3
156
+ css={{
157
+ margin: 0,
158
+ fontSize: typography.fontSize.lg,
159
+ fontWeight: typography.fontWeight.semibold,
160
+ color: colors.text,
161
+ }}
162
+ >
163
+ Semantic palette
164
+ </h3>
165
+ <p
166
+ css={{
167
+ margin: 0,
168
+ color: colors.textMuted,
169
+ fontSize: typography.fontSize.base,
170
+ lineHeight: 1.5,
171
+ }}
172
+ >
173
+ Use semantic names like primary, surface, and text instead of hex
174
+ values.
175
+ </p>
176
+ <div
177
+ css={{
178
+ display: 'flex',
179
+ gap: spacing.sm,
180
+ alignItems: 'center',
181
+ flexWrap: 'wrap',
182
+ color: colors.textSecondary,
183
+ fontSize: typography.fontSize.sm,
184
+ }}
185
+ >
186
+ <span css={swatchStyle(colors.primary)} />
187
+ <span css={swatchStyle(colors.infoSurface)} />
188
+ <span css={swatchStyle(colors.successSurface)} />
189
+ <span css={swatchStyle(colors.warningSurface)} />
190
+ <span css={swatchStyle(colors.dangerSurface)} />
191
+ <span>Primary, info, success, warning, danger</span>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </section>
196
+ )
197
+ }
@@ -0,0 +1,99 @@
1
+ export const colors = {
2
+ primary: 'var(--color-primary)',
3
+ primaryHover: 'var(--color-primary-hover)',
4
+ primaryActive: 'var(--color-primary-active)',
5
+ onPrimary: 'var(--color-on-primary)',
6
+ background: 'var(--color-background)',
7
+ surface: 'var(--color-surface)',
8
+ surfaceMuted: 'var(--color-surface-muted)',
9
+ surfaceInverse: 'var(--color-surface-inverse)',
10
+ text: 'var(--color-text)',
11
+ textMuted: 'var(--color-text-muted)',
12
+ textSubtle: 'var(--color-text-subtle)',
13
+ textSecondary: 'var(--color-text-secondary)',
14
+ textFaint: 'var(--color-text-faint)',
15
+ textInverse: 'var(--color-text-inverse)',
16
+ border: 'var(--color-border)',
17
+ borderStrong: 'var(--color-border-strong)',
18
+ borderAccent: 'var(--color-border-accent)',
19
+ infoSurface: 'var(--color-info-surface)',
20
+ infoText: 'var(--color-info-text)',
21
+ successSurface: 'var(--color-success-surface)',
22
+ successText: 'var(--color-success-text)',
23
+ warningSurface: 'var(--color-warning-surface)',
24
+ warningText: 'var(--color-warning-text)',
25
+ warningBorder: 'var(--color-warning-border)',
26
+ dangerSurface: 'var(--color-danger-surface)',
27
+ dangerText: 'var(--color-danger-text)',
28
+ dangerBorder: 'var(--color-danger-border)',
29
+ dangerBorderStrong: 'var(--color-danger-border-strong)',
30
+ primarySoft: 'color-mix(in srgb, var(--color-primary) 12%, transparent)',
31
+ primaryMuted: 'color-mix(in srgb, var(--color-primary) 24%, transparent)',
32
+ borderSubtle: 'color-mix(in srgb, var(--color-border) 60%, transparent)',
33
+ } as const
34
+
35
+ export const typography = {
36
+ fontFamily: 'var(--font-family)',
37
+ fontSize: {
38
+ xs: 'var(--font-size-xs)',
39
+ sm: 'var(--font-size-sm)',
40
+ base: 'var(--font-size-base)',
41
+ lg: 'var(--font-size-lg)',
42
+ xl: 'var(--font-size-xl)',
43
+ '2xl': 'var(--font-size-2xl)',
44
+ },
45
+ fontWeight: {
46
+ normal: 'var(--font-weight-normal)',
47
+ medium: 'var(--font-weight-medium)',
48
+ semibold: 'var(--font-weight-semibold)',
49
+ bold: 'var(--font-weight-bold)',
50
+ },
51
+ } as const
52
+
53
+ export const spacing = {
54
+ xs: 'var(--spacing-xs)',
55
+ sm: 'var(--spacing-sm)',
56
+ md: 'var(--spacing-md)',
57
+ lg: 'var(--spacing-lg)',
58
+ xl: 'var(--spacing-xl)',
59
+ '2xl': 'var(--spacing-2xl)',
60
+ '3xl': 'var(--spacing-3xl)',
61
+ '4xl': 'var(--spacing-4xl)',
62
+ '5xl': 'var(--spacing-5xl)',
63
+ } as const
64
+
65
+ export const radius = {
66
+ sm: 'var(--radius-sm)',
67
+ md: 'var(--radius-md)',
68
+ lg: 'var(--radius-lg)',
69
+ xl: 'var(--radius-xl)',
70
+ pill: 'var(--radius-pill)',
71
+ } as const
72
+
73
+ export const shadows = {
74
+ sm: 'var(--shadow-sm)',
75
+ md: 'var(--shadow-md)',
76
+ lg: 'var(--shadow-lg)',
77
+ } as const
78
+
79
+ export const transitions = {
80
+ fast: 'var(--transition-fast)',
81
+ normal: 'var(--transition-normal)',
82
+ } as const
83
+
84
+ export const responsive = {
85
+ spacingPage: 'var(--spacing-page)',
86
+ spacingSection: 'var(--spacing-section)',
87
+ cardMinWidth: 'var(--card-min-width)',
88
+ } as const
89
+
90
+ export const breakpoints = {
91
+ mobile: '640px',
92
+ tablet: '1024px',
93
+ } as const
94
+
95
+ export const mq = {
96
+ mobile: `@media (max-width: ${breakpoints.mobile})`,
97
+ tablet: `@media (max-width: ${breakpoints.tablet})`,
98
+ desktop: `@media (min-width: ${parseInt(breakpoints.tablet) + 1}px)`,
99
+ } as const
package/package.json CHANGED
@@ -1,35 +1,30 @@
1
1
  {
2
2
  "name": "eprec",
3
3
  "type": "module",
4
- "version": "1.4.0",
4
+ "version": "1.6.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/epicweb-dev/eprec"
9
9
  },
10
10
  "scripts": {
11
- "app:start": "bun --watch ./cli.ts app start",
11
+ "app:start": "bun --watch ./src/cli.ts app start",
12
12
  "format": "prettier --write .",
13
- "test": "bun test process-course utils.test.ts",
13
+ "test": "bun test process-course src/utils.test.ts",
14
14
  "test:e2e": "bun test ./e2e",
15
- "test:smoke": "bunx playwright test -c playwright-smoke-config.ts",
15
+ "test:smoke": "bunx playwright test -c playwright/playwright-smoke-config.ts",
16
16
  "test:all": "bun test '**/*.test.ts'",
17
17
  "validate": "bun run test"
18
18
  },
19
19
  "bin": {
20
- "eprec": "./cli.ts"
20
+ "eprec": "./src/cli.ts"
21
21
  },
22
22
  "files": [
23
23
  "app/**",
24
- "app-server.ts",
25
- "cli.ts",
26
24
  "process-course/**",
27
- "process-course-video.ts",
28
25
  "public/**",
29
26
  "server/**",
30
- "speech-detection.ts",
31
- "utils.ts",
32
- "whispercpp-transcribe.ts"
27
+ "src/**"
33
28
  ],
34
29
  "prettier": "@epic-web/config/prettier",
35
30
  "devDependencies": {
@@ -43,8 +38,10 @@
43
38
  "typescript": "^5"
44
39
  },
45
40
  "dependencies": {
41
+ "@inquirer/search": "^4.1.0",
46
42
  "get-port": "^7.1.0",
47
43
  "inquirer": "^13.2.1",
44
+ "match-sorter": "^8.2.0",
48
45
  "onnxruntime-node": "^1.23.2",
49
46
  "ora": "^9.1.0",
50
47
  "remix": "3.0.0-alpha.0",
@@ -1,7 +1,10 @@
1
1
  import path from 'node:path'
2
- import { detectSpeechBounds, checkSegmentHasSpeech } from '../speech-detection'
3
- import { transcribeAudio } from '../whispercpp-transcribe'
4
- import { clamp, formatSeconds } from '../utils'
2
+ import {
3
+ detectSpeechBounds,
4
+ checkSegmentHasSpeech,
5
+ } from '../src/speech-detection'
6
+ import { transcribeAudio } from '../src/whispercpp-transcribe'
7
+ import { clamp, formatSeconds } from '../src/utils'
5
8
  import {
6
9
  COMMAND_CLOSE_WORD,
7
10
  COMMAND_WAKE_WORD,
@@ -2,7 +2,7 @@ import path from 'node:path'
2
2
  import yargs from 'yargs/yargs'
3
3
  import { hideBin } from 'yargs/helpers'
4
4
  import type { Argv, Arguments } from 'yargs'
5
- import { getDefaultWhisperModelPath } from '../whispercpp-transcribe'
5
+ import { getDefaultWhisperModelPath } from '../src/whispercpp-transcribe'
6
6
  import { DEFAULT_MIN_CHAPTER_SECONDS, TRANSCRIPTION_PHRASES } from './config'
7
7
  import { normalizeSkipPhrases } from './utils/transcript'
8
8
  import { parseChapterSelection } from './utils/chapter-selection'
@@ -357,17 +357,25 @@ if (import.meta.main) {
357
357
  async function promptForEditsCommand(
358
358
  prompter: Prompter,
359
359
  ): Promise<string[] | null> {
360
- const selection = await prompter.select('Choose a command', [
360
+ const selection = await prompter.search('Choose a command (type to filter)', [
361
361
  {
362
- name: 'Edit a single video using transcript text edits',
362
+ name: 'edit-video - Edit a single video using transcript text edits',
363
363
  value: 'edit-video',
364
+ description: 'edit-video --input <file> --transcript <json> --edited <txt>',
365
+ keywords: ['transcript', 'cuts', 'remove', 'trim'],
364
366
  },
365
367
  {
366
- name: 'Combine two videos with speech-aligned padding',
368
+ name: 'combine-videos - Combine two videos with speech-aligned padding',
367
369
  value: 'combine-videos',
370
+ description: 'combine-videos --video1 <file> --video2 <file>',
371
+ keywords: ['merge', 'join', 'splice', 'padding'],
368
372
  },
369
- { name: 'Show help', value: 'help' },
370
- { name: 'Exit', value: 'exit' },
373
+ {
374
+ name: '--help - Show help',
375
+ value: 'help',
376
+ keywords: ['usage', '--help'],
377
+ },
378
+ { name: 'exit - Exit', value: 'exit', keywords: ['quit', 'cancel'] },
371
379
  ])
372
380
  if (selection === 'exit') {
373
381
  return null
@@ -4,9 +4,9 @@ import { copyFile, mkdir, mkdtemp, rename, rm } from 'node:fs/promises'
4
4
  import {
5
5
  detectSpeechBounds,
6
6
  checkSegmentHasSpeech,
7
- } from '../../speech-detection'
7
+ } from '../../src/speech-detection'
8
8
  import { extractChapterSegmentAccurate, concatSegments } from '../ffmpeg'
9
- import { clamp, getMediaDurationSeconds } from '../../utils'
9
+ import { clamp, getMediaDurationSeconds } from '../../src/utils'
10
10
  import { EDIT_CONFIG } from '../config'
11
11
  import { editVideo } from './video-editor'
12
12
  import {
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path'
2
2
  import { copyFile, mkdir } from 'node:fs/promises'
3
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
3
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
4
4
  import {
5
5
  buildTranscriptWordsWithIndices,
6
6
  generateTranscriptJson,
@@ -5,7 +5,7 @@ import { mkdtemp, readdir, rm } from 'node:fs/promises'
5
5
  import yargs from 'yargs/yargs'
6
6
  import { hideBin } from 'yargs/helpers'
7
7
  import { extractTranscriptionAudio } from '../ffmpeg'
8
- import { transcribeAudio } from '../../whispercpp-transcribe'
8
+ import { transcribeAudio } from '../../src/whispercpp-transcribe'
9
9
  import { scaleTranscriptSegments } from '../jarvis-commands/parser'
10
10
  import { EDIT_CONFIG } from '../config'
11
11
  import {
@@ -13,7 +13,7 @@ import {
13
13
  generateTranscriptJson,
14
14
  generateTranscriptText,
15
15
  } from './transcript-output'
16
- import { getMediaDurationSeconds } from '../../utils'
16
+ import { getMediaDurationSeconds } from '../../src/utils'
17
17
 
18
18
  async function main() {
19
19
  const argv = yargs(hideBin(process.argv))
@@ -6,7 +6,7 @@ import yargs from 'yargs/yargs'
6
6
  import { hideBin } from 'yargs/helpers'
7
7
  import { extractChapterSegmentAccurate, concatSegments } from '../ffmpeg'
8
8
  import { buildKeepRanges, mergeTimeRanges } from '../utils/time-ranges'
9
- import { clamp, getMediaDurationSeconds } from '../../utils'
9
+ import { clamp, getMediaDurationSeconds } from '../../src/utils'
10
10
  import type { TimeRange } from '../types'
11
11
 
12
12
  export type RemoveRangesOptions = {
@@ -1,6 +1,6 @@
1
1
  import { readAudioSamples } from '../ffmpeg'
2
2
  import { CONFIG, EDIT_CONFIG } from '../config'
3
- import { clamp } from '../../utils'
3
+ import { clamp } from '../../src/utils'
4
4
  import { mergeTimeRanges } from '../utils/time-ranges'
5
5
  import { findLowestAmplitudeBoundaryProgressive } from '../utils/audio-analysis'
6
6
  import type { TimeRange } from '../types'
@@ -1,5 +1,5 @@
1
1
  import { test, expect } from 'bun:test'
2
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
2
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
3
3
  import {
4
4
  buildTranscriptWordsWithIndices,
5
5
  generateTranscriptJson,
@@ -1,4 +1,4 @@
1
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
1
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
2
2
  import { buildTranscriptWords } from '../jarvis-commands/parser'
3
3
  import type { TranscriptJson, TranscriptWordWithIndex } from './types'
4
4
 
@@ -2,7 +2,7 @@ import {
2
2
  runCommand as runCommandBase,
3
3
  runCommandBinary as runCommandBinaryBase,
4
4
  formatSeconds,
5
- } from '../utils'
5
+ } from '../src/utils'
6
6
  import { CONFIG, TRANSCRIPTION_SAMPLE_RATE } from './config'
7
7
  import { logCommand, logInfo, logWarn } from './logging'
8
8
  import type { Chapter, LoudnormAnalysis } from './types'
@@ -1,6 +1,6 @@
1
1
  import { test, expect } from 'bun:test'
2
2
  import { scaleTranscriptSegments, extractTranscriptCommands } from './parser'
3
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
3
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
4
4
 
5
5
  // Factory functions for test data
6
6
  function createSegment(
@@ -1,4 +1,4 @@
1
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
1
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
2
2
  import { CONFIG } from '../config'
3
3
  import type { TimeRange } from '../types'
4
4
  import { normalizeWords } from '../utils/transcript'
@@ -1,9 +1,9 @@
1
- import { clamp } from '../../utils'
2
- import { detectSpeechSegmentsWithVad } from '../../speech-detection'
1
+ import { clamp } from '../../src/utils'
2
+ import { detectSpeechSegmentsWithVad } from '../../src/speech-detection'
3
3
  import { readAudioSamples } from '../ffmpeg'
4
4
  import { CONFIG } from '../config'
5
5
  import { logInfo } from '../logging'
6
- import { formatSeconds } from '../../utils'
6
+ import { formatSeconds } from '../../src/utils'
7
7
  import { mergeTimeRanges } from '../utils/time-ranges'
8
8
  import {
9
9
  buildSilenceGapsFromSpeech,
@@ -1,4 +1,4 @@
1
- import { formatCommand } from '../utils'
1
+ import { formatCommand } from '../src/utils'
2
2
  import { buildChapterLogPath } from './paths'
3
3
 
4
4
  type LogHook = () => void
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path'
2
- import { formatSeconds } from '../utils'
2
+ import { formatSeconds } from '../src/utils'
3
3
  import {
4
4
  buildSummaryLogPath,
5
5
  buildJarvisWarningLogPath,
@@ -1,5 +1,5 @@
1
1
  import type { Chapter } from '../types'
2
- import { normalizeFilename, toKebabCase } from '../../utils'
2
+ import { normalizeFilename, toKebabCase } from '../../src/utils'
3
3
 
4
4
  /**
5
5
  * Format a chapter into a filename-safe string.
@@ -7,7 +7,7 @@ import {
7
7
  normalizeWords,
8
8
  } from './transcript'
9
9
  import { TRANSCRIPTION_PHRASES } from '../config'
10
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
10
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
11
11
 
12
12
  function createPhrases(...phrases: string[]): string[] {
13
13
  return phrases
@@ -1,4 +1,4 @@
1
- import type { TranscriptSegment } from '../../whispercpp-transcribe'
1
+ import type { TranscriptSegment } from '../../src/whispercpp-transcribe'
2
2
  import type { TimeRange } from '../types'
3
3
  import { TRANSCRIPTION_PHRASES } from '../config'
4
4
  import { buildTranscriptWords } from '../jarvis-commands/parser'
@@ -1,9 +1,10 @@
1
- import './app/config/init-env.ts'
1
+ import path from 'node:path'
2
+ import '../app/config/init-env.ts'
2
3
 
3
4
  import getPort from 'get-port'
4
- import { getEnv } from './app/config/env.ts'
5
- import { createAppRouter } from './app/router.tsx'
6
- import { createBundlingRoutes } from './server/bundling.ts'
5
+ import { getEnv } from '../app/config/env.ts'
6
+ import { createAppRouter } from '../app/router.tsx'
7
+ import { createBundlingRoutes } from '../server/bundling.ts'
7
8
 
8
9
  type AppServerOptions = {
9
10
  host?: string
@@ -21,6 +22,7 @@ const SHORTCUT_COLORS: Record<string, string> = {
21
22
  h: '\u001b[35m',
22
23
  }
23
24
  const ANSI_RESET = '\u001b[0m'
25
+ const APP_ROOT = path.resolve(import.meta.dirname, '..')
24
26
 
25
27
  function colorizeShortcut(key: string) {
26
28
  if (!COLOR_ENABLED) {
@@ -145,12 +147,12 @@ function setupShortcutHandling(options: {
145
147
  }
146
148
 
147
149
  function startServer(port: number, hostname: string) {
148
- const router = createAppRouter(import.meta.dirname)
150
+ const router = createAppRouter(APP_ROOT)
149
151
  return Bun.serve({
150
152
  port,
151
153
  hostname,
152
154
  idleTimeout: 30,
153
- routes: createBundlingRoutes(import.meta.dirname),
155
+ routes: createBundlingRoutes(APP_ROOT),
154
156
  async fetch(request) {
155
157
  try {
156
158
  return await router.fetch(request)
@@ -214,13 +216,15 @@ export async function startAppServer(options: AppServerOptions = {}) {
214
216
  console.log(`[app] running at ${url}`)
215
217
  logShortcuts(url)
216
218
 
217
- return {
218
- get server() { return server },
219
+ return {
220
+ get server() {
221
+ return server
222
+ },
219
223
  url,
220
224
  stop: () => {
221
225
  cleanupInput()
222
226
  server.stop()
223
- }
227
+ },
224
228
  }
225
229
  }
226
230