@arcreflex/agent-transcripts 0.1.5 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +5 -0
- package/CLAUDE.md +4 -0
- package/README.md +70 -17
- package/bun.lock +89 -0
- package/package.json +3 -2
- package/src/adapters/claude-code.ts +300 -33
- package/src/cache.ts +129 -0
- package/src/cli.ts +95 -68
- package/src/convert.ts +82 -42
- package/src/parse.ts +7 -101
- package/src/render-html.ts +1096 -0
- package/src/render-index.ts +611 -0
- package/src/render.ts +7 -194
- package/src/serve.ts +308 -0
- package/src/sync.ts +211 -98
- package/src/title.ts +172 -0
- package/src/types.ts +18 -2
- package/src/utils/html.ts +12 -0
- package/src/utils/naming.ts +30 -143
- package/src/utils/openrouter.ts +116 -0
- package/src/utils/provenance.ts +167 -69
- package/src/utils/tree.ts +116 -0
- package/test/fixtures/claude/non-message-parents.input.jsonl +9 -0
- package/test/fixtures/claude/non-message-parents.output.md +30 -0
- package/test/snapshots.test.ts +39 -33
package/src/cli.ts
CHANGED
|
@@ -7,18 +7,28 @@ import {
|
|
|
7
7
|
subcommands,
|
|
8
8
|
run,
|
|
9
9
|
string,
|
|
10
|
+
number,
|
|
10
11
|
option,
|
|
11
12
|
optional,
|
|
12
13
|
positional,
|
|
13
14
|
flag,
|
|
14
15
|
} from "cmd-ts";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { sync } from "./sync.ts";
|
|
16
|
+
import { parseToTranscripts } from "./parse.ts";
|
|
17
|
+
import { renderTranscript } from "./render.ts";
|
|
18
|
+
import { sync, type OutputFormat } from "./sync.ts";
|
|
18
19
|
import { convertToDirectory } from "./convert.ts";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
import { generateTitles } from "./title.ts";
|
|
21
|
+
import { serve } from "./serve.ts";
|
|
22
|
+
|
|
23
|
+
// Custom type for format option
|
|
24
|
+
const formatType = {
|
|
25
|
+
async from(value: string): Promise<OutputFormat> {
|
|
26
|
+
if (value !== "md" && value !== "html") {
|
|
27
|
+
throw new Error(`Invalid format: ${value}. Must be "md" or "html".`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
22
32
|
|
|
23
33
|
// Shared options
|
|
24
34
|
const inputArg = positional({
|
|
@@ -31,7 +41,7 @@ const outputOpt = option({
|
|
|
31
41
|
type: optional(string),
|
|
32
42
|
long: "output",
|
|
33
43
|
short: "o",
|
|
34
|
-
description: "Output
|
|
44
|
+
description: "Output directory (prints to stdout if not specified)",
|
|
35
45
|
});
|
|
36
46
|
|
|
37
47
|
const adapterOpt = option({
|
|
@@ -47,50 +57,10 @@ const headOpt = option({
|
|
|
47
57
|
description: "Render branch ending at this message ID (default: latest)",
|
|
48
58
|
});
|
|
49
59
|
|
|
50
|
-
// Parse subcommand
|
|
51
|
-
const parseCmd = command({
|
|
52
|
-
name: "parse",
|
|
53
|
-
description: "Parse source format to intermediate JSON",
|
|
54
|
-
args: {
|
|
55
|
-
input: inputArg,
|
|
56
|
-
output: outputOpt,
|
|
57
|
-
adapter: adapterOpt,
|
|
58
|
-
},
|
|
59
|
-
async handler({ input, output, adapter }) {
|
|
60
|
-
const naming = OPENROUTER_API_KEY
|
|
61
|
-
? { apiKey: OPENROUTER_API_KEY }
|
|
62
|
-
: undefined;
|
|
63
|
-
|
|
64
|
-
if (output) {
|
|
65
|
-
await parse({ input, output, adapter, naming });
|
|
66
|
-
} else {
|
|
67
|
-
// Print JSONL to stdout (one transcript per line)
|
|
68
|
-
const { transcripts } = await parseToTranscripts({ input, adapter });
|
|
69
|
-
for (const transcript of transcripts) {
|
|
70
|
-
console.log(JSON.stringify(transcript));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Render subcommand
|
|
77
|
-
const renderCmd = command({
|
|
78
|
-
name: "render",
|
|
79
|
-
description: "Render intermediate JSON to markdown",
|
|
80
|
-
args: {
|
|
81
|
-
input: inputArg,
|
|
82
|
-
output: outputOpt,
|
|
83
|
-
head: headOpt,
|
|
84
|
-
},
|
|
85
|
-
async handler({ input, output, head }) {
|
|
86
|
-
await render({ input, output, head });
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
60
|
// Sync subcommand
|
|
91
61
|
const syncCmd = command({
|
|
92
62
|
name: "sync",
|
|
93
|
-
description: "Sync session files to markdown
|
|
63
|
+
description: "Sync session files to transcripts (markdown or HTML)",
|
|
94
64
|
args: {
|
|
95
65
|
source: positional({
|
|
96
66
|
type: string,
|
|
@@ -103,6 +73,15 @@ const syncCmd = command({
|
|
|
103
73
|
short: "o",
|
|
104
74
|
description: "Output directory for transcripts",
|
|
105
75
|
}),
|
|
76
|
+
format: option({
|
|
77
|
+
type: optional(formatType),
|
|
78
|
+
long: "format",
|
|
79
|
+
description: "Output format: md (default) or html",
|
|
80
|
+
}),
|
|
81
|
+
noTitle: flag({
|
|
82
|
+
long: "no-title",
|
|
83
|
+
description: "Skip LLM title generation (for HTML format)",
|
|
84
|
+
}),
|
|
106
85
|
force: flag({
|
|
107
86
|
long: "force",
|
|
108
87
|
short: "f",
|
|
@@ -114,11 +93,65 @@ const syncCmd = command({
|
|
|
114
93
|
description: "Suppress progress output",
|
|
115
94
|
}),
|
|
116
95
|
},
|
|
117
|
-
async handler({ source, output, force, quiet }) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
96
|
+
async handler({ source, output, format, noTitle, force, quiet }) {
|
|
97
|
+
await sync({ source, output, format, noTitle, force, quiet });
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Title subcommand
|
|
102
|
+
const titleCmd = command({
|
|
103
|
+
name: "title",
|
|
104
|
+
description: "Generate LLM titles for transcripts.json entries",
|
|
105
|
+
args: {
|
|
106
|
+
output: positional({
|
|
107
|
+
type: string,
|
|
108
|
+
displayName: "output",
|
|
109
|
+
description: "Output directory containing transcripts.json",
|
|
110
|
+
}),
|
|
111
|
+
force: flag({
|
|
112
|
+
long: "force",
|
|
113
|
+
short: "f",
|
|
114
|
+
description: "Regenerate all titles, not just missing ones",
|
|
115
|
+
}),
|
|
116
|
+
quiet: flag({
|
|
117
|
+
long: "quiet",
|
|
118
|
+
short: "q",
|
|
119
|
+
description: "Suppress progress output",
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
async handler({ output, force, quiet }) {
|
|
123
|
+
await generateTitles({ outputDir: output, force, quiet });
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Serve subcommand
|
|
128
|
+
const serveCmd = command({
|
|
129
|
+
name: "serve",
|
|
130
|
+
description: "Serve transcripts via HTTP (dynamic rendering with caching)",
|
|
131
|
+
args: {
|
|
132
|
+
source: positional({
|
|
133
|
+
type: string,
|
|
134
|
+
displayName: "source",
|
|
135
|
+
description: "Source directory to scan for session files",
|
|
136
|
+
}),
|
|
137
|
+
port: option({
|
|
138
|
+
type: optional(number),
|
|
139
|
+
long: "port",
|
|
140
|
+
short: "p",
|
|
141
|
+
description: "Port to listen on (default: 3000)",
|
|
142
|
+
}),
|
|
143
|
+
quiet: flag({
|
|
144
|
+
long: "quiet",
|
|
145
|
+
short: "q",
|
|
146
|
+
description: "Suppress request logging",
|
|
147
|
+
}),
|
|
148
|
+
noCache: flag({
|
|
149
|
+
long: "no-cache",
|
|
150
|
+
description: "Bypass HTML cache (for development)",
|
|
151
|
+
}),
|
|
152
|
+
},
|
|
153
|
+
async handler({ source, port, quiet, noCache }) {
|
|
154
|
+
await serve({ source, port: port ?? 3000, quiet, noCache });
|
|
122
155
|
},
|
|
123
156
|
});
|
|
124
157
|
|
|
@@ -140,26 +173,20 @@ const convertCmd = command({
|
|
|
140
173
|
head: headOpt,
|
|
141
174
|
},
|
|
142
175
|
async handler({ input, output, adapter, head }) {
|
|
143
|
-
const naming = OPENROUTER_API_KEY
|
|
144
|
-
? { apiKey: OPENROUTER_API_KEY }
|
|
145
|
-
: undefined;
|
|
146
|
-
|
|
147
176
|
if (output && isDirectoryOutput(output)) {
|
|
148
|
-
// Directory output: use
|
|
177
|
+
// Directory output: use provenance tracking
|
|
149
178
|
await convertToDirectory({
|
|
150
179
|
input,
|
|
151
180
|
outputDir: output,
|
|
152
181
|
adapter,
|
|
153
182
|
head,
|
|
154
|
-
naming,
|
|
155
183
|
});
|
|
156
184
|
} else if (output) {
|
|
157
|
-
// Explicit file output:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
185
|
+
// Explicit file output: not supported anymore (use directory)
|
|
186
|
+
console.error(
|
|
187
|
+
"Error: Explicit file output not supported. Use a directory path instead.",
|
|
188
|
+
);
|
|
189
|
+
process.exit(1);
|
|
163
190
|
} else {
|
|
164
191
|
// No output: stream to stdout
|
|
165
192
|
const { transcripts } = await parseToTranscripts({ input, adapter });
|
|
@@ -171,7 +198,7 @@ const convertCmd = command({
|
|
|
171
198
|
},
|
|
172
199
|
});
|
|
173
200
|
|
|
174
|
-
const SUBCOMMANDS = ["convert", "
|
|
201
|
+
const SUBCOMMANDS = ["convert", "sync", "title", "serve"] as const;
|
|
175
202
|
|
|
176
203
|
// Main CLI with subcommands
|
|
177
204
|
const cli = subcommands({
|
|
@@ -179,9 +206,9 @@ const cli = subcommands({
|
|
|
179
206
|
description: "Transform agent session files to readable transcripts",
|
|
180
207
|
cmds: {
|
|
181
208
|
convert: convertCmd,
|
|
182
|
-
parse: parseCmd,
|
|
183
|
-
render: renderCmd,
|
|
184
209
|
sync: syncCmd,
|
|
210
|
+
title: titleCmd,
|
|
211
|
+
serve: serveCmd,
|
|
185
212
|
},
|
|
186
213
|
});
|
|
187
214
|
|
package/src/convert.ts
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Convert command: full pipeline with provenance tracking.
|
|
3
3
|
*
|
|
4
|
-
* When output is a directory, uses
|
|
5
|
-
*
|
|
4
|
+
* When output is a directory, uses provenance tracking via transcripts.json
|
|
5
|
+
* index to manage output files.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { mkdir
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { mkdir } from "fs/promises";
|
|
10
10
|
import { parseToTranscripts } from "./parse.ts";
|
|
11
11
|
import { renderTranscript } from "./render.ts";
|
|
12
|
-
import { generateOutputName,
|
|
12
|
+
import { generateOutputName, extractSessionId } from "./utils/naming.ts";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
loadIndex,
|
|
15
|
+
saveIndex,
|
|
16
|
+
removeEntriesForSource,
|
|
17
|
+
restoreEntries,
|
|
18
|
+
deleteOutputFiles,
|
|
19
|
+
setEntry,
|
|
20
|
+
normalizeSourcePath,
|
|
21
|
+
extractFirstUserMessage,
|
|
16
22
|
} from "./utils/provenance.ts";
|
|
17
23
|
|
|
18
24
|
export interface ConvertToDirectoryOptions {
|
|
@@ -20,7 +26,6 @@ export interface ConvertToDirectoryOptions {
|
|
|
20
26
|
outputDir: string;
|
|
21
27
|
adapter?: string;
|
|
22
28
|
head?: string;
|
|
23
|
-
naming?: NamingOptions;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
/**
|
|
@@ -30,7 +35,10 @@ export interface ConvertToDirectoryOptions {
|
|
|
30
35
|
export async function convertToDirectory(
|
|
31
36
|
options: ConvertToDirectoryOptions,
|
|
32
37
|
): Promise<void> {
|
|
33
|
-
const { input, outputDir, adapter, head
|
|
38
|
+
const { input, outputDir, adapter, head } = options;
|
|
39
|
+
|
|
40
|
+
// Ensure output directory exists
|
|
41
|
+
await mkdir(outputDir, { recursive: true });
|
|
34
42
|
|
|
35
43
|
// Parse input to transcripts
|
|
36
44
|
const { transcripts, inputPath } = await parseToTranscripts({
|
|
@@ -38,41 +46,73 @@ export async function convertToDirectory(
|
|
|
38
46
|
adapter,
|
|
39
47
|
});
|
|
40
48
|
|
|
41
|
-
//
|
|
42
|
-
const sourcePath =
|
|
49
|
+
// Normalize source path for consistent index keys
|
|
50
|
+
const sourcePath = normalizeSourcePath(inputPath);
|
|
51
|
+
|
|
52
|
+
// Load index and handle existing outputs
|
|
53
|
+
const index = await loadIndex(outputDir);
|
|
54
|
+
|
|
55
|
+
// Remove old entries (save for restoration on error)
|
|
56
|
+
const removedEntries =
|
|
57
|
+
sourcePath !== "<stdin>" ? removeEntriesForSource(index, sourcePath) : [];
|
|
58
|
+
|
|
59
|
+
const sessionId = extractSessionId(inputPath);
|
|
60
|
+
const newOutputs: string[] = [];
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Generate fresh outputs
|
|
64
|
+
for (let i = 0; i < transcripts.length; i++) {
|
|
65
|
+
const transcript = transcripts[i];
|
|
66
|
+
const segmentIndex = transcripts.length > 1 ? i + 1 : undefined;
|
|
67
|
+
|
|
68
|
+
// Generate deterministic name
|
|
69
|
+
const baseName = generateOutputName(transcript, inputPath);
|
|
70
|
+
const suffix = segmentIndex ? `_${segmentIndex}` : "";
|
|
71
|
+
const relativePath = `${baseName}${suffix}.md`;
|
|
72
|
+
const outputPath = join(outputDir, relativePath);
|
|
43
73
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
// Render with provenance front matter
|
|
75
|
+
const markdown = renderTranscript(transcript, {
|
|
76
|
+
head,
|
|
77
|
+
sourcePath: sourcePath !== "<stdin>" ? sourcePath : undefined,
|
|
78
|
+
});
|
|
79
|
+
await Bun.write(outputPath, markdown);
|
|
80
|
+
newOutputs.push(relativePath);
|
|
81
|
+
|
|
82
|
+
// Update index (only for non-stdin sources)
|
|
83
|
+
if (sourcePath !== "<stdin>") {
|
|
84
|
+
setEntry(index, relativePath, {
|
|
85
|
+
source: sourcePath,
|
|
86
|
+
sessionId,
|
|
87
|
+
segmentIndex,
|
|
88
|
+
syncedAt: new Date().toISOString(),
|
|
89
|
+
firstUserMessage: extractFirstUserMessage(transcript),
|
|
90
|
+
messageCount: transcript.metadata.messageCount,
|
|
91
|
+
startTime: transcript.metadata.startTime,
|
|
92
|
+
endTime: transcript.metadata.endTime,
|
|
93
|
+
cwd: transcript.metadata.cwd,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.error(`Wrote: ${outputPath}`);
|
|
49
98
|
}
|
|
50
|
-
}
|
|
51
99
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Ensure output directory exists
|
|
67
|
-
await mkdir(dirname(outputPath), { recursive: true });
|
|
68
|
-
|
|
69
|
-
// Render with provenance front matter
|
|
70
|
-
const markdown = renderTranscript(transcript, {
|
|
71
|
-
head,
|
|
72
|
-
sourcePath: sourcePath !== "<stdin>" ? sourcePath : undefined,
|
|
73
|
-
});
|
|
74
|
-
await Bun.write(outputPath, markdown);
|
|
75
|
-
|
|
76
|
-
console.error(`Wrote: ${outputPath}`);
|
|
100
|
+
// Success: delete old output files (after new ones are written)
|
|
101
|
+
const oldFilenames = removedEntries.map((e) => e.filename);
|
|
102
|
+
const toDelete = oldFilenames.filter((f) => !newOutputs.includes(f));
|
|
103
|
+
if (toDelete.length > 0) {
|
|
104
|
+
await deleteOutputFiles(outputDir, toDelete);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Clean up any newly written files before restoring old entries
|
|
108
|
+
if (newOutputs.length > 0) {
|
|
109
|
+
await deleteOutputFiles(outputDir, newOutputs);
|
|
110
|
+
}
|
|
111
|
+
// Restore old entries on error to preserve provenance
|
|
112
|
+
restoreEntries(index, removedEntries);
|
|
113
|
+
throw error;
|
|
77
114
|
}
|
|
115
|
+
|
|
116
|
+
// Save index
|
|
117
|
+
await saveIndex(outputDir, index);
|
|
78
118
|
}
|
package/src/parse.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parse
|
|
2
|
+
* Parse: source format → intermediate transcript format
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { dirname, join } from "path";
|
|
6
|
-
import { mkdir } from "fs/promises";
|
|
7
5
|
import type { Transcript } from "./types.ts";
|
|
8
6
|
import { detectAdapter, getAdapter, listAdapters } from "./adapters/index.ts";
|
|
9
|
-
import { generateOutputName, type NamingOptions } from "./utils/naming.ts";
|
|
10
7
|
|
|
11
8
|
export interface ParseOptions {
|
|
12
9
|
input: string; // file path, or "-" for stdin
|
|
13
|
-
output?: string; // output path/dir
|
|
14
10
|
adapter?: string; // explicit adapter name
|
|
15
|
-
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ParseResult {
|
|
14
|
+
transcripts: Transcript[];
|
|
15
|
+
inputPath: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -40,73 +40,7 @@ async function readInput(
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*/
|
|
45
|
-
async function getOutputPaths(
|
|
46
|
-
transcripts: Transcript[],
|
|
47
|
-
inputPath: string,
|
|
48
|
-
outputOption?: string,
|
|
49
|
-
namingOptions?: NamingOptions,
|
|
50
|
-
): Promise<string[]> {
|
|
51
|
-
// Determine output directory
|
|
52
|
-
let outputDir: string;
|
|
53
|
-
let explicitBaseName: string | undefined;
|
|
54
|
-
|
|
55
|
-
if (outputOption) {
|
|
56
|
-
// If output looks like a file (has extension), use its directory and name
|
|
57
|
-
if (outputOption.match(/\.\w+$/)) {
|
|
58
|
-
outputDir = dirname(outputOption);
|
|
59
|
-
explicitBaseName = outputOption
|
|
60
|
-
.split("/")
|
|
61
|
-
.pop()!
|
|
62
|
-
.replace(/\.\w+$/, "");
|
|
63
|
-
} else {
|
|
64
|
-
outputDir = outputOption;
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
outputDir = process.cwd();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Generate paths with descriptive names
|
|
71
|
-
const paths: string[] = [];
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < transcripts.length; i++) {
|
|
74
|
-
let baseName: string;
|
|
75
|
-
|
|
76
|
-
if (explicitBaseName) {
|
|
77
|
-
// User provided explicit filename
|
|
78
|
-
baseName = explicitBaseName;
|
|
79
|
-
} else {
|
|
80
|
-
// Generate descriptive name
|
|
81
|
-
baseName = await generateOutputName(
|
|
82
|
-
transcripts[i],
|
|
83
|
-
inputPath,
|
|
84
|
-
namingOptions || {},
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Add suffix for multiple transcripts
|
|
89
|
-
if (transcripts.length > 1) {
|
|
90
|
-
baseName = `${baseName}_${i + 1}`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
paths.push(join(outputDir, `${baseName}.json`));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return paths;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface ParseResult {
|
|
100
|
-
transcripts: Transcript[];
|
|
101
|
-
inputPath: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface ParseAndWriteResult extends ParseResult {
|
|
105
|
-
outputPaths: string[];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Parse source file(s) to transcripts (no file I/O beyond reading input).
|
|
43
|
+
* Parse source file(s) to transcripts.
|
|
110
44
|
*/
|
|
111
45
|
export async function parseToTranscripts(
|
|
112
46
|
options: ParseOptions,
|
|
@@ -135,31 +69,3 @@ export async function parseToTranscripts(
|
|
|
135
69
|
const transcripts = adapter.parse(content, inputPath);
|
|
136
70
|
return { transcripts, inputPath };
|
|
137
71
|
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Parse source file(s) to intermediate JSON and write to files.
|
|
141
|
-
*/
|
|
142
|
-
export async function parse(
|
|
143
|
-
options: ParseOptions,
|
|
144
|
-
): Promise<ParseAndWriteResult> {
|
|
145
|
-
const { transcripts, inputPath } = await parseToTranscripts(options);
|
|
146
|
-
|
|
147
|
-
// Write output files
|
|
148
|
-
const outputPaths = await getOutputPaths(
|
|
149
|
-
transcripts,
|
|
150
|
-
inputPath,
|
|
151
|
-
options.output,
|
|
152
|
-
options.naming,
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
for (let i = 0; i < transcripts.length; i++) {
|
|
156
|
-
const json = JSON.stringify(transcripts[i], null, 2);
|
|
157
|
-
// Ensure directory exists
|
|
158
|
-
const dir = dirname(outputPaths[i]);
|
|
159
|
-
await mkdir(dir, { recursive: true });
|
|
160
|
-
await Bun.write(outputPaths[i], json);
|
|
161
|
-
console.error(`Wrote: ${outputPaths[i]}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return { transcripts, inputPath, outputPaths };
|
|
165
|
-
}
|