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.
Files changed (2) hide show
  1. package/dist/bavitutor.mjs +96 -42
  2. package/package.json +1 -1
@@ -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$2 = class extends ASTNodeImpl {
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$2();
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$1 = (port) => Number.isInteger(port) && port >= 0 && port <= 65535;
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$1(port)) return addressError(address, "port must be an integer from 0 through 65535");
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$1(port)) return addressError(address, "port must be an integer from 0 through 65535");
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$1 = /*#__PURE__*/ make$34(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$1, /*#__PURE__*/ transform({
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.0";
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$1,
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$1,
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$1
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$1
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$1,
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$1,
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$1
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$1,
49394
- isWarmup: Boolean$1
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$1,
49465
- isUnderconfident: Boolean$1
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$1
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$1,
49555
+ isLatest: Boolean$2,
49556
49556
  reasoningLevels: ArraySchema(EffortLevel),
49557
- supportsFastMode: Boolean$1
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$1,
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$1,
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 >= 3) return better;
53468
- await new Promise((done) => setTimeout(done, 1500));
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$1.annotate({ description: "read the OSSU computer-science curriculum (CS goals)" }),
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$1.annotate({ description: "false if you could only read part of the outline" }),
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$1.annotate({ description: "true for the concepts that together constitute reaching the learner goal" }),
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$1,
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$1
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
- * Fails with a clear message if the port is taken. Also probes loopback: on
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 checkPort = async (port, host) => {
61880
+ const isPortTaken = async (port, host) => {
61881
61881
  const error = await canListen(port, host);
61882
- if (error?.code === "EADDRINUSE" || error === void 0 && (await answers(port, "127.0.0.1") || await answers(port, "::1"))) fail([`Port ${port} is already in use${await isBaviTutor(port) ? " by a BaviTutor server" : ""}.`, `Pick another one: bavitutor --port ${port === 65535 ? port - 1 : port + 1}`]);
61883
- if (error !== void 0) fail([`Can't listen on ${host}:${port}: ${error.message}`]);
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 port = options.port !== void 0 ? parsePort(options.port, "--port") : process.env["PORT"] ? parsePort(process.env["PORT"], "PORT") : DEFAULT_PORT;
61921
- process.env["PORT"] = String(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 url = `http://${[
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) ? "localhost" : host}:${port}`;
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 checkPort(port, host);
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bavitutor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Describe what you want to learn; BaviTutor researches it, maps it and teaches it.",
5
5
  "keywords": [
6
6
  "claude",