poddrill 0.0.0-stage → 0.1.1
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/.agents/skills/poddrill/SKILL.md +104 -0
- package/.agents/skills/poddrill/references/assign-speakers.md +22 -0
- package/.agents/skills/poddrill/references/create-summary.md +140 -0
- package/.agents/skills/poddrill/references/fetch-audio.md +40 -0
- package/.agents/skills/poddrill/references/orwell.md +71 -0
- package/.agents/skills/poddrill/references/select-models.md +58 -0
- package/.agents/skills/poddrill/references/track-sources.md +59 -0
- package/.agents/skills/poddrill/scripts/fetch-audio.mjs +154 -0
- package/LICENSE +21 -0
- package/README.md +24 -2
- package/dist/audio.js +262 -0
- package/dist/audit.js +834 -0
- package/dist/cli.js +455 -0
- package/dist/client.js +1450 -0
- package/dist/disk.js +47 -0
- package/dist/embed.js +127 -0
- package/dist/episode.js +259 -0
- package/dist/fleet.js +176 -0
- package/dist/groups.js +512 -0
- package/dist/idle-fleet.js +71 -0
- package/dist/jobs.js +1053 -0
- package/dist/models.js +45 -0
- package/dist/moonshine.js +185 -0
- package/dist/needle.js +42 -0
- package/dist/owner.js +770 -0
- package/dist/package-identity.js +73 -0
- package/dist/provider.js +123 -0
- package/dist/stretch.js +46 -0
- package/dist/summary.js +258 -0
- package/dist/transcribe.js +53 -0
- package/dist/worker.js +215 -0
- package/dist/workspace.js +306 -0
- package/package.json +31 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { clientMain, workspaceSetupCollision } from "./client.js";
|
|
7
|
+
import { asRecord, errorCode } from "./disk.js";
|
|
8
|
+
import { isModelId } from "./models.js";
|
|
9
|
+
import { callOwner, ownerAlive, proxyMcp, render, serve } from "./owner.js";
|
|
10
|
+
import { resolveWorkspace, setupWorkspace } from "./workspace.js";
|
|
11
|
+
const usage = [
|
|
12
|
+
"usage: poddrill setup --provider <id> --queue-length <n> [--fleet-limit <n>] [--whistle-limit <n>]",
|
|
13
|
+
"[--model-fleet <id>=<count>] [--model-weights <id>=<path>] [--embedding-limit <n>]",
|
|
14
|
+
"| start <path> [--language <code>] | episode <episodeId> | cues <episodeId> [--limit <n>] [--after <cueId>] [--group-id <id>] [--hosts]",
|
|
15
|
+
"| label-groups <episodeId> <json> | start-summary <episodeId> | set-summary <episodeId> <summaryId> <json>",
|
|
16
|
+
"| put-paragraph <episodeId> <summaryId> <json> | post-paragraph <episodeId> <summaryId> <json> | summary <episodeId> <summaryId> | audit <episodeId> | serve | mcp",
|
|
17
|
+
].join(" ");
|
|
18
|
+
async function main() {
|
|
19
|
+
const command = process.argv[2];
|
|
20
|
+
switch (command) {
|
|
21
|
+
case "setup":
|
|
22
|
+
await runSetup(process.argv.slice(3));
|
|
23
|
+
return;
|
|
24
|
+
case "client": {
|
|
25
|
+
const rendered = await clientMain(process.argv.slice(3), process.env, import.meta.url);
|
|
26
|
+
await emit(rendered.text, rendered.exitCode);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
case "start":
|
|
30
|
+
await runStart(process.argv.slice(3));
|
|
31
|
+
return;
|
|
32
|
+
case "episode":
|
|
33
|
+
await runEpisode(process.argv[3]);
|
|
34
|
+
return;
|
|
35
|
+
case "cues":
|
|
36
|
+
await runCues(process.argv.slice(3));
|
|
37
|
+
return;
|
|
38
|
+
case "label-groups":
|
|
39
|
+
await runLabelGroups(process.argv[3], process.argv[4]);
|
|
40
|
+
return;
|
|
41
|
+
case "start-summary":
|
|
42
|
+
await runStartSummary(process.argv[3]);
|
|
43
|
+
return;
|
|
44
|
+
case "set-summary":
|
|
45
|
+
await runSetSummary(process.argv[3], process.argv[4], process.argv[5]);
|
|
46
|
+
return;
|
|
47
|
+
case "put-paragraph":
|
|
48
|
+
await runPutParagraph(process.argv[3], process.argv[4], process.argv[5]);
|
|
49
|
+
return;
|
|
50
|
+
case "post-paragraph":
|
|
51
|
+
await runPostParagraph(process.argv[3], process.argv[4], process.argv[5]);
|
|
52
|
+
return;
|
|
53
|
+
case "summary":
|
|
54
|
+
await runSummary(process.argv[3], process.argv[4]);
|
|
55
|
+
return;
|
|
56
|
+
case "audit":
|
|
57
|
+
await runAudit(process.argv[3]);
|
|
58
|
+
return;
|
|
59
|
+
case "serve":
|
|
60
|
+
await serve(await resolveWorkspace(process.env), false);
|
|
61
|
+
return;
|
|
62
|
+
case "mcp":
|
|
63
|
+
await runMcp();
|
|
64
|
+
return;
|
|
65
|
+
default:
|
|
66
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function runSetup(args) {
|
|
70
|
+
const collision = workspaceSetupCollision(args);
|
|
71
|
+
if (collision !== undefined) {
|
|
72
|
+
await emit(render({ kind: "error", message: collision }), 1);
|
|
73
|
+
}
|
|
74
|
+
const provider = flag(args, "--provider");
|
|
75
|
+
const queueText = flag(args, "--queue-length");
|
|
76
|
+
const fleetText = flag(args, "--fleet-limit");
|
|
77
|
+
const whistleText = flag(args, "--whistle-limit");
|
|
78
|
+
const embeddingText = flag(args, "--embedding-limit");
|
|
79
|
+
const fleetArgs = flagValues(args, "--model-fleet");
|
|
80
|
+
const weightArgs = flagValues(args, "--model-weights");
|
|
81
|
+
if (provider === undefined ||
|
|
82
|
+
queueText === undefined ||
|
|
83
|
+
!isCountText(queueText) ||
|
|
84
|
+
(args.includes("--fleet-limit") && !isCountText(fleetText ?? "")) ||
|
|
85
|
+
(args.includes("--whistle-limit") && !isCountText(whistleText ?? "")) ||
|
|
86
|
+
(args.includes("--embedding-limit") && !isCountText(embeddingText ?? "")) ||
|
|
87
|
+
!fleetPairsOk(fleetArgs) ||
|
|
88
|
+
!weightPairsOk(weightArgs)) {
|
|
89
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
90
|
+
}
|
|
91
|
+
const fleetLimit = sharedLimit(fleetText, whistleText);
|
|
92
|
+
const modelFleet = modelFleetFrom(fleetArgs);
|
|
93
|
+
const modelWeights = modelWeightsFrom(weightArgs);
|
|
94
|
+
const root = await workspaceArg();
|
|
95
|
+
const alive = await ownerAlive(root);
|
|
96
|
+
const result = await setupWorkspace({
|
|
97
|
+
root,
|
|
98
|
+
provider,
|
|
99
|
+
queueLength: Number(queueText),
|
|
100
|
+
...(fleetLimit === undefined ? {} : { fleetLimit }),
|
|
101
|
+
...(modelFleet === undefined ? {} : { modelFleet }),
|
|
102
|
+
...(modelWeights === undefined ? {} : { modelWeights }),
|
|
103
|
+
...(embeddingText === undefined ? {} : { embeddingLimit: Number(embeddingText) }),
|
|
104
|
+
ownerAlive: alive,
|
|
105
|
+
});
|
|
106
|
+
await emit(render(publicSetup(result)), result.kind === "ready" ? 0 : 1);
|
|
107
|
+
}
|
|
108
|
+
async function runStart(args) {
|
|
109
|
+
const language = flag(args, "--language");
|
|
110
|
+
const audioPath = startPath(args);
|
|
111
|
+
if (audioPath === undefined ||
|
|
112
|
+
(args.includes("--language") && (language === undefined || language === ""))) {
|
|
113
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
114
|
+
}
|
|
115
|
+
const root = await resolveWorkspace(process.env);
|
|
116
|
+
await ensureOwner(root);
|
|
117
|
+
const text = await callOwner(root, {
|
|
118
|
+
op: "start",
|
|
119
|
+
audioPath,
|
|
120
|
+
...(language === undefined ? {} : { language }),
|
|
121
|
+
});
|
|
122
|
+
const code = text.includes("\"kind\":\"rejected\"") || text.includes("\"kind\":\"full\"") ? 1 : 0;
|
|
123
|
+
await emit(text, code);
|
|
124
|
+
}
|
|
125
|
+
async function runCues(args) {
|
|
126
|
+
const episodeId = args[0];
|
|
127
|
+
if (episodeId === undefined || episodeId === "" || episodeId.startsWith("-")) {
|
|
128
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
129
|
+
}
|
|
130
|
+
const limitText = flag(args, "--limit");
|
|
131
|
+
const after = flag(args, "--after");
|
|
132
|
+
const groupId = flag(args, "--group-id");
|
|
133
|
+
if (args.includes("--limit") && (limitText === undefined || !/^-?\d+$/.test(limitText))) {
|
|
134
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
135
|
+
}
|
|
136
|
+
if (args.includes("--after") && (after === undefined || after === "")) {
|
|
137
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
138
|
+
}
|
|
139
|
+
if (args.includes("--group-id") && (groupId === undefined || groupId === "")) {
|
|
140
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
141
|
+
}
|
|
142
|
+
const root = await resolveWorkspace(process.env);
|
|
143
|
+
await ensureOwner(root);
|
|
144
|
+
const text = await callOwner(root, {
|
|
145
|
+
op: "cues",
|
|
146
|
+
episodeId,
|
|
147
|
+
...(limitText === undefined ? {} : { limit: Number(limitText) }),
|
|
148
|
+
...(after === undefined ? {} : { after }),
|
|
149
|
+
...(groupId === undefined ? {} : { groupId }),
|
|
150
|
+
...(args.includes("--hosts") ? { hosts: true } : {}),
|
|
151
|
+
});
|
|
152
|
+
const code = text.includes("\"kind\":\"rejected\"") || text.includes("\"kind\":\"error\"") ? 1 : 0;
|
|
153
|
+
await emit(text, code);
|
|
154
|
+
}
|
|
155
|
+
async function runLabelGroups(episodeId, json) {
|
|
156
|
+
if (episodeId === undefined || episodeId === "" || json === undefined || json === "") {
|
|
157
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
158
|
+
}
|
|
159
|
+
let groups;
|
|
160
|
+
try {
|
|
161
|
+
groups = JSON.parse(json);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
await emit(render({ kind: "error", message: "label-groups expects a JSON array of { groupId, label }" }), 1);
|
|
165
|
+
}
|
|
166
|
+
const root = await resolveWorkspace(process.env);
|
|
167
|
+
await ensureOwner(root);
|
|
168
|
+
const text = await callOwner(root, { op: "labelGroups", episodeId, groups });
|
|
169
|
+
const code = text.includes("\"kind\":\"error\"") ? 1 : 0;
|
|
170
|
+
await emit(text, code);
|
|
171
|
+
}
|
|
172
|
+
async function runStartSummary(episodeId) {
|
|
173
|
+
if (episodeId === undefined || episodeId === "") {
|
|
174
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
175
|
+
}
|
|
176
|
+
const root = await resolveWorkspace(process.env);
|
|
177
|
+
await ensureOwner(root);
|
|
178
|
+
const text = await callOwner(root, {
|
|
179
|
+
op: "startSummary",
|
|
180
|
+
episodeId,
|
|
181
|
+
});
|
|
182
|
+
await emit(text, text.includes("\"kind\":\"error\"") ? 1 : 0);
|
|
183
|
+
}
|
|
184
|
+
async function runSetSummary(episodeId, summaryId, json) {
|
|
185
|
+
if (episodeId === undefined || episodeId === "" || summaryId === undefined || summaryId === "") {
|
|
186
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
187
|
+
}
|
|
188
|
+
const body = await jsonObject(json, "set-summary expects JSON { summary }");
|
|
189
|
+
const root = await resolveWorkspace(process.env);
|
|
190
|
+
await ensureOwner(root);
|
|
191
|
+
const text = await callOwner(root, {
|
|
192
|
+
op: "setSummary",
|
|
193
|
+
episodeId,
|
|
194
|
+
summaryId,
|
|
195
|
+
summary: body["summary"],
|
|
196
|
+
});
|
|
197
|
+
await emit(text, text.includes("\"kind\":\"error\"") ? 1 : 0);
|
|
198
|
+
}
|
|
199
|
+
async function runPutParagraph(episodeId, summaryId, json) {
|
|
200
|
+
if (episodeId === undefined || episodeId === "" || summaryId === undefined || summaryId === "") {
|
|
201
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
202
|
+
}
|
|
203
|
+
const body = await jsonObject(json, "put-paragraph expects a JSON object");
|
|
204
|
+
const root = await resolveWorkspace(process.env);
|
|
205
|
+
await ensureOwner(root);
|
|
206
|
+
const text = await callOwner(root, { op: "putParagraph", episodeId, summaryId, body });
|
|
207
|
+
await emit(text, text.includes("\"kind\":\"error\"") ? 1 : 0);
|
|
208
|
+
}
|
|
209
|
+
async function runPostParagraph(episodeId, summaryId, json) {
|
|
210
|
+
if (episodeId === undefined || episodeId === "" || summaryId === undefined || summaryId === "") {
|
|
211
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
212
|
+
}
|
|
213
|
+
const body = await jsonObject(json, "post-paragraph expects a JSON object");
|
|
214
|
+
const root = await resolveWorkspace(process.env);
|
|
215
|
+
await ensureOwner(root);
|
|
216
|
+
const text = await callOwner(root, { op: "postParagraph", episodeId, summaryId, body });
|
|
217
|
+
await emit(text, text.includes("\"kind\":\"error\"") ? 1 : 0);
|
|
218
|
+
}
|
|
219
|
+
async function runSummary(episodeId, summaryId) {
|
|
220
|
+
if (episodeId === undefined ||
|
|
221
|
+
episodeId === "" ||
|
|
222
|
+
summaryId === undefined ||
|
|
223
|
+
summaryId === "") {
|
|
224
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
225
|
+
}
|
|
226
|
+
const root = await resolveWorkspace(process.env);
|
|
227
|
+
await ensureOwner(root);
|
|
228
|
+
const text = await callOwner(root, { op: "summary", episodeId, summaryId });
|
|
229
|
+
await emit(text, text.includes("\"kind\":\"error\"") ? 1 : 0);
|
|
230
|
+
}
|
|
231
|
+
async function jsonObject(json, message) {
|
|
232
|
+
if (json === undefined || json === "") {
|
|
233
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
234
|
+
}
|
|
235
|
+
let parsed;
|
|
236
|
+
try {
|
|
237
|
+
parsed = JSON.parse(json);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
await emit(render({ kind: "error", message }), 1);
|
|
241
|
+
}
|
|
242
|
+
const record = asRecord(parsed);
|
|
243
|
+
if (record === undefined) {
|
|
244
|
+
await emit(render({ kind: "error", message }), 1);
|
|
245
|
+
}
|
|
246
|
+
return record;
|
|
247
|
+
}
|
|
248
|
+
async function runAudit(episodeId) {
|
|
249
|
+
if (episodeId === undefined || episodeId === "") {
|
|
250
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
251
|
+
}
|
|
252
|
+
const root = await resolveWorkspace(process.env);
|
|
253
|
+
await ensureOwner(root);
|
|
254
|
+
const text = await callOwner(root, { op: "audit", episodeId });
|
|
255
|
+
await emit(text, text.includes("\"kind\":\"error\"") ? 1 : 0);
|
|
256
|
+
}
|
|
257
|
+
async function runEpisode(episodeId) {
|
|
258
|
+
if (episodeId === undefined || episodeId === "") {
|
|
259
|
+
await emit(render({ kind: "error", message: usage }), 1);
|
|
260
|
+
}
|
|
261
|
+
const root = await resolveWorkspace(process.env);
|
|
262
|
+
await ensureOwner(root);
|
|
263
|
+
await emit(await callOwner(root, { op: "episode", episodeId }), 0);
|
|
264
|
+
}
|
|
265
|
+
async function runMcp() {
|
|
266
|
+
const root = await resolveWorkspace(process.env);
|
|
267
|
+
if (await ownerAlive(root)) {
|
|
268
|
+
await proxyMcp(root);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
await serve(root, true);
|
|
272
|
+
}
|
|
273
|
+
async function ensureOwner(root) {
|
|
274
|
+
if (await ownerAlive(root)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const child = spawn(process.execPath, [...process.execArgv, fileURLToPath(import.meta.url), "serve"], { detached: true, stdio: "ignore", env: process.env });
|
|
278
|
+
child.unref();
|
|
279
|
+
await waitForOwner(root);
|
|
280
|
+
}
|
|
281
|
+
async function waitForOwner(root) {
|
|
282
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
283
|
+
if (await ownerAlive(root)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
await delay(50);
|
|
287
|
+
}
|
|
288
|
+
throw new Error("poddrill serve did not start");
|
|
289
|
+
}
|
|
290
|
+
function publicSetup(result) {
|
|
291
|
+
switch (result.kind) {
|
|
292
|
+
case "ready":
|
|
293
|
+
return {
|
|
294
|
+
kind: "ready",
|
|
295
|
+
provider: result.report.config.provider,
|
|
296
|
+
queueLength: result.report.config.queueLength,
|
|
297
|
+
fleetLimit: result.report.config.fleetLimit,
|
|
298
|
+
embeddingLimit: result.report.config.embeddingLimit,
|
|
299
|
+
};
|
|
300
|
+
case "rejected":
|
|
301
|
+
return {
|
|
302
|
+
kind: "rejected",
|
|
303
|
+
reason: result.reason,
|
|
304
|
+
provider: result.current.provider,
|
|
305
|
+
queueLength: result.current.queueLength,
|
|
306
|
+
fleetLimit: result.current.fleetLimit,
|
|
307
|
+
embeddingLimit: result.current.embeddingLimit,
|
|
308
|
+
message: result.message,
|
|
309
|
+
};
|
|
310
|
+
default: {
|
|
311
|
+
const leftover = result;
|
|
312
|
+
throw new Error(`unexpected setup ${JSON.stringify(leftover)}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function isCountText(text) {
|
|
317
|
+
return /^[1-9]\d*$/.test(text);
|
|
318
|
+
}
|
|
319
|
+
async function workspaceArg() {
|
|
320
|
+
const configured = process.env["PODDRILL_WORKSPACE"];
|
|
321
|
+
const candidate = resolve(configured === undefined || configured === "" ? process.cwd() : configured);
|
|
322
|
+
try {
|
|
323
|
+
return await realpath(candidate);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
if (errorCode(error) === "ENOENT") {
|
|
327
|
+
return candidate;
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function startPath(args) {
|
|
333
|
+
const paths = [];
|
|
334
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
335
|
+
const arg = args[index];
|
|
336
|
+
if (arg === undefined) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (arg === "--language") {
|
|
340
|
+
index += 1;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
paths.push(arg);
|
|
344
|
+
}
|
|
345
|
+
return paths.length === 1 ? paths[0] : undefined;
|
|
346
|
+
}
|
|
347
|
+
function flag(args, name) {
|
|
348
|
+
const index = args.indexOf(name);
|
|
349
|
+
if (index === -1) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
return args[index + 1];
|
|
353
|
+
}
|
|
354
|
+
function flagValues(args, name) {
|
|
355
|
+
const values = [];
|
|
356
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
357
|
+
if (args[index] !== name) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const value = args[index + 1];
|
|
361
|
+
if (value === undefined || value.startsWith("--")) {
|
|
362
|
+
values.push("");
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
values.push(value);
|
|
366
|
+
index += 1;
|
|
367
|
+
}
|
|
368
|
+
return values;
|
|
369
|
+
}
|
|
370
|
+
function sharedLimit(fleetText, whistleText) {
|
|
371
|
+
if (fleetText !== undefined && whistleText !== undefined && fleetText !== whistleText) {
|
|
372
|
+
throw new Error(`fleet limit ${fleetText} and whistle limit ${whistleText} differ`);
|
|
373
|
+
}
|
|
374
|
+
const chosen = fleetText ?? whistleText;
|
|
375
|
+
if (chosen === undefined) {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
return Number(chosen);
|
|
379
|
+
}
|
|
380
|
+
function fleetPairsOk(values) {
|
|
381
|
+
return values.every((value) => {
|
|
382
|
+
const pair = splitPair(value);
|
|
383
|
+
return pair !== undefined && isCountText(pair.right);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function weightPairsOk(values) {
|
|
387
|
+
return values.every((value) => splitPair(value) !== undefined);
|
|
388
|
+
}
|
|
389
|
+
function modelFleetFrom(values) {
|
|
390
|
+
if (values.length === 0) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
const fleet = {};
|
|
394
|
+
for (const value of values) {
|
|
395
|
+
const pair = splitPair(value);
|
|
396
|
+
if (pair === undefined) {
|
|
397
|
+
throw new Error(usage);
|
|
398
|
+
}
|
|
399
|
+
if (!isModelId(pair.left)) {
|
|
400
|
+
throw new Error(`unknown model ${pair.left}`);
|
|
401
|
+
}
|
|
402
|
+
fleet[pair.left] = Number(pair.right);
|
|
403
|
+
}
|
|
404
|
+
return fleet;
|
|
405
|
+
}
|
|
406
|
+
function modelWeightsFrom(values) {
|
|
407
|
+
if (values.length === 0) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
const weights = {};
|
|
411
|
+
for (const value of values) {
|
|
412
|
+
const pair = splitPair(value);
|
|
413
|
+
if (pair === undefined) {
|
|
414
|
+
throw new Error(usage);
|
|
415
|
+
}
|
|
416
|
+
if (!isModelId(pair.left)) {
|
|
417
|
+
throw new Error(`unknown model ${pair.left}`);
|
|
418
|
+
}
|
|
419
|
+
if (!isAbsolute(pair.right)) {
|
|
420
|
+
throw new Error(`model weights path for ${pair.left} must be absolute, got ${pair.right}`);
|
|
421
|
+
}
|
|
422
|
+
weights[pair.left] = pair.right;
|
|
423
|
+
}
|
|
424
|
+
return weights;
|
|
425
|
+
}
|
|
426
|
+
function splitPair(text) {
|
|
427
|
+
const index = text.indexOf("=");
|
|
428
|
+
if (index <= 0 || index === text.length - 1) {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
return { left: text.slice(0, index), right: text.slice(index + 1) };
|
|
432
|
+
}
|
|
433
|
+
function delay(ms) {
|
|
434
|
+
return new Promise((resolve) => {
|
|
435
|
+
setTimeout(resolve, ms);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async function emit(text, code) {
|
|
439
|
+
await new Promise((resolve, reject) => {
|
|
440
|
+
process.stdout.write(text, (error) => {
|
|
441
|
+
if (error) {
|
|
442
|
+
reject(error);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
resolve();
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
process.exit(code);
|
|
449
|
+
}
|
|
450
|
+
void main().catch((error) => {
|
|
451
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
452
|
+
process.stdout.write(render({ kind: "error", message }), () => {
|
|
453
|
+
process.exit(1);
|
|
454
|
+
});
|
|
455
|
+
});
|