bavitutor 0.1.0 → 0.1.2
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/README.md +7 -1
- package/dist/bavitutor.mjs +135 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,13 @@ npx bavitutor
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
That's all: it downloads BaviTutor, serves it on <http://localhost:4100> and
|
|
19
|
-
opens your browser. Data stays on your machine in `~/.bavitutor`.
|
|
19
|
+
opens your browser. Data stays on your machine in `~/.bavitutor`. If 4100 is
|
|
20
|
+
taken it moves to the next free port, and if BaviTutor is already running it
|
|
21
|
+
opens that one.
|
|
22
|
+
|
|
23
|
+
npx and vpx reuse a version they downloaded before (pnpm for a day), so when a
|
|
24
|
+
newer one is out BaviTutor says so on start, with the exact command:
|
|
25
|
+
`npx bavitutor@<version>` always fetches that version.
|
|
20
26
|
|
|
21
27
|
Requirements: Node 24+, [Vite+](https://viteplus.dev) (`vp`), and a logged-in
|
|
22
28
|
[Claude Code](https://claude.com/claude-code) install (the Claude Agent SDK
|
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,8 @@ const codeByLiteral = {
|
|
|
46583
46583
|
const fromLiteral = (literal) => codeByLiteral[literal];
|
|
46584
46584
|
//#endregion
|
|
46585
46585
|
//#region package.json
|
|
46586
|
-
var
|
|
46586
|
+
var name = "bavitutor";
|
|
46587
|
+
var version$1 = "0.1.2";
|
|
46587
46588
|
/**
|
|
46588
46589
|
* Default Server-Sent Events encoder.
|
|
46589
46590
|
*
|
|
@@ -48740,7 +48741,7 @@ const Concept = Struct({
|
|
|
48740
48741
|
estimatedMinutes: Number$1,
|
|
48741
48742
|
difficulty: Number$1,
|
|
48742
48743
|
depth: Number$1,
|
|
48743
|
-
isGoal: Boolean$
|
|
48744
|
+
isGoal: Boolean$2,
|
|
48744
48745
|
sourceIds: ArraySchema(SourceId)
|
|
48745
48746
|
});
|
|
48746
48747
|
/** `from` is the prerequisite, `to` is the concept that depends on it. */
|
|
@@ -49031,7 +49032,7 @@ const ReviewResult = Struct({
|
|
|
49031
49032
|
nextDueAt: Number$1,
|
|
49032
49033
|
nextDueInHours: Number$1,
|
|
49033
49034
|
/** Whether this attempt updated the memory model (first attempt of the day). */
|
|
49034
|
-
counted: Boolean$
|
|
49035
|
+
counted: Boolean$2,
|
|
49035
49036
|
/** Prerequisites whose next review was postponed by this success. */
|
|
49036
49037
|
creditedConcepts: ArraySchema(String$2),
|
|
49037
49038
|
/** After a lapse: quick checks on the weakest prerequisites (R3.3). */
|
|
@@ -49044,7 +49045,7 @@ const ReplacementAnswer = Struct({
|
|
|
49044
49045
|
goalId: GoalId,
|
|
49045
49046
|
conceptId: ConceptId,
|
|
49046
49047
|
verdict: Verdict,
|
|
49047
|
-
isMultipleChoice: Boolean$
|
|
49048
|
+
isMultipleChoice: Boolean$2
|
|
49048
49049
|
});
|
|
49049
49050
|
const ReplacementSummary = Struct({
|
|
49050
49051
|
/** Answered well: processed as successful reviews. */
|
|
@@ -49097,7 +49098,7 @@ const StreakInfo = Struct({
|
|
|
49097
49098
|
freezesUsed: Number$1,
|
|
49098
49099
|
freezesLeft: Number$1,
|
|
49099
49100
|
/** A freeze kept the streak alive within the last week. */
|
|
49100
|
-
isSavedByFreeze: Boolean$
|
|
49101
|
+
isSavedByFreeze: Boolean$2
|
|
49101
49102
|
});
|
|
49102
49103
|
const Today = Struct({
|
|
49103
49104
|
plan: DayPlan,
|
|
@@ -49312,7 +49313,7 @@ const TeachBackResult = Struct({
|
|
|
49312
49313
|
});
|
|
49313
49314
|
const LessonOutcome = Struct({
|
|
49314
49315
|
mastery: Mastery,
|
|
49315
|
-
criterionMet: Boolean$
|
|
49316
|
+
criterionMet: Boolean$2,
|
|
49316
49317
|
maybeNextReviewAt: OptionFromNullOr(Number$1),
|
|
49317
49318
|
message: String$2
|
|
49318
49319
|
});
|
|
@@ -49329,7 +49330,7 @@ const AnswerContext = Literals([
|
|
|
49329
49330
|
const AskTutorPayload = Struct({
|
|
49330
49331
|
question: String$2,
|
|
49331
49332
|
stepContext: String$2,
|
|
49332
|
-
isGradedStep: Boolean$
|
|
49333
|
+
isGradedStep: Boolean$2,
|
|
49333
49334
|
history: ArraySchema(Struct({
|
|
49334
49335
|
role: Literals(["Learner", "Tutor"]),
|
|
49335
49336
|
text: String$2
|
|
@@ -49364,7 +49365,7 @@ const PlacementBelief = Struct({
|
|
|
49364
49365
|
"ToLearn"
|
|
49365
49366
|
]),
|
|
49366
49367
|
/** True when the learner answered this concept directly. */
|
|
49367
|
-
wasAsked: Boolean$
|
|
49368
|
+
wasAsked: Boolean$2
|
|
49368
49369
|
});
|
|
49369
49370
|
const PlacementAnswerReview = Struct({
|
|
49370
49371
|
conceptId: ConceptId,
|
|
@@ -49390,8 +49391,8 @@ const PlacementView = Union([
|
|
|
49390
49391
|
conceptTitle: String$2,
|
|
49391
49392
|
number: Number$1,
|
|
49392
49393
|
questionsLeft: Number$1,
|
|
49393
|
-
canFinishEarly: Boolean$
|
|
49394
|
-
isWarmup: Boolean$
|
|
49394
|
+
canFinishEarly: Boolean$2,
|
|
49395
|
+
isWarmup: Boolean$2
|
|
49395
49396
|
}),
|
|
49396
49397
|
TaggedStruct("Summary", {
|
|
49397
49398
|
beliefs: ArraySchema(PlacementBelief),
|
|
@@ -49461,8 +49462,8 @@ const Calibration = Struct({
|
|
|
49461
49462
|
readings: ArraySchema(CalibrationReading),
|
|
49462
49463
|
/** Fewer answers than this in a bucket are too few to read. */
|
|
49463
49464
|
minimumAnswers: Number$1,
|
|
49464
|
-
isOverconfident: Boolean$
|
|
49465
|
-
isUnderconfident: Boolean$
|
|
49465
|
+
isOverconfident: Boolean$2,
|
|
49466
|
+
isUnderconfident: Boolean$2
|
|
49466
49467
|
});
|
|
49467
49468
|
const Activity = Struct({
|
|
49468
49469
|
/** Whole weeks from a Sunday, oldest first, ending today. */
|
|
@@ -49538,7 +49539,7 @@ const ModelChoice = Struct({
|
|
|
49538
49539
|
/** An alias that follows the newest version (`opus`) or a pinned model id. */
|
|
49539
49540
|
model: String$2,
|
|
49540
49541
|
reasoning: Reasoning,
|
|
49541
|
-
fastMode: Boolean$
|
|
49542
|
+
fastMode: Boolean$2
|
|
49542
49543
|
});
|
|
49543
49544
|
const ModelChoices = Struct({
|
|
49544
49545
|
deep: ModelChoice,
|
|
@@ -49552,16 +49553,16 @@ const ModelOption = Struct({
|
|
|
49552
49553
|
displayName: String$2,
|
|
49553
49554
|
description: String$2,
|
|
49554
49555
|
/** Aliases like `opus` move to each new version; ids stay pinned. */
|
|
49555
|
-
isLatest: Boolean$
|
|
49556
|
+
isLatest: Boolean$2,
|
|
49556
49557
|
reasoningLevels: ArraySchema(EffortLevel),
|
|
49557
|
-
supportsFastMode: Boolean$
|
|
49558
|
+
supportsFastMode: Boolean$2
|
|
49558
49559
|
});
|
|
49559
49560
|
/**
|
|
49560
49561
|
* A daily nudge at a fixed time (R14.6: a stable cue helps habits form).
|
|
49561
49562
|
* Shown as a browser notification while BaviTutor is open in a tab.
|
|
49562
49563
|
*/
|
|
49563
49564
|
const Reminder = Struct({
|
|
49564
|
-
isOn: Boolean$
|
|
49565
|
+
isOn: Boolean$2,
|
|
49565
49566
|
/** Local time of day, "HH:MM". */
|
|
49566
49567
|
time: String$2
|
|
49567
49568
|
});
|
|
@@ -49579,7 +49580,7 @@ const Settings = Struct({
|
|
|
49579
49580
|
desiredRetention: Number$1,
|
|
49580
49581
|
/** Hard cap on new concepts per day: 3 by default, 5 for "intensive" (R12). */
|
|
49581
49582
|
maxNewLessonsPerDay: Number$1,
|
|
49582
|
-
showVisuals: Boolean$
|
|
49583
|
+
showVisuals: Boolean$2,
|
|
49583
49584
|
reminder: Reminder,
|
|
49584
49585
|
models: ModelChoices
|
|
49585
49586
|
});
|
|
@@ -53464,8 +53465,8 @@ const askSdk = tryPromise({
|
|
|
53464
53465
|
const ask = async (attempt, best) => {
|
|
53465
53466
|
const models = await session.supportedModels();
|
|
53466
53467
|
const better = models.length >= best.length ? models : best;
|
|
53467
|
-
if (isComplete(better) || attempt >=
|
|
53468
|
-
await new Promise((done) => setTimeout(done,
|
|
53468
|
+
if (isComplete(better) || attempt >= 5) return better;
|
|
53469
|
+
await new Promise((done) => setTimeout(done, 2e3));
|
|
53469
53470
|
return ask(attempt + 1, better);
|
|
53470
53471
|
};
|
|
53471
53472
|
return await ask(1, []);
|
|
@@ -57715,7 +57716,7 @@ const AdapterChoice = Struct({
|
|
|
57715
57716
|
})).annotate({ description: "Achieve the Core coherence map (K-12 maths prerequisite graph). Use for school maths goals; null otherwise" }),
|
|
57716
57717
|
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
57718
|
exercismTracks: ArraySchema(String$2).annotate({ description: "Exercism language track slugs (0-2), e.g. [\"python\"], for programming-language goals" }),
|
|
57718
|
-
ossu: Boolean$
|
|
57719
|
+
ossu: Boolean$2.annotate({ description: "read the OSSU computer-science curriculum (CS goals)" }),
|
|
57719
57720
|
ocwCourses: ArraySchema(String$2).annotate({ description: "MIT OpenCourseWare course numbers or exact titles you are confident exist (0-2), e.g. [\"6.100L\"]" }),
|
|
57720
57721
|
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
57722
|
});
|
|
@@ -57764,7 +57765,7 @@ const DiscoveryReport = Struct({
|
|
|
57764
57765
|
scores: JudgedScores,
|
|
57765
57766
|
fit: Confidence.annotate({ description: "goal fit: 0, 0.25, 0.5, 0.75 or 1" }),
|
|
57766
57767
|
flags: ArraySchema(GraderFlag),
|
|
57767
|
-
complete: Boolean$
|
|
57768
|
+
complete: Boolean$2.annotate({ description: "false if you could only read part of the outline" }),
|
|
57768
57769
|
topics: ArraySchema(SourceTopic).annotate({ description: "the outline in the order the source teaches it (up to 60 items)" })
|
|
57769
57770
|
})),
|
|
57770
57771
|
gaps: String$2.annotate({ description: "one sentence on what you looked for but could not find" })
|
|
@@ -57796,7 +57797,7 @@ const DraftConcept = Struct({
|
|
|
57796
57797
|
objectives: ArraySchema(String$2).annotate({ description: "2-3 testable objectives starting with a verb (\"Solve…\", \"Explain…\")" }),
|
|
57797
57798
|
estimatedMinutes: Int$1,
|
|
57798
57799
|
difficulty: Score,
|
|
57799
|
-
isGoal: Boolean$
|
|
57800
|
+
isGoal: Boolean$2.annotate({ description: "true for the concepts that together constitute reaching the learner goal" }),
|
|
57800
57801
|
sourceRefs: ArraySchema(String$2).annotate({ description: "source tags like \"S1\" that teach it" }),
|
|
57801
57802
|
prerequisites: ArraySchema(DraftPrerequisite).annotate({ description: "direct prerequisites only (keys of other concepts), never transitive ones" })
|
|
57802
57803
|
});
|
|
@@ -58480,7 +58481,7 @@ const Difficulty = Int$1.check(isBetween$1({
|
|
|
58480
58481
|
}));
|
|
58481
58482
|
const DraftOption = Struct({
|
|
58482
58483
|
text: String$2.annotate({ description: "a bare claim or value, no justification, same grammatical skeleton and length as the other options" }),
|
|
58483
|
-
isCorrect: Boolean$
|
|
58484
|
+
isCorrect: Boolean$2,
|
|
58484
58485
|
misconception: String$2.annotate({ description: "for a distractor: the specific misconception that produces it (one short phrase). Empty for the correct option" })
|
|
58485
58486
|
});
|
|
58486
58487
|
const DiagnosticBatch = Struct({ questions: ArraySchema(Struct({
|
|
@@ -58998,7 +58999,7 @@ const decodeFrameEvent = decodeUnknownOption(Union([
|
|
|
58998
58999
|
index: Number$1,
|
|
58999
59000
|
count: Number$1,
|
|
59000
59001
|
caption: String$2,
|
|
59001
|
-
playing: Boolean$
|
|
59002
|
+
playing: Boolean$2
|
|
59002
59003
|
}),
|
|
59003
59004
|
Struct({
|
|
59004
59005
|
type: Literal("bv:audit"),
|
|
@@ -59392,7 +59393,7 @@ var Visuals = class Visuals extends Service()("bavitutor/Visuals") {
|
|
|
59392
59393
|
const claude = yield* Claude;
|
|
59393
59394
|
const renderer = yield* Renderer;
|
|
59394
59395
|
const goals = yield* Goals;
|
|
59395
|
-
const isEnabled = yield* Boolean("BAVI_VISUALS").pipe(withDefault$1(true));
|
|
59396
|
+
const isEnabled = yield* Boolean$1("BAVI_VISUALS").pipe(withDefault$1(true));
|
|
59396
59397
|
const running = yield* make$18();
|
|
59397
59398
|
const slots = yield* make$50(CONCURRENT_GENERATIONS);
|
|
59398
59399
|
const visualRow = (goalId, visualId) => sql`
|
|
@@ -61874,13 +61875,91 @@ const isBaviTutor = async (port) => {
|
|
|
61874
61875
|
}
|
|
61875
61876
|
};
|
|
61876
61877
|
/**
|
|
61877
|
-
*
|
|
61878
|
+
* Whether something already listens on the port. Also probes loopback: on
|
|
61878
61879
|
* Windows a second server can bind 127.0.0.1 next to one on all interfaces.
|
|
61879
61880
|
*/
|
|
61880
|
-
const
|
|
61881
|
+
const isPortTaken = async (port, host) => {
|
|
61881
61882
|
const error = await canListen(port, host);
|
|
61882
|
-
if (error
|
|
61883
|
-
|
|
61883
|
+
if (error !== void 0 && error.code !== "EADDRINUSE") fail([`Can't listen on ${host}:${port}: ${error.message}`]);
|
|
61884
|
+
return error !== void 0 || await answers(port, "127.0.0.1") || await answers(port, "::1");
|
|
61885
|
+
};
|
|
61886
|
+
/** A whole BaviTutor (web app included) is serving on this port already. */
|
|
61887
|
+
const servesBaviTutorApp = async (port) => {
|
|
61888
|
+
try {
|
|
61889
|
+
const response = await fetch(`http://127.0.0.1:${port}/`, {
|
|
61890
|
+
headers: { accept: "text/html" },
|
|
61891
|
+
signal: AbortSignal.timeout(1500)
|
|
61892
|
+
});
|
|
61893
|
+
return response.ok && (await response.text()).includes("BaviTutor");
|
|
61894
|
+
} catch {
|
|
61895
|
+
return false;
|
|
61896
|
+
}
|
|
61897
|
+
};
|
|
61898
|
+
const SPARE_PORTS = 20;
|
|
61899
|
+
/**
|
|
61900
|
+
* The port to serve on. A port the learner asked for must be free. The
|
|
61901
|
+
* default moves on to the next free one, unless a BaviTutor app already runs
|
|
61902
|
+
* on one of them, which is then opened instead of starting a second copy.
|
|
61903
|
+
*/
|
|
61904
|
+
const choosePort = async (requested, isExplicit, host) => {
|
|
61905
|
+
if (!await isPortTaken(requested, host)) return {
|
|
61906
|
+
port: requested,
|
|
61907
|
+
isRunning: false
|
|
61908
|
+
};
|
|
61909
|
+
if (await servesBaviTutorApp(requested)) return {
|
|
61910
|
+
port: requested,
|
|
61911
|
+
isRunning: true
|
|
61912
|
+
};
|
|
61913
|
+
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}`]);
|
|
61914
|
+
const candidates = Array.from({ length: SPARE_PORTS }, (_, offset) => requested + 1 + offset).filter((candidate) => candidate <= 65535);
|
|
61915
|
+
for (const candidate of candidates) {
|
|
61916
|
+
if (!await isPortTaken(candidate, host)) {
|
|
61917
|
+
say(` port ${requested} is busy, so using ${candidate}`);
|
|
61918
|
+
return {
|
|
61919
|
+
port: candidate,
|
|
61920
|
+
isRunning: false
|
|
61921
|
+
};
|
|
61922
|
+
}
|
|
61923
|
+
if (await servesBaviTutorApp(candidate)) return {
|
|
61924
|
+
port: candidate,
|
|
61925
|
+
isRunning: true
|
|
61926
|
+
};
|
|
61927
|
+
}
|
|
61928
|
+
return fail([`Ports ${requested}-${requested + SPARE_PORTS} are all in use.`, "Pick a free one: bavitutor --port <port>"]);
|
|
61929
|
+
};
|
|
61930
|
+
/** The version npm calls latest, or undefined when npm doesn't answer quickly. */
|
|
61931
|
+
const latestVersion = async () => {
|
|
61932
|
+
if (process.env["NO_UPDATE_NOTIFIER"]) return;
|
|
61933
|
+
try {
|
|
61934
|
+
const response = await fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(3e3) });
|
|
61935
|
+
const body = await response.json();
|
|
61936
|
+
return response.ok && body !== null && typeof body === "object" && "version" in body && typeof body.version === "string" ? body.version : void 0;
|
|
61937
|
+
} catch {
|
|
61938
|
+
return;
|
|
61939
|
+
}
|
|
61940
|
+
};
|
|
61941
|
+
const isNewer = (candidate, current) => {
|
|
61942
|
+
const parts = (version) => (version.split("-")[0] ?? "").split(".").map(Number);
|
|
61943
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
61944
|
+
const difference = [
|
|
61945
|
+
0,
|
|
61946
|
+
1,
|
|
61947
|
+
2
|
|
61948
|
+
].map((i) => (next[i] ?? 0) - (now[i] ?? 0)).find((delta) => delta !== 0);
|
|
61949
|
+
return difference !== void 0 && difference > 0;
|
|
61950
|
+
};
|
|
61951
|
+
/**
|
|
61952
|
+
* npx and vpx (through pnpm dlx) reuse what they downloaded before, so a bare
|
|
61953
|
+
* `npx bavitutor` can keep starting an old version. An exact version is
|
|
61954
|
+
* always fetched, so the notice names one.
|
|
61955
|
+
*/
|
|
61956
|
+
const sayIfOutdated = (latest) => {
|
|
61957
|
+
if (latest !== void 0 && isNewer(latest, version$1)) {
|
|
61958
|
+
say(`BaviTutor ${latest} is out (this is ${version$1}). To update,`);
|
|
61959
|
+
say(`stop this one and run: npx bavitutor@${latest}`);
|
|
61960
|
+
say(` or: vpx bavitutor@${latest}`);
|
|
61961
|
+
say();
|
|
61962
|
+
}
|
|
61884
61963
|
};
|
|
61885
61964
|
const openBrowser = (url) => {
|
|
61886
61965
|
const [command, args] = process.platform === "win32" ? ["cmd.exe", [
|
|
@@ -61913,20 +61992,22 @@ const main = async () => {
|
|
|
61913
61992
|
say(version$1);
|
|
61914
61993
|
return;
|
|
61915
61994
|
}
|
|
61995
|
+
const latest = latestVersion();
|
|
61916
61996
|
const dataDir = resolve(expandHome(options["data-dir"] ?? join(homedir(), ".bavitutor")));
|
|
61917
61997
|
mkdirSync(dataDir, { recursive: true });
|
|
61918
61998
|
const envFile = join(dataDir, ".env");
|
|
61919
61999
|
if (existsSync(envFile)) process.loadEnvFile(envFile);
|
|
61920
|
-
const
|
|
61921
|
-
process.env["PORT"]
|
|
62000
|
+
const isExplicitPort = options.port !== void 0 || Boolean(process.env["PORT"]);
|
|
62001
|
+
const requestedPort = options.port !== void 0 ? parsePort(options.port, "--port") : process.env["PORT"] ? parsePort(process.env["PORT"], "PORT") : DEFAULT_PORT;
|
|
61922
62002
|
const host = options.host ?? DEFAULT_HOST;
|
|
61923
|
-
const
|
|
62003
|
+
const isLocal = [
|
|
61924
62004
|
"127.0.0.1",
|
|
61925
62005
|
"localhost",
|
|
61926
62006
|
"::1",
|
|
61927
62007
|
"0.0.0.0",
|
|
61928
62008
|
"::"
|
|
61929
|
-
].includes(host)
|
|
62009
|
+
].includes(host);
|
|
62010
|
+
const urlFor = (port) => `http://${isLocal ? "localhost" : host}:${port}`;
|
|
61930
62011
|
const staticDir = fileURLToPath(new URL("web", import.meta.url));
|
|
61931
62012
|
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
62013
|
say(`BaviTutor ${version$1}`);
|
|
@@ -61948,13 +62029,25 @@ const main = async () => {
|
|
|
61948
62029
|
]);
|
|
61949
62030
|
else say(` visuals ${chrome}`);
|
|
61950
62031
|
}
|
|
61951
|
-
await
|
|
62032
|
+
const choice = await choosePort(requestedPort, isExplicitPort, host);
|
|
62033
|
+
if (choice.isRunning) {
|
|
62034
|
+
say();
|
|
62035
|
+
say(`BaviTutor is already running at ${urlFor(choice.port)}`);
|
|
62036
|
+
say();
|
|
62037
|
+
if (options.open) openBrowser(urlFor(choice.port));
|
|
62038
|
+
sayIfOutdated(await latest);
|
|
62039
|
+
return;
|
|
62040
|
+
}
|
|
62041
|
+
const port = choice.port;
|
|
62042
|
+
process.env["PORT"] = String(port);
|
|
62043
|
+
const url = urlFor(port);
|
|
61952
62044
|
effectDiscard(gen(function* () {
|
|
61953
62045
|
yield* HttpServer;
|
|
61954
62046
|
say();
|
|
61955
62047
|
say(`BaviTutor is running at ${url} (Ctrl+C to stop)`);
|
|
61956
62048
|
say();
|
|
61957
62049
|
if (options.open) openBrowser(url);
|
|
62050
|
+
latest.then(sayIfOutdated);
|
|
61958
62051
|
})).pipe(provide(makeServer({
|
|
61959
62052
|
port,
|
|
61960
62053
|
host,
|