@tienne/gestalt 0.19.1 → 0.19.3

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 (147) hide show
  1. package/dist/package.json +2 -2
  2. package/package.json +2 -2
  3. package/dist/skills/seed/SKILL.md +0 -92
  4. package/dist/src/cli/commands/monitor.d.ts +0 -2
  5. package/dist/src/cli/commands/monitor.d.ts.map +0 -1
  6. package/dist/src/cli/commands/monitor.js +0 -13
  7. package/dist/src/cli/commands/monitor.js.map +0 -1
  8. package/dist/src/cli/commands/seed.d.ts +0 -4
  9. package/dist/src/cli/commands/seed.d.ts.map +0 -1
  10. package/dist/src/cli/commands/seed.js +0 -34
  11. package/dist/src/cli/commands/seed.js.map +0 -1
  12. package/dist/src/interview/ambiguity.d.ts +0 -8
  13. package/dist/src/interview/ambiguity.d.ts.map +0 -1
  14. package/dist/src/interview/ambiguity.js +0 -69
  15. package/dist/src/interview/ambiguity.js.map +0 -1
  16. package/dist/src/mcp/tools/seed-passthrough.d.ts +0 -5
  17. package/dist/src/mcp/tools/seed-passthrough.d.ts.map +0 -1
  18. package/dist/src/mcp/tools/seed-passthrough.js +0 -29
  19. package/dist/src/mcp/tools/seed-passthrough.js.map +0 -1
  20. package/dist/src/mcp/tools/seed.d.ts +0 -5
  21. package/dist/src/mcp/tools/seed.d.ts.map +0 -1
  22. package/dist/src/mcp/tools/seed.js +0 -19
  23. package/dist/src/mcp/tools/seed.js.map +0 -1
  24. package/dist/src/recording/agg-converter.d.ts +0 -25
  25. package/dist/src/recording/agg-converter.d.ts.map +0 -1
  26. package/dist/src/recording/agg-converter.js +0 -80
  27. package/dist/src/recording/agg-converter.js.map +0 -1
  28. package/dist/src/recording/agg-installer.d.ts +0 -6
  29. package/dist/src/recording/agg-installer.d.ts.map +0 -1
  30. package/dist/src/recording/agg-installer.js +0 -50
  31. package/dist/src/recording/agg-installer.js.map +0 -1
  32. package/dist/src/recording/asciinema-installer.d.ts +0 -6
  33. package/dist/src/recording/asciinema-installer.d.ts.map +0 -1
  34. package/dist/src/recording/asciinema-installer.js +0 -50
  35. package/dist/src/recording/asciinema-installer.js.map +0 -1
  36. package/dist/src/recording/asciinema-recorder.d.ts +0 -26
  37. package/dist/src/recording/asciinema-recorder.d.ts.map +0 -1
  38. package/dist/src/recording/asciinema-recorder.js +0 -52
  39. package/dist/src/recording/asciinema-recorder.js.map +0 -1
  40. package/dist/src/recording/cast-generator.d.ts +0 -7
  41. package/dist/src/recording/cast-generator.d.ts.map +0 -1
  42. package/dist/src/recording/cast-generator.js +0 -97
  43. package/dist/src/recording/cast-generator.js.map +0 -1
  44. package/dist/src/recording/filename-generator.d.ts +0 -19
  45. package/dist/src/recording/filename-generator.d.ts.map +0 -1
  46. package/dist/src/recording/filename-generator.js +0 -67
  47. package/dist/src/recording/filename-generator.js.map +0 -1
  48. package/dist/src/recording/gif-generator.d.ts +0 -21
  49. package/dist/src/recording/gif-generator.d.ts.map +0 -1
  50. package/dist/src/recording/gif-generator.js +0 -121
  51. package/dist/src/recording/gif-generator.js.map +0 -1
  52. package/dist/src/recording/recording-dir.d.ts +0 -5
  53. package/dist/src/recording/recording-dir.d.ts.map +0 -1
  54. package/dist/src/recording/recording-dir.js +0 -13
  55. package/dist/src/recording/recording-dir.js.map +0 -1
  56. package/dist/src/recording/recording-orchestrator.d.ts +0 -50
  57. package/dist/src/recording/recording-orchestrator.d.ts.map +0 -1
  58. package/dist/src/recording/recording-orchestrator.js +0 -98
  59. package/dist/src/recording/recording-orchestrator.js.map +0 -1
  60. package/dist/src/recording/resume-detector.d.ts +0 -10
  61. package/dist/src/recording/resume-detector.d.ts.map +0 -1
  62. package/dist/src/recording/resume-detector.js +0 -14
  63. package/dist/src/recording/resume-detector.js.map +0 -1
  64. package/dist/src/recording/segment-merger.d.ts +0 -27
  65. package/dist/src/recording/segment-merger.d.ts.map +0 -1
  66. package/dist/src/recording/segment-merger.js +0 -65
  67. package/dist/src/recording/segment-merger.js.map +0 -1
  68. package/dist/src/recording/terminal-recorder.d.ts +0 -31
  69. package/dist/src/recording/terminal-recorder.d.ts.map +0 -1
  70. package/dist/src/recording/terminal-recorder.js +0 -111
  71. package/dist/src/recording/terminal-recorder.js.map +0 -1
  72. package/dist/src/scripts/postinstall.d.ts +0 -2
  73. package/dist/src/scripts/postinstall.d.ts.map +0 -1
  74. package/dist/src/scripts/postinstall.js +0 -29
  75. package/dist/src/scripts/postinstall.js.map +0 -1
  76. package/dist/src/seed/extractor.d.ts +0 -15
  77. package/dist/src/seed/extractor.d.ts.map +0 -1
  78. package/dist/src/seed/extractor.js +0 -88
  79. package/dist/src/seed/extractor.js.map +0 -1
  80. package/dist/src/seed/generator.d.ts +0 -12
  81. package/dist/src/seed/generator.d.ts.map +0 -1
  82. package/dist/src/seed/generator.js +0 -66
  83. package/dist/src/seed/generator.js.map +0 -1
  84. package/dist/src/seed/passthrough-generator.d.ts +0 -31
  85. package/dist/src/seed/passthrough-generator.d.ts.map +0 -1
  86. package/dist/src/seed/passthrough-generator.js +0 -80
  87. package/dist/src/seed/passthrough-generator.js.map +0 -1
  88. package/dist/src/seed/schema.d.ts +0 -145
  89. package/dist/src/seed/schema.d.ts.map +0 -1
  90. package/dist/src/seed/schema.js +0 -37
  91. package/dist/src/seed/schema.js.map +0 -1
  92. package/dist/src/tui/components/TUIApp.d.ts +0 -20
  93. package/dist/src/tui/components/TUIApp.d.ts.map +0 -1
  94. package/dist/src/tui/components/TUIApp.js +0 -84
  95. package/dist/src/tui/components/TUIApp.js.map +0 -1
  96. package/dist/src/tui/hooks/event-store-reader.d.ts +0 -28
  97. package/dist/src/tui/hooks/event-store-reader.d.ts.map +0 -1
  98. package/dist/src/tui/hooks/event-store-reader.js +0 -141
  99. package/dist/src/tui/hooks/event-store-reader.js.map +0 -1
  100. package/dist/src/tui/hooks/useEventStorePoller.d.ts +0 -12
  101. package/dist/src/tui/hooks/useEventStorePoller.d.ts.map +0 -1
  102. package/dist/src/tui/hooks/useEventStorePoller.js +0 -84
  103. package/dist/src/tui/hooks/useEventStorePoller.js.map +0 -1
  104. package/dist/src/tui/screens/DashboardScreen.d.ts +0 -4
  105. package/dist/src/tui/screens/DashboardScreen.d.ts.map +0 -1
  106. package/dist/src/tui/screens/DashboardScreen.js +0 -132
  107. package/dist/src/tui/screens/DashboardScreen.js.map +0 -1
  108. package/dist/src/tui/screens/DebugScreen.d.ts +0 -4
  109. package/dist/src/tui/screens/DebugScreen.d.ts.map +0 -1
  110. package/dist/src/tui/screens/DebugScreen.js +0 -40
  111. package/dist/src/tui/screens/DebugScreen.js.map +0 -1
  112. package/dist/src/tui/screens/EvolutionScreen.d.ts +0 -4
  113. package/dist/src/tui/screens/EvolutionScreen.d.ts.map +0 -1
  114. package/dist/src/tui/screens/EvolutionScreen.js +0 -136
  115. package/dist/src/tui/screens/EvolutionScreen.js.map +0 -1
  116. package/dist/src/tui/screens/HUDPanel.d.ts +0 -4
  117. package/dist/src/tui/screens/HUDPanel.d.ts.map +0 -1
  118. package/dist/src/tui/screens/HUDPanel.js +0 -13
  119. package/dist/src/tui/screens/HUDPanel.js.map +0 -1
  120. package/dist/src/tui/screens/InterviewScreen.d.ts +0 -4
  121. package/dist/src/tui/screens/InterviewScreen.d.ts.map +0 -1
  122. package/dist/src/tui/screens/InterviewScreen.js +0 -103
  123. package/dist/src/tui/screens/InterviewScreen.js.map +0 -1
  124. package/dist/src/tui/screens/LogScreen.d.ts +0 -4
  125. package/dist/src/tui/screens/LogScreen.d.ts.map +0 -1
  126. package/dist/src/tui/screens/LogScreen.js +0 -83
  127. package/dist/src/tui/screens/LogScreen.js.map +0 -1
  128. package/dist/src/tui/screens/SessionListScreen.d.ts +0 -4
  129. package/dist/src/tui/screens/SessionListScreen.d.ts.map +0 -1
  130. package/dist/src/tui/screens/SessionListScreen.js +0 -71
  131. package/dist/src/tui/screens/SessionListScreen.js.map +0 -1
  132. package/dist/src/tui/screens/SpecViewerScreen.d.ts +0 -4
  133. package/dist/src/tui/screens/SpecViewerScreen.d.ts.map +0 -1
  134. package/dist/src/tui/screens/SpecViewerScreen.js +0 -73
  135. package/dist/src/tui/screens/SpecViewerScreen.js.map +0 -1
  136. package/dist/src/tui/widgets/DriftMeter.d.ts +0 -15
  137. package/dist/src/tui/widgets/DriftMeter.d.ts.map +0 -1
  138. package/dist/src/tui/widgets/DriftMeter.js +0 -27
  139. package/dist/src/tui/widgets/DriftMeter.js.map +0 -1
  140. package/dist/src/tui/widgets/GestaltPrincipleBar.d.ts +0 -9
  141. package/dist/src/tui/widgets/GestaltPrincipleBar.d.ts.map +0 -1
  142. package/dist/src/tui/widgets/GestaltPrincipleBar.js +0 -35
  143. package/dist/src/tui/widgets/GestaltPrincipleBar.js.map +0 -1
  144. package/dist/src/tui/widgets/TaskDAGTree.d.ts +0 -15
  145. package/dist/src/tui/widgets/TaskDAGTree.d.ts.map +0 -1
  146. package/dist/src/tui/widgets/TaskDAGTree.js +0 -54
  147. package/dist/src/tui/widgets/TaskDAGTree.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"asciinema-installer.js","sourceRoot":"","sources":["../../../src/recording/asciinema-installer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,MAAM,OAAO,kBAAkB;IAC7B,WAAW;QACT,IAAI,CAAC;YACH,QAAQ,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO;QAE/B,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAE5D,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACpB,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACjF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACjF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CACb,8DAA8D,EAAE,0FAA0F,CAC3J,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,gHAAgH,CACjH,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IACvD,CAAC;IAEO,mBAAmB;QACzB,IAAI,CAAC;YACH,QAAQ,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,uGAAuG,CACxG,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -1,26 +0,0 @@
1
- /**
2
- * AsciinemaRecorder: self-respawn 패턴으로 asciinema 녹화를 구현한다.
3
- *
4
- * --record 플래그가 있고 GESTALT_RECORDING 환경변수가 없으면,
5
- * 현재 프로세스를 asciinema rec으로 감싸서 재실행한다.
6
- * 재실행된 프로세스는 GESTALT_RECORDING=1로 실행되므로 일반 인터뷰 로직을 수행한다.
7
- */
8
- export declare class AsciinemaRecorder {
9
- /** 이미 asciinema로 감싸진 상태인지 확인 */
10
- static isInsideRecording(): boolean;
11
- /** 현재 녹화 중인 cast 파일 경로 (GESTALT_CAST_PATH 환경변수) */
12
- static getCurrentCastPath(): string | undefined;
13
- /**
14
- * 임시 cast 파일 경로를 생성한다.
15
- * 실제 파일명은 인터뷰 완료 후 topic 기반으로 rename된다.
16
- */
17
- static createTempCastPath(recordingsDir?: string): string;
18
- /**
19
- * asciinema rec으로 현재 프로세스를 재실행한다.
20
- * 이 함수는 return하지 않는다 (spawnSync가 블로킹).
21
- *
22
- * @param castPath - 녹화 결과를 저장할 .cast 파일 경로
23
- */
24
- static respawnWithAsciinema(castPath: string): void;
25
- }
26
- //# sourceMappingURL=asciinema-recorder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asciinema-recorder.d.ts","sourceRoot":"","sources":["../../../src/recording/asciinema-recorder.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B,gCAAgC;IAChC,MAAM,CAAC,iBAAiB,IAAI,OAAO;IAInC,mDAAmD;IACnD,MAAM,CAAC,kBAAkB,IAAI,MAAM,GAAG,SAAS;IAI/C;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,aAAa,SAAwB,GAAG,MAAM;IAKxE;;;;;OAKG;IACH,MAAM,CAAC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAuBpD"}
@@ -1,52 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { mkdirSync } from 'node:fs';
3
- import { dirname } from 'node:path';
4
- import { randomUUID } from 'node:crypto';
5
- /**
6
- * AsciinemaRecorder: self-respawn 패턴으로 asciinema 녹화를 구현한다.
7
- *
8
- * --record 플래그가 있고 GESTALT_RECORDING 환경변수가 없으면,
9
- * 현재 프로세스를 asciinema rec으로 감싸서 재실행한다.
10
- * 재실행된 프로세스는 GESTALT_RECORDING=1로 실행되므로 일반 인터뷰 로직을 수행한다.
11
- */
12
- export class AsciinemaRecorder {
13
- /** 이미 asciinema로 감싸진 상태인지 확인 */
14
- static isInsideRecording() {
15
- return process.env['GESTALT_RECORDING'] === '1';
16
- }
17
- /** 현재 녹화 중인 cast 파일 경로 (GESTALT_CAST_PATH 환경변수) */
18
- static getCurrentCastPath() {
19
- return process.env['GESTALT_CAST_PATH'];
20
- }
21
- /**
22
- * 임시 cast 파일 경로를 생성한다.
23
- * 실제 파일명은 인터뷰 완료 후 topic 기반으로 rename된다.
24
- */
25
- static createTempCastPath(recordingsDir = '.gestalt/recordings') {
26
- mkdirSync(recordingsDir, { recursive: true });
27
- return `${recordingsDir}/tmp-${randomUUID()}.cast`;
28
- }
29
- /**
30
- * asciinema rec으로 현재 프로세스를 재실행한다.
31
- * 이 함수는 return하지 않는다 (spawnSync가 블로킹).
32
- *
33
- * @param castPath - 녹화 결과를 저장할 .cast 파일 경로
34
- */
35
- static respawnWithAsciinema(castPath) {
36
- // 현재 process.argv에서 node 실행파일을 제외한 스크립트 + 인자
37
- const [, ...scriptAndArgs] = process.argv;
38
- // --record, -r 플래그 제거 (재실행 시 무한루프 방지)
39
- const filteredArgs = (scriptAndArgs ?? []).filter((a) => a !== '--record' && a !== '-r');
40
- mkdirSync(dirname(castPath), { recursive: true });
41
- const result = spawnSync('asciinema', ['rec', '--overwrite', castPath, '--', 'node', ...filteredArgs], {
42
- stdio: 'inherit',
43
- env: {
44
- ...process.env,
45
- GESTALT_RECORDING: '1',
46
- GESTALT_CAST_PATH: castPath,
47
- },
48
- });
49
- process.exit(result.status ?? 0);
50
- }
51
- }
52
- //# sourceMappingURL=asciinema-recorder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asciinema-recorder.js","sourceRoot":"","sources":["../../../src/recording/asciinema-recorder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,OAAO,iBAAiB;IAC5B,gCAAgC;IAChC,MAAM,CAAC,iBAAiB;QACtB,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,KAAK,GAAG,CAAC;IAClD,CAAC;IAED,mDAAmD;IACnD,MAAM,CAAC,kBAAkB;QACvB,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,aAAa,GAAG,qBAAqB;QAC7D,SAAS,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,OAAO,GAAG,aAAa,QAAQ,UAAU,EAAE,OAAO,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,oBAAoB,CAAC,QAAgB;QAC1C,6CAA6C;QAC7C,MAAM,CAAC,EAAE,GAAG,aAAa,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1C,sCAAsC;QACtC,MAAM,YAAY,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAEzF,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAElD,MAAM,MAAM,GAAG,SAAS,CACtB,WAAW,EACX,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,EAC/D;YACE,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,iBAAiB,EAAE,GAAG;gBACtB,iBAAiB,EAAE,QAAQ;aAC5B;SACF,CACF,CAAC;QAEF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -1,7 +0,0 @@
1
- import type { InterviewSession } from '../core/types.js';
2
- export declare function slugify(topic: string): string;
3
- export declare function getDateString(date?: Date): string;
4
- export declare class CastGenerator {
5
- generate(session: InterviewSession, outputPath: string): void;
6
- }
7
- //# sourceMappingURL=cast-generator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cast-generator.d.ts","sourceRoot":"","sources":["../../../src/recording/cast-generator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAYzD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ7C;AAED,wBAAgB,aAAa,CAAC,IAAI,OAAa,GAAG,MAAM,CAEvD;AAED,qBAAa,aAAa;IACxB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;CAkF9D"}
@@ -1,97 +0,0 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs';
2
- import { dirname } from 'node:path';
3
- // ANSI color codes
4
- const RESET = '\x1b[0m';
5
- const BOLD = '\x1b[1m';
6
- const CYAN = '\x1b[36m';
7
- const YELLOW = '\x1b[33m';
8
- const GREEN = '\x1b[32m';
9
- const DIM = '\x1b[2m';
10
- export function slugify(topic) {
11
- return (topic
12
- .toLowerCase()
13
- .replace(/[^a-z0-9]+/g, '-')
14
- .replace(/^-+|-+$/g, '')
15
- .slice(0, 50) || 'interview');
16
- }
17
- export function getDateString(date = new Date()) {
18
- return date.toISOString().slice(0, 10).replace(/-/g, '');
19
- }
20
- export class CastGenerator {
21
- generate(session, outputPath) {
22
- const startTs = Math.floor(Date.parse(session.createdAt) / 1000);
23
- const header = {
24
- version: 2,
25
- width: 100,
26
- height: 40,
27
- timestamp: startTs,
28
- title: `Gestalt Interview: ${session.topic}`,
29
- };
30
- const events = [];
31
- let t = 0;
32
- // Banner
33
- events.push([
34
- t,
35
- 'o',
36
- `\r\n${BOLD}${CYAN}╔══════════════════════════════════════════════╗${RESET}\r\n`,
37
- ]);
38
- t += 0.05;
39
- events.push([
40
- t,
41
- 'o',
42
- `${BOLD}${CYAN}║ 🎯 Gestalt Interview ║${RESET}\r\n`,
43
- ]);
44
- t += 0.05;
45
- events.push([
46
- t,
47
- 'o',
48
- `${BOLD}${CYAN}║ ${DIM}${session.topic.slice(0, 44).padEnd(44)}${RESET}${BOLD}${CYAN} ║${RESET}\r\n`,
49
- ]);
50
- t += 0.05;
51
- events.push([
52
- t,
53
- 'o',
54
- `${BOLD}${CYAN}╚══════════════════════════════════════════════╝${RESET}\r\n\r\n`,
55
- ]);
56
- t += 0.5;
57
- // Q&A rounds
58
- for (const round of session.rounds) {
59
- if (!round.userResponse)
60
- continue;
61
- // Question
62
- events.push([
63
- t,
64
- 'o',
65
- `${BOLD}${YELLOW}Q${round.roundNumber} [${round.gestaltFocus}]${RESET}\r\n`,
66
- ]);
67
- t += 0.1;
68
- events.push([t, 'o', `${round.question}\r\n\r\n`]);
69
- t += 1.2;
70
- // Answer
71
- events.push([t, 'o', `${BOLD}${GREEN}❯${RESET} `]);
72
- t += 0.1;
73
- events.push([t, 'o', `${round.userResponse}\r\n\r\n`]);
74
- t += 0.8;
75
- }
76
- // Footer
77
- events.push([
78
- t,
79
- 'o',
80
- `${BOLD}${CYAN}✅ Interview completed — ${session.rounds.length} rounds${RESET}\r\n`,
81
- ]);
82
- t += 0.3;
83
- if (session.resolutionScore) {
84
- events.push([
85
- t,
86
- 'o',
87
- `${DIM}Resolution score: ${session.resolutionScore.overall.toFixed(2)}${RESET}\r\n`,
88
- ]);
89
- }
90
- events.push([t + 0.2, 'o', '\r\n']);
91
- // Write file
92
- mkdirSync(dirname(outputPath), { recursive: true });
93
- const lines = [JSON.stringify(header), ...events.map((e) => JSON.stringify(e))];
94
- writeFileSync(outputPath, lines.join('\n') + '\n', 'utf8');
95
- }
96
- }
97
- //# sourceMappingURL=cast-generator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cast-generator.js","sourceRoot":"","sources":["../../../src/recording/cast-generator.ts"],"names":[],"mappings":"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"}
@@ -1,19 +0,0 @@
1
- import type { LLMAdapter } from '../llm/types.js';
2
- export interface FilenameGeneratorOptions {
3
- outputDir?: string;
4
- }
5
- export declare class FilenameGenerator {
6
- private readonly llm;
7
- private readonly options;
8
- constructor(llm: LLMAdapter, options?: FilenameGeneratorOptions);
9
- /**
10
- * 인터뷰 topic 기반으로 kebab-case 이름을 LLM에게 요청하고
11
- * YYYYMMDD 날짜 접미사를 붙여 GIF 파일명을 생성한다.
12
- */
13
- generate(topic: string, sessionId: string): Promise<string>;
14
- generateCast(topic: string, sessionId: string, outputDir?: string): Promise<string>;
15
- private requestSlugFromLLM;
16
- private fallbackSlug;
17
- private getDateString;
18
- }
19
- //# sourceMappingURL=filename-generator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"filename-generator.d.ts","sourceRoot":"","sources":["../../../src/recording/filename-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,wBAA6B;IAGzD;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ3D,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAQ3E,kBAAkB;IA4BhC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;CAOtB"}
@@ -1,67 +0,0 @@
1
- export class FilenameGenerator {
2
- llm;
3
- options;
4
- constructor(llm, options = {}) {
5
- this.llm = llm;
6
- this.options = options;
7
- }
8
- /**
9
- * 인터뷰 topic 기반으로 kebab-case 이름을 LLM에게 요청하고
10
- * YYYYMMDD 날짜 접미사를 붙여 GIF 파일명을 생성한다.
11
- */
12
- async generate(topic, sessionId) {
13
- const slug = await this.requestSlugFromLLM(topic, sessionId);
14
- const date = this.getDateString();
15
- const filename = `${slug}-${date}.gif`;
16
- const dir = this.options.outputDir ?? '.';
17
- return dir === '.' ? filename : `${dir}/${filename}`;
18
- }
19
- async generateCast(topic, sessionId, outputDir) {
20
- const slug = await this.requestSlugFromLLM(topic, sessionId);
21
- const date = this.getDateString();
22
- const filename = `${slug}-${date}.cast`;
23
- const dir = outputDir ?? this.options.outputDir ?? '.gestalt/recordings';
24
- return `${dir}/${filename}`;
25
- }
26
- async requestSlugFromLLM(topic, sessionId) {
27
- try {
28
- const response = await this.llm.chat({
29
- system: 'You are a file naming assistant. Respond with ONLY a kebab-case slug (2-5 words, lowercase, hyphens only, no spaces, no special chars, no extension).',
30
- messages: [
31
- {
32
- role: 'user',
33
- content: `Generate a descriptive kebab-case filename for a terminal recording of an interview about: "${topic}"\n\nSession: ${sessionId}\n\nRespond with ONLY the kebab-case slug, nothing else.`,
34
- },
35
- ],
36
- maxTokens: 50,
37
- temperature: 0.3,
38
- });
39
- const slug = response.content
40
- .trim()
41
- .toLowerCase()
42
- .replace(/[^a-z0-9-]/g, '-')
43
- .replace(/-+/g, '-')
44
- .replace(/^-|-$/g, '');
45
- return slug || this.fallbackSlug(topic);
46
- }
47
- catch {
48
- return this.fallbackSlug(topic);
49
- }
50
- }
51
- fallbackSlug(topic) {
52
- return (topic
53
- .toLowerCase()
54
- .replace(/[^a-z0-9\s]/g, '')
55
- .trim()
56
- .replace(/\s+/g, '-')
57
- .slice(0, 40) || 'interview');
58
- }
59
- getDateString() {
60
- const now = new Date();
61
- const year = now.getFullYear();
62
- const month = String(now.getMonth() + 1).padStart(2, '0');
63
- const day = String(now.getDate()).padStart(2, '0');
64
- return `${year}${month}${day}`;
65
- }
66
- }
67
- //# sourceMappingURL=filename-generator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"filename-generator.js","sourceRoot":"","sources":["../../../src/recording/filename-generator.ts"],"names":[],"mappings":"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"}
@@ -1,21 +0,0 @@
1
- import type { TerminalFrame, GifOutput } from '../core/types.js';
2
- export interface GifGeneratorOptions {
3
- repeat?: number;
4
- quality?: number;
5
- frameDelay?: number;
6
- }
7
- export declare class GifGenerator {
8
- private readonly repeat;
9
- private readonly quality;
10
- private readonly frameDelay;
11
- constructor(options?: GifGeneratorOptions);
12
- /** .frames NDJSON 파일을 읽어 GIF 파일로 변환 */
13
- generate(framesPath: string, outputPath: string): Promise<GifOutput>;
14
- /** TerminalFrame 배열을 직접 받아 GIF 생성 (SegmentMerger에서 병합된 결과 사용) */
15
- generateFromFrames(frames: TerminalFrame[], outputPath: string): Promise<GifOutput>;
16
- readFrames(framesPath: string): Promise<TerminalFrame[]>;
17
- private encodeGif;
18
- private renderFrame;
19
- private loadTerminalFont;
20
- }
21
- //# sourceMappingURL=gif-generator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gif-generator.d.ts","sourceRoot":"","sources":["../../../src/recording/gif-generator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAWjE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,OAAO,GAAE,mBAAwB;IAM7C,uCAAuC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAa1E,iEAAiE;IAC3D,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAOnF,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;YAqBhD,SAAS;YA6CT,WAAW;YAuBX,gBAAgB;CAS/B"}
@@ -1,121 +0,0 @@
1
- import { createReadStream, createWriteStream, existsSync } from 'node:fs';
2
- import { createInterface } from 'node:readline';
3
- import { createRequire } from 'node:module';
4
- import { dirname, join } from 'node:path';
5
- import { Jimp, loadFont } from 'jimp';
6
- // eslint-disable-next-line @typescript-eslint/no-require-imports
7
- import GIFEncoder from 'gifencoder';
8
- // ANSI escape code 제거 정규식
9
- const ANSI_RE = /\x1B\[[0-9;]*[a-zA-Z]|\x1B\][^\x07]*\x07|\x1B[()][A-Z0-9]|\r/g;
10
- // 터미널 렌더링 설정
11
- const CHAR_WIDTH = 9;
12
- const CHAR_HEIGHT = 18;
13
- const PADDING = 10;
14
- const BG_COLOR = 0x1e1e2eff; // dark background
15
- export class GifGenerator {
16
- repeat;
17
- quality;
18
- frameDelay;
19
- constructor(options = {}) {
20
- this.repeat = options.repeat ?? 0;
21
- this.quality = options.quality ?? 10;
22
- this.frameDelay = options.frameDelay ?? 100;
23
- }
24
- /** .frames NDJSON 파일을 읽어 GIF 파일로 변환 */
25
- async generate(framesPath, outputPath) {
26
- if (!existsSync(framesPath)) {
27
- throw new Error(`Frames file not found: ${framesPath}`);
28
- }
29
- const frames = await this.readFrames(framesPath);
30
- if (frames.length === 0) {
31
- throw new Error('No frames found in recording');
32
- }
33
- return this.encodeGif(frames, outputPath);
34
- }
35
- /** TerminalFrame 배열을 직접 받아 GIF 생성 (SegmentMerger에서 병합된 결과 사용) */
36
- async generateFromFrames(frames, outputPath) {
37
- if (frames.length === 0) {
38
- throw new Error('No frames to encode');
39
- }
40
- return this.encodeGif(frames, outputPath);
41
- }
42
- async readFrames(framesPath) {
43
- const frames = [];
44
- const rl = createInterface({
45
- input: createReadStream(framesPath),
46
- crlfDelay: Infinity,
47
- });
48
- for await (const line of rl) {
49
- const trimmed = line.trim();
50
- if (!trimmed)
51
- continue;
52
- try {
53
- const frame = JSON.parse(trimmed);
54
- frames.push(frame);
55
- }
56
- catch {
57
- // 파싱 실패한 라인 무시
58
- }
59
- }
60
- return frames;
61
- }
62
- async encodeGif(frames, outputPath) {
63
- const font = await this.loadTerminalFont();
64
- const firstFrame = frames[0];
65
- const width = firstFrame.cols * CHAR_WIDTH + PADDING * 2;
66
- const height = firstFrame.rows * CHAR_HEIGHT + PADDING * 2;
67
- const encoder = new GIFEncoder(width, height);
68
- const outputStream = createWriteStream(outputPath);
69
- encoder.createReadStream().pipe(outputStream);
70
- encoder.start();
71
- encoder.setRepeat(this.repeat);
72
- encoder.setDelay(this.frameDelay);
73
- encoder.setQuality(this.quality);
74
- let prevTimestamp = frames[0].timestamp;
75
- for (const frame of frames) {
76
- const delay = Math.min(Math.max(frame.timestamp - prevTimestamp, 50), 3000);
77
- encoder.setDelay(delay || this.frameDelay);
78
- prevTimestamp = frame.timestamp;
79
- const imageData = await this.renderFrame(frame, font, width, height);
80
- encoder.addFrame(imageData);
81
- }
82
- encoder.finish();
83
- await new Promise((resolve, reject) => {
84
- outputStream.on('finish', resolve);
85
- outputStream.on('error', reject);
86
- });
87
- const { statSync } = await import('node:fs');
88
- const stat = statSync(outputPath);
89
- return {
90
- filePath: outputPath,
91
- sizeBytes: stat.size,
92
- frameCount: frames.length,
93
- durationMs: frames[frames.length - 1].timestamp - frames[0].timestamp,
94
- };
95
- }
96
- async renderFrame(frame, font, width, height) {
97
- const image = new Jimp({ width, height, color: BG_COLOR });
98
- const text = stripAnsi(frame.data);
99
- const lines = text.split('\n');
100
- let y = PADDING;
101
- for (const line of lines) {
102
- if (y >= height - PADDING)
103
- break;
104
- if (line.length > 0) {
105
- image.print({ font, x: PADDING, y, text: line });
106
- }
107
- y += CHAR_HEIGHT;
108
- }
109
- return image.bitmap.data;
110
- }
111
- async loadTerminalFont() {
112
- const req = createRequire(import.meta.url);
113
- const jimpPath = req.resolve('jimp');
114
- const fontPath = join(dirname(jimpPath), '../../../@jimp/plugin-print/fonts/open-sans/open-sans-16-white/open-sans-16-white.fnt');
115
- return loadFont(fontPath);
116
- }
117
- }
118
- function stripAnsi(str) {
119
- return str.replace(ANSI_RE, '');
120
- }
121
- //# sourceMappingURL=gif-generator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gif-generator.js","sourceRoot":"","sources":["../../../src/recording/gif-generator.ts"],"names":[],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- export declare const RECORDINGS_BASE_DIR = ".gestalt/recordings";
2
- /** sessionId에 대한 .frames 파일 경로를 반환하고, 부모 디렉토리가 없으면 생성한다 */
3
- export declare function ensureRecordingsDir(): void;
4
- export declare function getFramesPath(sessionId: string): string;
5
- //# sourceMappingURL=recording-dir.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"recording-dir.d.ts","sourceRoot":"","sources":["../../../src/recording/recording-dir.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD,2DAA2D;AAC3D,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -1,13 +0,0 @@
1
- import { mkdirSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- export const RECORDINGS_BASE_DIR = '.gestalt/recordings';
4
- /** sessionId에 대한 .frames 파일 경로를 반환하고, 부모 디렉토리가 없으면 생성한다 */
5
- export function ensureRecordingsDir() {
6
- if (!existsSync(RECORDINGS_BASE_DIR)) {
7
- mkdirSync(RECORDINGS_BASE_DIR, { recursive: true });
8
- }
9
- }
10
- export function getFramesPath(sessionId) {
11
- return join(RECORDINGS_BASE_DIR, `${sessionId}.frames`);
12
- }
13
- //# sourceMappingURL=recording-dir.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"recording-dir.js","sourceRoot":"","sources":["../../../src/recording/recording-dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAEzD,2DAA2D;AAC3D,MAAM,UAAU,mBAAmB;IACjC,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACrC,SAAS,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB;IAC7C,OAAO,IAAI,CAAC,mBAAmB,EAAE,GAAG,SAAS,SAAS,CAAC,CAAC;AAC1D,CAAC"}
@@ -1,50 +0,0 @@
1
- import type { LLMAdapter } from '../llm/types.js';
2
- export interface RecordingOptions {
3
- /** --record 또는 -r 플래그 */
4
- record?: boolean;
5
- /** --mp4 플래그 — GIF와 함께 mp4도 생성 */
6
- mp4?: boolean;
7
- /** 출력 디렉토리 (기본값: 현재 디렉토리) */
8
- outputDir?: string;
9
- }
10
- /**
11
- * RecordingOrchestrator: asciinema 기반 녹화의 전체 생명주기를 조율한다.
12
- *
13
- * 사용 패턴 (interview CLI):
14
- *
15
- * 1. startIfNeeded() — interview 시작 전 호출. --record 플래그가 있고
16
- * 아직 asciinema로 감싸지지 않았으면 self-respawn으로 재실행.
17
- *
18
- * 2. stopAndConvert() — interview 완료 후 호출. 백그라운드 비동기로
19
- * .cast → GIF (→ mp4) 변환을 트리거한다.
20
- */
21
- export declare class RecordingOrchestrator {
22
- private readonly llm;
23
- private readonly asciinemaInstaller;
24
- private readonly aggInstaller;
25
- private readonly converter;
26
- constructor(llm: LLMAdapter);
27
- /**
28
- * 필요하면 asciinema 녹화를 시작한다.
29
- * - GESTALT_RECORDING=1이면 이미 asciinema 안에 있으므로 아무것도 하지 않음.
30
- * - --record 플래그가 있으면 asciinema를 설치하고 self-respawn.
31
- * - 재실행 후에는 process.exit()이 호출되므로 이 함수는 return하지 않을 수 있음.
32
- */
33
- startIfNeeded(options: RecordingOptions): Promise<void>;
34
- /**
35
- * 현재 프로세스가 asciinema로 녹화 중인지 확인.
36
- * GESTALT_CAST_PATH 환경변수가 있으면 true.
37
- */
38
- isRecording(): boolean;
39
- /**
40
- * 녹화를 종료하고 GIF (+ mp4) 변환을 백그라운드로 트리거한다.
41
- * asciinema는 부모 프로세스(respawned)에서 자동 종료되므로
42
- * 여기서는 cast 파일 경로를 읽어 변환만 시작한다.
43
- *
44
- * @param topic - 인터뷰 주제 (파일명 생성용)
45
- * @param sessionId - 세션 ID (파일명 생성용)
46
- * @param options - 녹화 옵션
47
- */
48
- stopAndConvert(topic: string, sessionId: string, options?: RecordingOptions): Promise<void>;
49
- }
50
- //# sourceMappingURL=recording-orchestrator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"recording-orchestrator.d.ts","sourceRoot":"","sources":["../../../src/recording/recording-orchestrator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,yBAAyB;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kCAAkC;IAClC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;IAKpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJhC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4B;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;gBAEnB,GAAG,EAAE,UAAU;IAE5C;;;;;OAKG;IACG,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7D;;;OAGG;IACH,WAAW,IAAI,OAAO;IAItB;;;;;;;;OAQG;IACG,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC;CAyCjB"}