bavitutor 0.1.0 → 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/dist/bavitutor.mjs +96 -42
- package/package.json +1 -1
package/dist/bavitutor.mjs
CHANGED
|
@@ -25052,7 +25052,7 @@ const number = /*#__PURE__*/ new Number$2();
|
|
|
25052
25052
|
* @category constructors
|
|
25053
25053
|
* @since 4.0.0
|
|
25054
25054
|
*/
|
|
25055
|
-
const Boolean$
|
|
25055
|
+
const Boolean$3 = class extends ASTNodeImpl {
|
|
25056
25056
|
_tag = "Boolean";
|
|
25057
25057
|
/** @internal */
|
|
25058
25058
|
getParser() {
|
|
@@ -25077,7 +25077,7 @@ const Boolean$2 = class extends ASTNodeImpl {
|
|
|
25077
25077
|
* @category constructors
|
|
25078
25078
|
* @since 4.0.0
|
|
25079
25079
|
*/
|
|
25080
|
-
const boolean = /*#__PURE__*/ new Boolean$
|
|
25080
|
+
const boolean = /*#__PURE__*/ new Boolean$3();
|
|
25081
25081
|
/**
|
|
25082
25082
|
* Constructs a {@link Arrays}.
|
|
25083
25083
|
*
|
|
@@ -30776,7 +30776,7 @@ const InetV6Proto = {
|
|
|
30776
30776
|
return this.toJSON();
|
|
30777
30777
|
}
|
|
30778
30778
|
};
|
|
30779
|
-
const checkPort
|
|
30779
|
+
const checkPort = (port) => Number.isInteger(port) && port >= 0 && port <= 65535;
|
|
30780
30780
|
/**
|
|
30781
30781
|
* Creates a checked IPv4 internet address.
|
|
30782
30782
|
*
|
|
@@ -30785,7 +30785,7 @@ const checkPort$1 = (port) => Number.isInteger(port) && port >= 0 && port <= 655
|
|
|
30785
30785
|
* @since 4.0.0
|
|
30786
30786
|
*/
|
|
30787
30787
|
const inetAddressV4 = (address, port) => {
|
|
30788
|
-
if (!checkPort
|
|
30788
|
+
if (!checkPort(port)) return addressError(address, "port must be an integer from 0 through 65535");
|
|
30789
30789
|
const self = Object.create(InetV4Proto);
|
|
30790
30790
|
self.address = address;
|
|
30791
30791
|
self.port = port;
|
|
@@ -30799,7 +30799,7 @@ const inetAddressV4 = (address, port) => {
|
|
|
30799
30799
|
* @since 4.0.0
|
|
30800
30800
|
*/
|
|
30801
30801
|
const inetAddressV6 = (address, port, options) => {
|
|
30802
|
-
if (!checkPort
|
|
30802
|
+
if (!checkPort(port)) return addressError(address, "port must be an integer from 0 through 65535");
|
|
30803
30803
|
const scopeId = options?.scopeId ?? 0;
|
|
30804
30804
|
if (!Number.isInteger(scopeId) || scopeId < 0 || scopeId > 4294967295) return addressError(address, "scopeId must be an unsigned 32-bit integer");
|
|
30805
30805
|
const self = Object.create(InetV6Proto);
|
|
@@ -31552,7 +31552,7 @@ const Number$1 = /*#__PURE__*/ make$34(number);
|
|
|
31552
31552
|
* @category schemas
|
|
31553
31553
|
* @since 4.0.0
|
|
31554
31554
|
*/
|
|
31555
|
-
const Boolean$
|
|
31555
|
+
const Boolean$2 = /*#__PURE__*/ make$34(boolean);
|
|
31556
31556
|
/**
|
|
31557
31557
|
* Schema for a TypeScript `void` return value.
|
|
31558
31558
|
*
|
|
@@ -31761,7 +31761,7 @@ const FalseLiterals = /*#__PURE__*/ Literals([
|
|
|
31761
31761
|
"n"
|
|
31762
31762
|
]);
|
|
31763
31763
|
/** @internal */
|
|
31764
|
-
const BooleanLiterals = /*#__PURE__*/ Literals([...TrueLiterals.literals, ...FalseLiterals.literals]).pipe(/*#__PURE__*/ decodeTo(Boolean$
|
|
31764
|
+
const BooleanLiterals = /*#__PURE__*/ Literals([...TrueLiterals.literals, ...FalseLiterals.literals]).pipe(/*#__PURE__*/ decodeTo(Boolean$2, /*#__PURE__*/ transform({
|
|
31765
31765
|
decode: (value) => value === "true" || value === "yes" || value === "on" || value === "1" || value === "y",
|
|
31766
31766
|
encode: (value) => value ? "true" : "false"
|
|
31767
31767
|
})));
|
|
@@ -38187,7 +38187,7 @@ function Int(name) {
|
|
|
38187
38187
|
* @category constructors
|
|
38188
38188
|
* @since 2.0.0
|
|
38189
38189
|
*/
|
|
38190
|
-
function Boolean(name) {
|
|
38190
|
+
function Boolean$1(name) {
|
|
38191
38191
|
return schema(BooleanLiterals, name);
|
|
38192
38192
|
}
|
|
38193
38193
|
//#endregion
|
|
@@ -46583,7 +46583,7 @@ const codeByLiteral = {
|
|
|
46583
46583
|
const fromLiteral = (literal) => codeByLiteral[literal];
|
|
46584
46584
|
//#endregion
|
|
46585
46585
|
//#region package.json
|
|
46586
|
-
var version$1 = "0.1.
|
|
46586
|
+
var version$1 = "0.1.1";
|
|
46587
46587
|
/**
|
|
46588
46588
|
* Default Server-Sent Events encoder.
|
|
46589
46589
|
*
|
|
@@ -48740,7 +48740,7 @@ const Concept = Struct({
|
|
|
48740
48740
|
estimatedMinutes: Number$1,
|
|
48741
48741
|
difficulty: Number$1,
|
|
48742
48742
|
depth: Number$1,
|
|
48743
|
-
isGoal: Boolean$
|
|
48743
|
+
isGoal: Boolean$2,
|
|
48744
48744
|
sourceIds: ArraySchema(SourceId)
|
|
48745
48745
|
});
|
|
48746
48746
|
/** `from` is the prerequisite, `to` is the concept that depends on it. */
|
|
@@ -49031,7 +49031,7 @@ const ReviewResult = Struct({
|
|
|
49031
49031
|
nextDueAt: Number$1,
|
|
49032
49032
|
nextDueInHours: Number$1,
|
|
49033
49033
|
/** Whether this attempt updated the memory model (first attempt of the day). */
|
|
49034
|
-
counted: Boolean$
|
|
49034
|
+
counted: Boolean$2,
|
|
49035
49035
|
/** Prerequisites whose next review was postponed by this success. */
|
|
49036
49036
|
creditedConcepts: ArraySchema(String$2),
|
|
49037
49037
|
/** After a lapse: quick checks on the weakest prerequisites (R3.3). */
|
|
@@ -49044,7 +49044,7 @@ const ReplacementAnswer = Struct({
|
|
|
49044
49044
|
goalId: GoalId,
|
|
49045
49045
|
conceptId: ConceptId,
|
|
49046
49046
|
verdict: Verdict,
|
|
49047
|
-
isMultipleChoice: Boolean$
|
|
49047
|
+
isMultipleChoice: Boolean$2
|
|
49048
49048
|
});
|
|
49049
49049
|
const ReplacementSummary = Struct({
|
|
49050
49050
|
/** Answered well: processed as successful reviews. */
|
|
@@ -49097,7 +49097,7 @@ const StreakInfo = Struct({
|
|
|
49097
49097
|
freezesUsed: Number$1,
|
|
49098
49098
|
freezesLeft: Number$1,
|
|
49099
49099
|
/** A freeze kept the streak alive within the last week. */
|
|
49100
|
-
isSavedByFreeze: Boolean$
|
|
49100
|
+
isSavedByFreeze: Boolean$2
|
|
49101
49101
|
});
|
|
49102
49102
|
const Today = Struct({
|
|
49103
49103
|
plan: DayPlan,
|
|
@@ -49312,7 +49312,7 @@ const TeachBackResult = Struct({
|
|
|
49312
49312
|
});
|
|
49313
49313
|
const LessonOutcome = Struct({
|
|
49314
49314
|
mastery: Mastery,
|
|
49315
|
-
criterionMet: Boolean$
|
|
49315
|
+
criterionMet: Boolean$2,
|
|
49316
49316
|
maybeNextReviewAt: OptionFromNullOr(Number$1),
|
|
49317
49317
|
message: String$2
|
|
49318
49318
|
});
|
|
@@ -49329,7 +49329,7 @@ const AnswerContext = Literals([
|
|
|
49329
49329
|
const AskTutorPayload = Struct({
|
|
49330
49330
|
question: String$2,
|
|
49331
49331
|
stepContext: String$2,
|
|
49332
|
-
isGradedStep: Boolean$
|
|
49332
|
+
isGradedStep: Boolean$2,
|
|
49333
49333
|
history: ArraySchema(Struct({
|
|
49334
49334
|
role: Literals(["Learner", "Tutor"]),
|
|
49335
49335
|
text: String$2
|
|
@@ -49364,7 +49364,7 @@ const PlacementBelief = Struct({
|
|
|
49364
49364
|
"ToLearn"
|
|
49365
49365
|
]),
|
|
49366
49366
|
/** True when the learner answered this concept directly. */
|
|
49367
|
-
wasAsked: Boolean$
|
|
49367
|
+
wasAsked: Boolean$2
|
|
49368
49368
|
});
|
|
49369
49369
|
const PlacementAnswerReview = Struct({
|
|
49370
49370
|
conceptId: ConceptId,
|
|
@@ -49390,8 +49390,8 @@ const PlacementView = Union([
|
|
|
49390
49390
|
conceptTitle: String$2,
|
|
49391
49391
|
number: Number$1,
|
|
49392
49392
|
questionsLeft: Number$1,
|
|
49393
|
-
canFinishEarly: Boolean$
|
|
49394
|
-
isWarmup: Boolean$
|
|
49393
|
+
canFinishEarly: Boolean$2,
|
|
49394
|
+
isWarmup: Boolean$2
|
|
49395
49395
|
}),
|
|
49396
49396
|
TaggedStruct("Summary", {
|
|
49397
49397
|
beliefs: ArraySchema(PlacementBelief),
|
|
@@ -49461,8 +49461,8 @@ const Calibration = Struct({
|
|
|
49461
49461
|
readings: ArraySchema(CalibrationReading),
|
|
49462
49462
|
/** Fewer answers than this in a bucket are too few to read. */
|
|
49463
49463
|
minimumAnswers: Number$1,
|
|
49464
|
-
isOverconfident: Boolean$
|
|
49465
|
-
isUnderconfident: Boolean$
|
|
49464
|
+
isOverconfident: Boolean$2,
|
|
49465
|
+
isUnderconfident: Boolean$2
|
|
49466
49466
|
});
|
|
49467
49467
|
const Activity = Struct({
|
|
49468
49468
|
/** Whole weeks from a Sunday, oldest first, ending today. */
|
|
@@ -49538,7 +49538,7 @@ const ModelChoice = Struct({
|
|
|
49538
49538
|
/** An alias that follows the newest version (`opus`) or a pinned model id. */
|
|
49539
49539
|
model: String$2,
|
|
49540
49540
|
reasoning: Reasoning,
|
|
49541
|
-
fastMode: Boolean$
|
|
49541
|
+
fastMode: Boolean$2
|
|
49542
49542
|
});
|
|
49543
49543
|
const ModelChoices = Struct({
|
|
49544
49544
|
deep: ModelChoice,
|
|
@@ -49552,16 +49552,16 @@ const ModelOption = Struct({
|
|
|
49552
49552
|
displayName: String$2,
|
|
49553
49553
|
description: String$2,
|
|
49554
49554
|
/** Aliases like `opus` move to each new version; ids stay pinned. */
|
|
49555
|
-
isLatest: Boolean$
|
|
49555
|
+
isLatest: Boolean$2,
|
|
49556
49556
|
reasoningLevels: ArraySchema(EffortLevel),
|
|
49557
|
-
supportsFastMode: Boolean$
|
|
49557
|
+
supportsFastMode: Boolean$2
|
|
49558
49558
|
});
|
|
49559
49559
|
/**
|
|
49560
49560
|
* A daily nudge at a fixed time (R14.6: a stable cue helps habits form).
|
|
49561
49561
|
* Shown as a browser notification while BaviTutor is open in a tab.
|
|
49562
49562
|
*/
|
|
49563
49563
|
const Reminder = Struct({
|
|
49564
|
-
isOn: Boolean$
|
|
49564
|
+
isOn: Boolean$2,
|
|
49565
49565
|
/** Local time of day, "HH:MM". */
|
|
49566
49566
|
time: String$2
|
|
49567
49567
|
});
|
|
@@ -49579,7 +49579,7 @@ const Settings = Struct({
|
|
|
49579
49579
|
desiredRetention: Number$1,
|
|
49580
49580
|
/** Hard cap on new concepts per day: 3 by default, 5 for "intensive" (R12). */
|
|
49581
49581
|
maxNewLessonsPerDay: Number$1,
|
|
49582
|
-
showVisuals: Boolean$
|
|
49582
|
+
showVisuals: Boolean$2,
|
|
49583
49583
|
reminder: Reminder,
|
|
49584
49584
|
models: ModelChoices
|
|
49585
49585
|
});
|
|
@@ -53464,8 +53464,8 @@ const askSdk = tryPromise({
|
|
|
53464
53464
|
const ask = async (attempt, best) => {
|
|
53465
53465
|
const models = await session.supportedModels();
|
|
53466
53466
|
const better = models.length >= best.length ? models : best;
|
|
53467
|
-
if (isComplete(better) || attempt >=
|
|
53468
|
-
await new Promise((done) => setTimeout(done,
|
|
53467
|
+
if (isComplete(better) || attempt >= 5) return better;
|
|
53468
|
+
await new Promise((done) => setTimeout(done, 2e3));
|
|
53469
53469
|
return ask(attempt + 1, better);
|
|
53470
53470
|
};
|
|
53471
53471
|
return await ask(1, []);
|
|
@@ -57715,7 +57715,7 @@ const AdapterChoice = Struct({
|
|
|
57715
57715
|
})).annotate({ description: "Achieve the Core coherence map (K-12 maths prerequisite graph). Use for school maths goals; null otherwise" }),
|
|
57716
57716
|
openStaxBooks: ArraySchema(String$2).annotate({ description: "OpenStax textbook titles to read the table of contents of (0-2), e.g. [\"Algebra 1\", \"Prealgebra 2e\"]" }),
|
|
57717
57717
|
exercismTracks: ArraySchema(String$2).annotate({ description: "Exercism language track slugs (0-2), e.g. [\"python\"], for programming-language goals" }),
|
|
57718
|
-
ossu: Boolean$
|
|
57718
|
+
ossu: Boolean$2.annotate({ description: "read the OSSU computer-science curriculum (CS goals)" }),
|
|
57719
57719
|
ocwCourses: ArraySchema(String$2).annotate({ description: "MIT OpenCourseWare course numbers or exact titles you are confident exist (0-2), e.g. [\"6.100L\"]" }),
|
|
57720
57720
|
roadmapSh: ArraySchema(String$2).annotate({ description: "roadmap.sh roadmap slugs (0-2) for software and tech goals, the goal's own roadmap first, e.g. [\"backend\"], [\"react\"], [\"docker\"]" })
|
|
57721
57721
|
});
|
|
@@ -57764,7 +57764,7 @@ const DiscoveryReport = Struct({
|
|
|
57764
57764
|
scores: JudgedScores,
|
|
57765
57765
|
fit: Confidence.annotate({ description: "goal fit: 0, 0.25, 0.5, 0.75 or 1" }),
|
|
57766
57766
|
flags: ArraySchema(GraderFlag),
|
|
57767
|
-
complete: Boolean$
|
|
57767
|
+
complete: Boolean$2.annotate({ description: "false if you could only read part of the outline" }),
|
|
57768
57768
|
topics: ArraySchema(SourceTopic).annotate({ description: "the outline in the order the source teaches it (up to 60 items)" })
|
|
57769
57769
|
})),
|
|
57770
57770
|
gaps: String$2.annotate({ description: "one sentence on what you looked for but could not find" })
|
|
@@ -57796,7 +57796,7 @@ const DraftConcept = Struct({
|
|
|
57796
57796
|
objectives: ArraySchema(String$2).annotate({ description: "2-3 testable objectives starting with a verb (\"Solve…\", \"Explain…\")" }),
|
|
57797
57797
|
estimatedMinutes: Int$1,
|
|
57798
57798
|
difficulty: Score,
|
|
57799
|
-
isGoal: Boolean$
|
|
57799
|
+
isGoal: Boolean$2.annotate({ description: "true for the concepts that together constitute reaching the learner goal" }),
|
|
57800
57800
|
sourceRefs: ArraySchema(String$2).annotate({ description: "source tags like \"S1\" that teach it" }),
|
|
57801
57801
|
prerequisites: ArraySchema(DraftPrerequisite).annotate({ description: "direct prerequisites only (keys of other concepts), never transitive ones" })
|
|
57802
57802
|
});
|
|
@@ -58480,7 +58480,7 @@ const Difficulty = Int$1.check(isBetween$1({
|
|
|
58480
58480
|
}));
|
|
58481
58481
|
const DraftOption = Struct({
|
|
58482
58482
|
text: String$2.annotate({ description: "a bare claim or value, no justification, same grammatical skeleton and length as the other options" }),
|
|
58483
|
-
isCorrect: Boolean$
|
|
58483
|
+
isCorrect: Boolean$2,
|
|
58484
58484
|
misconception: String$2.annotate({ description: "for a distractor: the specific misconception that produces it (one short phrase). Empty for the correct option" })
|
|
58485
58485
|
});
|
|
58486
58486
|
const DiagnosticBatch = Struct({ questions: ArraySchema(Struct({
|
|
@@ -58998,7 +58998,7 @@ const decodeFrameEvent = decodeUnknownOption(Union([
|
|
|
58998
58998
|
index: Number$1,
|
|
58999
58999
|
count: Number$1,
|
|
59000
59000
|
caption: String$2,
|
|
59001
|
-
playing: Boolean$
|
|
59001
|
+
playing: Boolean$2
|
|
59002
59002
|
}),
|
|
59003
59003
|
Struct({
|
|
59004
59004
|
type: Literal("bv:audit"),
|
|
@@ -59392,7 +59392,7 @@ var Visuals = class Visuals extends Service()("bavitutor/Visuals") {
|
|
|
59392
59392
|
const claude = yield* Claude;
|
|
59393
59393
|
const renderer = yield* Renderer;
|
|
59394
59394
|
const goals = yield* Goals;
|
|
59395
|
-
const isEnabled = yield* Boolean("BAVI_VISUALS").pipe(withDefault$1(true));
|
|
59395
|
+
const isEnabled = yield* Boolean$1("BAVI_VISUALS").pipe(withDefault$1(true));
|
|
59396
59396
|
const running = yield* make$18();
|
|
59397
59397
|
const slots = yield* make$50(CONCURRENT_GENERATIONS);
|
|
59398
59398
|
const visualRow = (goalId, visualId) => sql`
|
|
@@ -61874,13 +61874,57 @@ const isBaviTutor = async (port) => {
|
|
|
61874
61874
|
}
|
|
61875
61875
|
};
|
|
61876
61876
|
/**
|
|
61877
|
-
*
|
|
61877
|
+
* Whether something already listens on the port. Also probes loopback: on
|
|
61878
61878
|
* Windows a second server can bind 127.0.0.1 next to one on all interfaces.
|
|
61879
61879
|
*/
|
|
61880
|
-
const
|
|
61880
|
+
const isPortTaken = async (port, host) => {
|
|
61881
61881
|
const error = await canListen(port, host);
|
|
61882
|
-
if (error
|
|
61883
|
-
|
|
61882
|
+
if (error !== void 0 && error.code !== "EADDRINUSE") fail([`Can't listen on ${host}:${port}: ${error.message}`]);
|
|
61883
|
+
return error !== void 0 || await answers(port, "127.0.0.1") || await answers(port, "::1");
|
|
61884
|
+
};
|
|
61885
|
+
/** A whole BaviTutor (web app included) is serving on this port already. */
|
|
61886
|
+
const servesBaviTutorApp = async (port) => {
|
|
61887
|
+
try {
|
|
61888
|
+
const response = await fetch(`http://127.0.0.1:${port}/`, {
|
|
61889
|
+
headers: { accept: "text/html" },
|
|
61890
|
+
signal: AbortSignal.timeout(1500)
|
|
61891
|
+
});
|
|
61892
|
+
return response.ok && (await response.text()).includes("BaviTutor");
|
|
61893
|
+
} catch {
|
|
61894
|
+
return false;
|
|
61895
|
+
}
|
|
61896
|
+
};
|
|
61897
|
+
const SPARE_PORTS = 20;
|
|
61898
|
+
/**
|
|
61899
|
+
* The port to serve on. A port the learner asked for must be free. The
|
|
61900
|
+
* default moves on to the next free one, unless a BaviTutor app already runs
|
|
61901
|
+
* on one of them, which is then opened instead of starting a second copy.
|
|
61902
|
+
*/
|
|
61903
|
+
const choosePort = async (requested, isExplicit, host) => {
|
|
61904
|
+
if (!await isPortTaken(requested, host)) return {
|
|
61905
|
+
port: requested,
|
|
61906
|
+
isRunning: false
|
|
61907
|
+
};
|
|
61908
|
+
if (await servesBaviTutorApp(requested)) return {
|
|
61909
|
+
port: requested,
|
|
61910
|
+
isRunning: true
|
|
61911
|
+
};
|
|
61912
|
+
if (isExplicit) return fail([`Port ${requested} is already in use${await isBaviTutor(requested) ? " by a BaviTutor server" : ""}.`, `Pick another one: bavitutor --port ${requested === 65535 ? requested - 1 : requested + 1}`]);
|
|
61913
|
+
const candidates = Array.from({ length: SPARE_PORTS }, (_, offset) => requested + 1 + offset).filter((candidate) => candidate <= 65535);
|
|
61914
|
+
for (const candidate of candidates) {
|
|
61915
|
+
if (!await isPortTaken(candidate, host)) {
|
|
61916
|
+
say(` port ${requested} is busy, so using ${candidate}`);
|
|
61917
|
+
return {
|
|
61918
|
+
port: candidate,
|
|
61919
|
+
isRunning: false
|
|
61920
|
+
};
|
|
61921
|
+
}
|
|
61922
|
+
if (await servesBaviTutorApp(candidate)) return {
|
|
61923
|
+
port: candidate,
|
|
61924
|
+
isRunning: true
|
|
61925
|
+
};
|
|
61926
|
+
}
|
|
61927
|
+
return fail([`Ports ${requested}-${requested + SPARE_PORTS} are all in use.`, "Pick a free one: bavitutor --port <port>"]);
|
|
61884
61928
|
};
|
|
61885
61929
|
const openBrowser = (url) => {
|
|
61886
61930
|
const [command, args] = process.platform === "win32" ? ["cmd.exe", [
|
|
@@ -61917,16 +61961,17 @@ const main = async () => {
|
|
|
61917
61961
|
mkdirSync(dataDir, { recursive: true });
|
|
61918
61962
|
const envFile = join(dataDir, ".env");
|
|
61919
61963
|
if (existsSync(envFile)) process.loadEnvFile(envFile);
|
|
61920
|
-
const
|
|
61921
|
-
process.env["PORT"]
|
|
61964
|
+
const isExplicitPort = options.port !== void 0 || Boolean(process.env["PORT"]);
|
|
61965
|
+
const requestedPort = options.port !== void 0 ? parsePort(options.port, "--port") : process.env["PORT"] ? parsePort(process.env["PORT"], "PORT") : DEFAULT_PORT;
|
|
61922
61966
|
const host = options.host ?? DEFAULT_HOST;
|
|
61923
|
-
const
|
|
61967
|
+
const isLocal = [
|
|
61924
61968
|
"127.0.0.1",
|
|
61925
61969
|
"localhost",
|
|
61926
61970
|
"::1",
|
|
61927
61971
|
"0.0.0.0",
|
|
61928
61972
|
"::"
|
|
61929
|
-
].includes(host)
|
|
61973
|
+
].includes(host);
|
|
61974
|
+
const urlFor = (port) => `http://${isLocal ? "localhost" : host}:${port}`;
|
|
61930
61975
|
const staticDir = fileURLToPath(new URL("web", import.meta.url));
|
|
61931
61976
|
if (!existsSync(join(staticDir, "index.html"))) fail([`The web app isn't built (no ${join(staticDir, "index.html")}).`, "Build it from the repository with `vp run build`."]);
|
|
61932
61977
|
say(`BaviTutor ${version$1}`);
|
|
@@ -61948,7 +61993,16 @@ const main = async () => {
|
|
|
61948
61993
|
]);
|
|
61949
61994
|
else say(` visuals ${chrome}`);
|
|
61950
61995
|
}
|
|
61951
|
-
await
|
|
61996
|
+
const choice = await choosePort(requestedPort, isExplicitPort, host);
|
|
61997
|
+
if (choice.isRunning) {
|
|
61998
|
+
say();
|
|
61999
|
+
say(`BaviTutor is already running at ${urlFor(choice.port)}`);
|
|
62000
|
+
if (options.open) openBrowser(urlFor(choice.port));
|
|
62001
|
+
return;
|
|
62002
|
+
}
|
|
62003
|
+
const port = choice.port;
|
|
62004
|
+
process.env["PORT"] = String(port);
|
|
62005
|
+
const url = urlFor(port);
|
|
61952
62006
|
effectDiscard(gen(function* () {
|
|
61953
62007
|
yield* HttpServer;
|
|
61954
62008
|
say();
|