timeback-studio 0.1.4 → 0.1.5

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/bin.js CHANGED
@@ -3148,7 +3148,7 @@ var {
3148
3148
  function isCancelled(value) {
3149
3149
  return typeof value === "symbol";
3150
3150
  }
3151
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/external.js
3151
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
3152
3152
  var exports_external = {};
3153
3153
  __export(exports_external, {
3154
3154
  xor: () => xor,
@@ -3389,7 +3389,7 @@ __export(exports_external, {
3389
3389
  $brand: () => $brand
3390
3390
  });
3391
3391
 
3392
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/index.js
3392
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/index.js
3393
3393
  var exports_core2 = {};
3394
3394
  __export(exports_core2, {
3395
3395
  version: () => version,
@@ -3667,7 +3667,7 @@ __export(exports_core2, {
3667
3667
  $ZodAny: () => $ZodAny
3668
3668
  });
3669
3669
 
3670
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/core.js
3670
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
3671
3671
  var NEVER = Object.freeze({
3672
3672
  status: "aborted"
3673
3673
  });
@@ -3743,7 +3743,7 @@ function config(newConfig) {
3743
3743
  Object.assign(globalConfig, newConfig);
3744
3744
  return globalConfig;
3745
3745
  }
3746
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/util.js
3746
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/util.js
3747
3747
  var exports_util = {};
3748
3748
  __export(exports_util, {
3749
3749
  unwrapMessage: () => unwrapMessage,
@@ -4417,7 +4417,7 @@ class Class {
4417
4417
  constructor(..._args) {}
4418
4418
  }
4419
4419
 
4420
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/errors.js
4420
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/errors.js
4421
4421
  var initializer = (inst, def) => {
4422
4422
  inst.name = "$ZodError";
4423
4423
  Object.defineProperty(inst, "_zod", {
@@ -4554,7 +4554,7 @@ function prettifyError(error) {
4554
4554
  `);
4555
4555
  }
4556
4556
 
4557
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/parse.js
4557
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/parse.js
4558
4558
  var _parse = (_Err) => (schema, value, _ctx, _params) => {
4559
4559
  const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
4560
4560
  const result = schema._zod.run({ value, issues: [] }, ctx);
@@ -4641,7 +4641,7 @@ var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
4641
4641
  return _safeParseAsync(_Err)(schema, value, _ctx);
4642
4642
  };
4643
4643
  var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync($ZodRealError);
4644
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/regexes.js
4644
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/regexes.js
4645
4645
  var exports_regexes = {};
4646
4646
  __export(exports_regexes, {
4647
4647
  xid: () => xid,
@@ -4798,7 +4798,7 @@ var sha512_hex = /^[0-9a-fA-F]{128}$/;
4798
4798
  var sha512_base64 = /* @__PURE__ */ fixedBase64(86, "==");
4799
4799
  var sha512_base64url = /* @__PURE__ */ fixedBase64url(86);
4800
4800
 
4801
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/checks.js
4801
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/checks.js
4802
4802
  var $ZodCheck = /* @__PURE__ */ $constructor("$ZodCheck", (inst, def) => {
4803
4803
  var _a;
4804
4804
  inst._zod ?? (inst._zod = {});
@@ -5345,7 +5345,7 @@ var $ZodCheckOverwrite = /* @__PURE__ */ $constructor("$ZodCheckOverwrite", (ins
5345
5345
  };
5346
5346
  });
5347
5347
 
5348
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/doc.js
5348
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/doc.js
5349
5349
  class Doc {
5350
5350
  constructor(args = []) {
5351
5351
  this.content = [];
@@ -5383,14 +5383,14 @@ class Doc {
5383
5383
  }
5384
5384
  }
5385
5385
 
5386
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/versions.js
5386
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/versions.js
5387
5387
  var version = {
5388
5388
  major: 4,
5389
5389
  minor: 3,
5390
- patch: 5
5390
+ patch: 6
5391
5391
  };
5392
5392
 
5393
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/schemas.js
5393
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/schemas.js
5394
5394
  var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
5395
5395
  var _a;
5396
5396
  inst ?? (inst = {});
@@ -6673,7 +6673,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
6673
6673
  if (keyResult instanceof Promise) {
6674
6674
  throw new Error("Async schemas not supported in object keys currently");
6675
6675
  }
6676
- const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number");
6676
+ const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
6677
6677
  if (checkNumericKey) {
6678
6678
  const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
6679
6679
  if (retryResult instanceof Promise) {
@@ -7352,7 +7352,7 @@ function handleRefineResult(result, payload, input, inst) {
7352
7352
  payload.issues.push(issue(_iss));
7353
7353
  }
7354
7354
  }
7355
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/index.js
7355
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/index.js
7356
7356
  var exports_locales = {};
7357
7357
  __export(exports_locales, {
7358
7358
  zhTW: () => zh_TW_default,
@@ -7406,7 +7406,7 @@ __export(exports_locales, {
7406
7406
  ar: () => ar_default
7407
7407
  });
7408
7408
 
7409
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ar.js
7409
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ar.js
7410
7410
  var error = () => {
7411
7411
  const Sizable = {
7412
7412
  string: { unit: "حرف", verb: "أن يحوي" },
@@ -7512,7 +7512,7 @@ function ar_default() {
7512
7512
  localeError: error()
7513
7513
  };
7514
7514
  }
7515
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/az.js
7515
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/az.js
7516
7516
  var error2 = () => {
7517
7517
  const Sizable = {
7518
7518
  string: { unit: "simvol", verb: "olmalıdır" },
@@ -7617,7 +7617,7 @@ function az_default() {
7617
7617
  localeError: error2()
7618
7618
  };
7619
7619
  }
7620
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/be.js
7620
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/be.js
7621
7621
  function getBelarusianPlural(count, one, few, many) {
7622
7622
  const absCount = Math.abs(count);
7623
7623
  const lastDigit = absCount % 10;
@@ -7773,7 +7773,7 @@ function be_default() {
7773
7773
  localeError: error3()
7774
7774
  };
7775
7775
  }
7776
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/bg.js
7776
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/bg.js
7777
7777
  var error4 = () => {
7778
7778
  const Sizable = {
7779
7779
  string: { unit: "символа", verb: "да съдържа" },
@@ -7893,7 +7893,7 @@ function bg_default() {
7893
7893
  localeError: error4()
7894
7894
  };
7895
7895
  }
7896
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ca.js
7896
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ca.js
7897
7897
  var error5 = () => {
7898
7898
  const Sizable = {
7899
7899
  string: { unit: "caràcters", verb: "contenir" },
@@ -8000,7 +8000,7 @@ function ca_default() {
8000
8000
  localeError: error5()
8001
8001
  };
8002
8002
  }
8003
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/cs.js
8003
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/cs.js
8004
8004
  var error6 = () => {
8005
8005
  const Sizable = {
8006
8006
  string: { unit: "znaků", verb: "mít" },
@@ -8111,7 +8111,7 @@ function cs_default() {
8111
8111
  localeError: error6()
8112
8112
  };
8113
8113
  }
8114
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/da.js
8114
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/da.js
8115
8115
  var error7 = () => {
8116
8116
  const Sizable = {
8117
8117
  string: { unit: "tegn", verb: "havde" },
@@ -8226,7 +8226,7 @@ function da_default() {
8226
8226
  localeError: error7()
8227
8227
  };
8228
8228
  }
8229
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/de.js
8229
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/de.js
8230
8230
  var error8 = () => {
8231
8231
  const Sizable = {
8232
8232
  string: { unit: "Zeichen", verb: "zu haben" },
@@ -8334,7 +8334,7 @@ function de_default() {
8334
8334
  localeError: error8()
8335
8335
  };
8336
8336
  }
8337
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/en.js
8337
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/en.js
8338
8338
  var error9 = () => {
8339
8339
  const Sizable = {
8340
8340
  string: { unit: "characters", verb: "to have" },
@@ -8440,7 +8440,7 @@ function en_default() {
8440
8440
  localeError: error9()
8441
8441
  };
8442
8442
  }
8443
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/eo.js
8443
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/eo.js
8444
8444
  var error10 = () => {
8445
8445
  const Sizable = {
8446
8446
  string: { unit: "karaktrojn", verb: "havi" },
@@ -8549,7 +8549,7 @@ function eo_default() {
8549
8549
  localeError: error10()
8550
8550
  };
8551
8551
  }
8552
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/es.js
8552
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/es.js
8553
8553
  var error11 = () => {
8554
8554
  const Sizable = {
8555
8555
  string: { unit: "caracteres", verb: "tener" },
@@ -8681,7 +8681,7 @@ function es_default() {
8681
8681
  localeError: error11()
8682
8682
  };
8683
8683
  }
8684
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fa.js
8684
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fa.js
8685
8685
  var error12 = () => {
8686
8686
  const Sizable = {
8687
8687
  string: { unit: "کاراکتر", verb: "داشته باشد" },
@@ -8795,7 +8795,7 @@ function fa_default() {
8795
8795
  localeError: error12()
8796
8796
  };
8797
8797
  }
8798
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fi.js
8798
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fi.js
8799
8799
  var error13 = () => {
8800
8800
  const Sizable = {
8801
8801
  string: { unit: "merkkiä", subject: "merkkijonon" },
@@ -8907,7 +8907,7 @@ function fi_default() {
8907
8907
  localeError: error13()
8908
8908
  };
8909
8909
  }
8910
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fr.js
8910
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr.js
8911
8911
  var error14 = () => {
8912
8912
  const Sizable = {
8913
8913
  string: { unit: "caractères", verb: "avoir" },
@@ -9015,7 +9015,7 @@ function fr_default() {
9015
9015
  localeError: error14()
9016
9016
  };
9017
9017
  }
9018
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fr-CA.js
9018
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/fr-CA.js
9019
9019
  var error15 = () => {
9020
9020
  const Sizable = {
9021
9021
  string: { unit: "caractères", verb: "avoir" },
@@ -9122,7 +9122,7 @@ function fr_CA_default() {
9122
9122
  localeError: error15()
9123
9123
  };
9124
9124
  }
9125
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/he.js
9125
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/he.js
9126
9126
  var error16 = () => {
9127
9127
  const TypeNames = {
9128
9128
  string: { label: "מחרוזת", gender: "f" },
@@ -9315,7 +9315,7 @@ function he_default() {
9315
9315
  localeError: error16()
9316
9316
  };
9317
9317
  }
9318
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/hu.js
9318
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hu.js
9319
9319
  var error17 = () => {
9320
9320
  const Sizable = {
9321
9321
  string: { unit: "karakter", verb: "legyen" },
@@ -9423,7 +9423,7 @@ function hu_default() {
9423
9423
  localeError: error17()
9424
9424
  };
9425
9425
  }
9426
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/hy.js
9426
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/hy.js
9427
9427
  function getArmenianPlural(count, one, many) {
9428
9428
  return Math.abs(count) === 1 ? one : many;
9429
9429
  }
@@ -9570,7 +9570,7 @@ function hy_default() {
9570
9570
  localeError: error18()
9571
9571
  };
9572
9572
  }
9573
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/id.js
9573
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/id.js
9574
9574
  var error19 = () => {
9575
9575
  const Sizable = {
9576
9576
  string: { unit: "karakter", verb: "memiliki" },
@@ -9676,7 +9676,7 @@ function id_default() {
9676
9676
  localeError: error19()
9677
9677
  };
9678
9678
  }
9679
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/is.js
9679
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/is.js
9680
9680
  var error20 = () => {
9681
9681
  const Sizable = {
9682
9682
  string: { unit: "stafi", verb: "að hafa" },
@@ -9785,7 +9785,7 @@ function is_default() {
9785
9785
  localeError: error20()
9786
9786
  };
9787
9787
  }
9788
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/it.js
9788
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/it.js
9789
9789
  var error21 = () => {
9790
9790
  const Sizable = {
9791
9791
  string: { unit: "caratteri", verb: "avere" },
@@ -9893,7 +9893,7 @@ function it_default() {
9893
9893
  localeError: error21()
9894
9894
  };
9895
9895
  }
9896
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ja.js
9896
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ja.js
9897
9897
  var error22 = () => {
9898
9898
  const Sizable = {
9899
9899
  string: { unit: "文字", verb: "である" },
@@ -10000,7 +10000,7 @@ function ja_default() {
10000
10000
  localeError: error22()
10001
10001
  };
10002
10002
  }
10003
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ka.js
10003
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ka.js
10004
10004
  var error23 = () => {
10005
10005
  const Sizable = {
10006
10006
  string: { unit: "სიმბოლო", verb: "უნდა შეიცავდეს" },
@@ -10112,7 +10112,7 @@ function ka_default() {
10112
10112
  localeError: error23()
10113
10113
  };
10114
10114
  }
10115
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/km.js
10115
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/km.js
10116
10116
  var error24 = () => {
10117
10117
  const Sizable = {
10118
10118
  string: { unit: "តួអក្សរ", verb: "គួរមាន" },
@@ -10223,11 +10223,11 @@ function km_default() {
10223
10223
  };
10224
10224
  }
10225
10225
 
10226
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/kh.js
10226
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/kh.js
10227
10227
  function kh_default() {
10228
10228
  return km_default();
10229
10229
  }
10230
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ko.js
10230
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ko.js
10231
10231
  var error25 = () => {
10232
10232
  const Sizable = {
10233
10233
  string: { unit: "문자", verb: "to have" },
@@ -10338,7 +10338,7 @@ function ko_default() {
10338
10338
  localeError: error25()
10339
10339
  };
10340
10340
  }
10341
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/lt.js
10341
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/lt.js
10342
10342
  var capitalizeFirstCharacter = (text) => {
10343
10343
  return text.charAt(0).toUpperCase() + text.slice(1);
10344
10344
  };
@@ -10541,7 +10541,7 @@ function lt_default() {
10541
10541
  localeError: error26()
10542
10542
  };
10543
10543
  }
10544
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/mk.js
10544
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/mk.js
10545
10545
  var error27 = () => {
10546
10546
  const Sizable = {
10547
10547
  string: { unit: "знаци", verb: "да имаат" },
@@ -10650,7 +10650,7 @@ function mk_default() {
10650
10650
  localeError: error27()
10651
10651
  };
10652
10652
  }
10653
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ms.js
10653
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ms.js
10654
10654
  var error28 = () => {
10655
10655
  const Sizable = {
10656
10656
  string: { unit: "aksara", verb: "mempunyai" },
@@ -10757,7 +10757,7 @@ function ms_default() {
10757
10757
  localeError: error28()
10758
10758
  };
10759
10759
  }
10760
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/nl.js
10760
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/nl.js
10761
10761
  var error29 = () => {
10762
10762
  const Sizable = {
10763
10763
  string: { unit: "tekens", verb: "heeft" },
@@ -10867,7 +10867,7 @@ function nl_default() {
10867
10867
  localeError: error29()
10868
10868
  };
10869
10869
  }
10870
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/no.js
10870
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/no.js
10871
10871
  var error30 = () => {
10872
10872
  const Sizable = {
10873
10873
  string: { unit: "tegn", verb: "å ha" },
@@ -10975,7 +10975,7 @@ function no_default() {
10975
10975
  localeError: error30()
10976
10976
  };
10977
10977
  }
10978
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ota.js
10978
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ota.js
10979
10979
  var error31 = () => {
10980
10980
  const Sizable = {
10981
10981
  string: { unit: "harf", verb: "olmalıdır" },
@@ -11084,7 +11084,7 @@ function ota_default() {
11084
11084
  localeError: error31()
11085
11085
  };
11086
11086
  }
11087
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ps.js
11087
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ps.js
11088
11088
  var error32 = () => {
11089
11089
  const Sizable = {
11090
11090
  string: { unit: "توکي", verb: "ولري" },
@@ -11198,7 +11198,7 @@ function ps_default() {
11198
11198
  localeError: error32()
11199
11199
  };
11200
11200
  }
11201
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/pl.js
11201
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pl.js
11202
11202
  var error33 = () => {
11203
11203
  const Sizable = {
11204
11204
  string: { unit: "znaków", verb: "mieć" },
@@ -11307,7 +11307,7 @@ function pl_default() {
11307
11307
  localeError: error33()
11308
11308
  };
11309
11309
  }
11310
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/pt.js
11310
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/pt.js
11311
11311
  var error34 = () => {
11312
11312
  const Sizable = {
11313
11313
  string: { unit: "caracteres", verb: "ter" },
@@ -11415,7 +11415,7 @@ function pt_default() {
11415
11415
  localeError: error34()
11416
11416
  };
11417
11417
  }
11418
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ru.js
11418
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ru.js
11419
11419
  function getRussianPlural(count, one, few, many) {
11420
11420
  const absCount = Math.abs(count);
11421
11421
  const lastDigit = absCount % 10;
@@ -11571,7 +11571,7 @@ function ru_default() {
11571
11571
  localeError: error35()
11572
11572
  };
11573
11573
  }
11574
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/sl.js
11574
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sl.js
11575
11575
  var error36 = () => {
11576
11576
  const Sizable = {
11577
11577
  string: { unit: "znakov", verb: "imeti" },
@@ -11680,7 +11680,7 @@ function sl_default() {
11680
11680
  localeError: error36()
11681
11681
  };
11682
11682
  }
11683
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/sv.js
11683
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/sv.js
11684
11684
  var error37 = () => {
11685
11685
  const Sizable = {
11686
11686
  string: { unit: "tecken", verb: "att ha" },
@@ -11790,7 +11790,7 @@ function sv_default() {
11790
11790
  localeError: error37()
11791
11791
  };
11792
11792
  }
11793
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ta.js
11793
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ta.js
11794
11794
  var error38 = () => {
11795
11795
  const Sizable = {
11796
11796
  string: { unit: "எழுத்துக்கள்", verb: "கொண்டிருக்க வேண்டும்" },
@@ -11900,7 +11900,7 @@ function ta_default() {
11900
11900
  localeError: error38()
11901
11901
  };
11902
11902
  }
11903
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/th.js
11903
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/th.js
11904
11904
  var error39 = () => {
11905
11905
  const Sizable = {
11906
11906
  string: { unit: "ตัวอักษร", verb: "ควรมี" },
@@ -12010,7 +12010,7 @@ function th_default() {
12010
12010
  localeError: error39()
12011
12011
  };
12012
12012
  }
12013
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/tr.js
12013
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/tr.js
12014
12014
  var error40 = () => {
12015
12015
  const Sizable = {
12016
12016
  string: { unit: "karakter", verb: "olmalı" },
@@ -12115,7 +12115,7 @@ function tr_default() {
12115
12115
  localeError: error40()
12116
12116
  };
12117
12117
  }
12118
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/uk.js
12118
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uk.js
12119
12119
  var error41 = () => {
12120
12120
  const Sizable = {
12121
12121
  string: { unit: "символів", verb: "матиме" },
@@ -12224,11 +12224,11 @@ function uk_default() {
12224
12224
  };
12225
12225
  }
12226
12226
 
12227
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ua.js
12227
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ua.js
12228
12228
  function ua_default() {
12229
12229
  return uk_default();
12230
12230
  }
12231
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ur.js
12231
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/ur.js
12232
12232
  var error42 = () => {
12233
12233
  const Sizable = {
12234
12234
  string: { unit: "حروف", verb: "ہونا" },
@@ -12338,7 +12338,7 @@ function ur_default() {
12338
12338
  localeError: error42()
12339
12339
  };
12340
12340
  }
12341
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/uz.js
12341
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/uz.js
12342
12342
  var error43 = () => {
12343
12343
  const Sizable = {
12344
12344
  string: { unit: "belgi", verb: "bo‘lishi kerak" },
@@ -12447,7 +12447,7 @@ function uz_default() {
12447
12447
  localeError: error43()
12448
12448
  };
12449
12449
  }
12450
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/vi.js
12450
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/vi.js
12451
12451
  var error44 = () => {
12452
12452
  const Sizable = {
12453
12453
  string: { unit: "ký tự", verb: "có" },
@@ -12555,7 +12555,7 @@ function vi_default() {
12555
12555
  localeError: error44()
12556
12556
  };
12557
12557
  }
12558
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/zh-CN.js
12558
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-CN.js
12559
12559
  var error45 = () => {
12560
12560
  const Sizable = {
12561
12561
  string: { unit: "字符", verb: "包含" },
@@ -12664,7 +12664,7 @@ function zh_CN_default() {
12664
12664
  localeError: error45()
12665
12665
  };
12666
12666
  }
12667
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/zh-TW.js
12667
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/zh-TW.js
12668
12668
  var error46 = () => {
12669
12669
  const Sizable = {
12670
12670
  string: { unit: "字元", verb: "擁有" },
@@ -12771,7 +12771,7 @@ function zh_TW_default() {
12771
12771
  localeError: error46()
12772
12772
  };
12773
12773
  }
12774
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/yo.js
12774
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/locales/yo.js
12775
12775
  var error47 = () => {
12776
12776
  const Sizable = {
12777
12777
  string: { unit: "àmi", verb: "ní" },
@@ -12878,7 +12878,7 @@ function yo_default() {
12878
12878
  localeError: error47()
12879
12879
  };
12880
12880
  }
12881
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/registries.js
12881
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/registries.js
12882
12882
  var _a;
12883
12883
  var $output = Symbol("ZodOutput");
12884
12884
  var $input = Symbol("ZodInput");
@@ -12928,7 +12928,7 @@ function registry() {
12928
12928
  }
12929
12929
  (_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
12930
12930
  var globalRegistry = globalThis.__zod_globalRegistry;
12931
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/api.js
12931
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/api.js
12932
12932
  function _string(Class2, params) {
12933
12933
  return new Class2({
12934
12934
  type: "string",
@@ -13848,7 +13848,7 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
13848
13848
  const inst = new Class2(def);
13849
13849
  return inst;
13850
13850
  }
13851
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/to-json-schema.js
13851
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/to-json-schema.js
13852
13852
  function initializeContext(params) {
13853
13853
  let target = params?.target ?? "draft-2020-12";
13854
13854
  if (target === "draft-4")
@@ -14044,7 +14044,7 @@ function finalize(ctx, schema) {
14044
14044
  }
14045
14045
  }
14046
14046
  }
14047
- if (refSchema.$ref) {
14047
+ if (refSchema.$ref && refSeen.def) {
14048
14048
  for (const key in schema2) {
14049
14049
  if (key === "$ref" || key === "allOf")
14050
14050
  continue;
@@ -14193,7 +14193,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
14193
14193
  extractDefs(ctx, schema);
14194
14194
  return finalize(ctx, schema);
14195
14195
  };
14196
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/json-schema-processors.js
14196
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema-processors.js
14197
14197
  var formatMap = {
14198
14198
  guid: "uuid",
14199
14199
  url: "uri",
@@ -14738,7 +14738,7 @@ function toJSONSchema(input, params) {
14738
14738
  extractDefs(ctx, input);
14739
14739
  return finalize(ctx, input);
14740
14740
  }
14741
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/json-schema-generator.js
14741
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema-generator.js
14742
14742
  class JSONSchemaGenerator {
14743
14743
  get metadataRegistry() {
14744
14744
  return this.ctx.metadataRegistry;
@@ -14797,9 +14797,9 @@ class JSONSchemaGenerator {
14797
14797
  return plainResult;
14798
14798
  }
14799
14799
  }
14800
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/json-schema.js
14800
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/json-schema.js
14801
14801
  var exports_json_schema = {};
14802
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/schemas.js
14802
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/schemas.js
14803
14803
  var exports_schemas2 = {};
14804
14804
  __export(exports_schemas2, {
14805
14805
  xor: () => xor,
@@ -14968,7 +14968,7 @@ __export(exports_schemas2, {
14968
14968
  ZodAny: () => ZodAny
14969
14969
  });
14970
14970
 
14971
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/checks.js
14971
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/checks.js
14972
14972
  var exports_checks2 = {};
14973
14973
  __export(exports_checks2, {
14974
14974
  uppercase: () => _uppercase,
@@ -15002,7 +15002,7 @@ __export(exports_checks2, {
15002
15002
  endsWith: () => _endsWith
15003
15003
  });
15004
15004
 
15005
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/iso.js
15005
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/iso.js
15006
15006
  var exports_iso = {};
15007
15007
  __export(exports_iso, {
15008
15008
  time: () => time2,
@@ -15043,7 +15043,7 @@ function duration2(params) {
15043
15043
  return _isoDuration(ZodISODuration, params);
15044
15044
  }
15045
15045
 
15046
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/errors.js
15046
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/errors.js
15047
15047
  var initializer2 = (inst, issues) => {
15048
15048
  $ZodError.init(inst, issues);
15049
15049
  inst.name = "ZodError";
@@ -15078,7 +15078,7 @@ var ZodRealError = $constructor("ZodError", initializer2, {
15078
15078
  Parent: Error
15079
15079
  });
15080
15080
 
15081
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/parse.js
15081
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/parse.js
15082
15082
  var parse3 = /* @__PURE__ */ _parse(ZodRealError);
15083
15083
  var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
15084
15084
  var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
@@ -15092,7 +15092,7 @@ var safeDecode2 = /* @__PURE__ */ _safeDecode(ZodRealError);
15092
15092
  var safeEncodeAsync2 = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
15093
15093
  var safeDecodeAsync2 = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
15094
15094
 
15095
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/schemas.js
15095
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/schemas.js
15096
15096
  var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
15097
15097
  $ZodType.init(inst, def);
15098
15098
  Object.assign(inst["~standard"], {
@@ -16168,7 +16168,7 @@ function json(params) {
16168
16168
  function preprocess(fn, schema) {
16169
16169
  return pipe(transform(fn), schema);
16170
16170
  }
16171
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/compat.js
16171
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/compat.js
16172
16172
  var ZodIssueCode = {
16173
16173
  invalid_type: "invalid_type",
16174
16174
  too_big: "too_big",
@@ -16192,7 +16192,7 @@ function getErrorMap() {
16192
16192
  }
16193
16193
  var ZodFirstPartyTypeKind;
16194
16194
  (function(ZodFirstPartyTypeKind2) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
16195
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/from-json-schema.js
16195
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/from-json-schema.js
16196
16196
  var z2 = {
16197
16197
  ...exports_schemas2,
16198
16198
  ...exports_checks2,
@@ -16653,7 +16653,7 @@ function fromJSONSchema(schema, params) {
16653
16653
  };
16654
16654
  return convertSchema(schema, ctx);
16655
16655
  }
16656
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/coerce.js
16656
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/coerce.js
16657
16657
  var exports_coerce = {};
16658
16658
  __export(exports_coerce, {
16659
16659
  string: () => string3,
@@ -16678,7 +16678,7 @@ function date4(params) {
16678
16678
  return _coercedDate(ZodDate, params);
16679
16679
  }
16680
16680
 
16681
- // ../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/external.js
16681
+ // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
16682
16682
  config(en_default());
16683
16683
  // ../internal/cli-infra/src/credentials/schema.ts
16684
16684
  var CredentialsSchema = exports_external.object({
@@ -16688,81 +16688,19 @@ var CredentialsSchema = exports_external.object({
16688
16688
  });
16689
16689
  var ClientIdSchema = exports_external.string().min(1, "Client ID is required").regex(/^[a-z0-9]+$/, "Client ID must contain only lowercase letters and numbers").length(26, "Client ID must be exactly 26 characters");
16690
16690
  var ClientSecretSchema = exports_external.string().min(1, "Client secret is required").regex(/^[a-z0-9]+$/, "Client secret must contain only lowercase letters and numbers").max(53, "Client secret must be less than 53 characters");
16691
- // ../internal/cli-infra/src/credentials/validation.ts
16692
- import { TimebackClient } from "@timeback/core";
16693
- async function validateEmailWithTimeback(environment, clientId, clientSecret, email3) {
16694
- const client = new TimebackClient({
16695
- env: environment,
16696
- auth: { clientId, clientSecret }
16697
- });
16698
- try {
16699
- const page = await client.oneroster.users.list({
16700
- where: { email: email3 },
16701
- limit: 1
16702
- });
16703
- if (page.data.length === 0) {
16704
- return {
16705
- valid: false,
16706
- error: `No user found with email "${email3}" in ${environment}`
16707
- };
16708
- }
16709
- return { valid: true };
16710
- } catch (error48) {
16711
- const message = error48 instanceof Error ? error48.message : "Unknown error";
16712
- return { valid: false, error: `Failed to validate email: ${message}` };
16713
- }
16691
+ // ../internal/cli-infra/src/ui.ts
16692
+ function intro(title) {
16693
+ console.log("");
16694
+ Ie(bgCyan(black(` ${title} `)));
16714
16695
  }
16696
+ var outro = {
16697
+ success: (message = "Done") => Se(green(message)),
16698
+ cancelled: () => Se(dim("Cancelled")),
16699
+ error: (message) => Se(red(message)),
16700
+ warn: (message) => Se(yellow(message)),
16701
+ info: (message) => Se(dim(message))
16702
+ };
16715
16703
 
16716
- // ../internal/cli-infra/src/credentials/prompts.ts
16717
- async function promptForCredentials(environment) {
16718
- const clientId = await he({
16719
- message: `Client ID ${dim(`(${environment})`)}`,
16720
- placeholder: "tb_client_...",
16721
- validate: (value) => {
16722
- const result = ClientIdSchema.safeParse(value);
16723
- if (!result.success)
16724
- return result.error.issues[0]?.message;
16725
- }
16726
- });
16727
- if (isCancelled(clientId))
16728
- return null;
16729
- const clientSecret = await ge({
16730
- message: `Client Secret ${dim(`(${environment})`)}`,
16731
- validate: (value) => {
16732
- const result = ClientSecretSchema.safeParse(value);
16733
- if (!result.success)
16734
- return result.error.issues[0]?.message;
16735
- }
16736
- });
16737
- if (isCancelled(clientSecret))
16738
- return null;
16739
- const email3 = await he({
16740
- message: `Your email ${dim("(for fetching your OneRoster profile)")}`,
16741
- placeholder: "you@example.com",
16742
- validate: (value) => {
16743
- if (!value)
16744
- return;
16745
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
16746
- if (!emailRegex.test(value))
16747
- return "Please enter a valid email";
16748
- }
16749
- });
16750
- if (isCancelled(email3))
16751
- return null;
16752
- if (email3) {
16753
- const s = Y2();
16754
- s.start("Validating email...");
16755
- const result = await validateEmailWithTimeback(environment, clientId, clientSecret, email3);
16756
- if (!result.valid) {
16757
- s.stop(red("Email validation failed"));
16758
- M2.error(result.error ?? "Unknown error");
16759
- M2.info("Please contact a Timeback admin to set up your account.");
16760
- return null;
16761
- }
16762
- s.stop(green("Email validated"));
16763
- }
16764
- return { clientId, clientSecret, email: email3 || undefined };
16765
- }
16766
16704
  // ../internal/cli-infra/src/credentials/store.ts
16767
16705
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
16768
16706
  import { homedir, platform as platform2 } from "node:os";
@@ -16788,12 +16726,12 @@ async function ensureCredentialsDir() {
16788
16726
  try {
16789
16727
  const stats = await stat(CREDENTIALS_DIR);
16790
16728
  if (!stats.isDirectory()) {
16791
- console.log();
16729
+ console.log("");
16792
16730
  console.log(` ${red("✖")} ${bold("Cannot save credentials")}`);
16793
- console.log();
16731
+ console.log("");
16794
16732
  console.log(` A file exists at ${dim(CREDENTIALS_DIR)}`);
16795
16733
  console.log(` Please remove it or rename it to allow credential storage.`);
16796
- console.log();
16734
+ console.log("");
16797
16735
  return false;
16798
16736
  }
16799
16737
  } catch {
@@ -16812,7 +16750,13 @@ async function readCredentialsStore() {
16812
16750
  async function writeCredentialsStore(store) {
16813
16751
  if (!await ensureCredentialsDir())
16814
16752
  return false;
16815
- await writeFile(CREDENTIALS_FILE, JSON.stringify(store, null, 2), { mode: 384 });
16753
+ const { staging, production } = store;
16754
+ const cleaned = {};
16755
+ if (staging)
16756
+ cleaned.staging = staging;
16757
+ if (production)
16758
+ cleaned.production = production;
16759
+ await writeFile(CREDENTIALS_FILE, JSON.stringify(cleaned, null, 2), { mode: 384 });
16816
16760
  return true;
16817
16761
  }
16818
16762
  async function getSavedCredentials(environment) {
@@ -16831,15 +16775,6 @@ async function saveCredentials(environment, credentials) {
16831
16775
  store[environment] = credentials;
16832
16776
  return writeCredentialsStore(store);
16833
16777
  }
16834
- async function getDefaultEnvironment() {
16835
- const store = await readCredentialsStore();
16836
- return store.default ?? null;
16837
- }
16838
- async function saveDefaultEnvironment(environment) {
16839
- const store = await readCredentialsStore();
16840
- store.default = environment;
16841
- return writeCredentialsStore(store);
16842
- }
16843
16778
  async function clearCredentials(environment) {
16844
16779
  if (environment) {
16845
16780
  const store = await readCredentialsStore();
@@ -16884,18 +16819,115 @@ async function loadCredentials(environment) {
16884
16819
  error: `No credentials found for ${environment}`
16885
16820
  };
16886
16821
  }
16887
- // ../internal/cli-infra/src/ui.ts
16888
- function intro(title) {
16889
- console.log("");
16890
- Ie(bgCyan(black(` ${title} `)));
16822
+ async function loadAllCredentials() {
16823
+ const configuredEnvs = await getConfiguredEnvironments();
16824
+ const credentials = {};
16825
+ for (const env2 of configuredEnvs) {
16826
+ const result = await loadCredentials(env2);
16827
+ if (result.success) {
16828
+ credentials[env2] = result.credentials;
16829
+ }
16830
+ }
16831
+ return credentials;
16832
+ }
16833
+
16834
+ // ../internal/cli-infra/src/credentials/validation.ts
16835
+ import { TimebackClient } from "@timeback/core";
16836
+ async function validateEmailWithTimeback(environment, clientId, clientSecret, email3) {
16837
+ const client = new TimebackClient({
16838
+ env: environment,
16839
+ auth: { clientId, clientSecret }
16840
+ });
16841
+ try {
16842
+ const page = await client.oneroster.users.list({
16843
+ where: { email: email3 },
16844
+ limit: 1
16845
+ });
16846
+ if (page.data.length === 0) {
16847
+ return {
16848
+ valid: false,
16849
+ error: `No user found with email "${email3}" in ${environment}`
16850
+ };
16851
+ }
16852
+ return { valid: true };
16853
+ } catch (error48) {
16854
+ const message = error48 instanceof Error ? error48.message : "Unknown error";
16855
+ return { valid: false, error: `Failed to validate email: ${message}` };
16856
+ }
16857
+ }
16858
+
16859
+ // ../internal/cli-infra/src/credentials/prompts.ts
16860
+ async function promptForCredentials(environment) {
16861
+ const clientId = await he({
16862
+ message: `Client ID ${dim(`(${environment})`)}`,
16863
+ placeholder: "tb_client_...",
16864
+ validate: (value) => {
16865
+ const result = ClientIdSchema.safeParse(value);
16866
+ if (!result.success)
16867
+ return result.error.issues[0]?.message;
16868
+ }
16869
+ });
16870
+ if (isCancelled(clientId))
16871
+ return null;
16872
+ const clientSecret = await ge({
16873
+ message: `Client Secret ${dim(`(${environment})`)}`,
16874
+ validate: (value) => {
16875
+ const result = ClientSecretSchema.safeParse(value);
16876
+ if (!result.success)
16877
+ return result.error.issues[0]?.message;
16878
+ }
16879
+ });
16880
+ if (isCancelled(clientSecret))
16881
+ return null;
16882
+ const email3 = await he({
16883
+ message: `Your email ${dim("(for fetching your OneRoster profile)")}`,
16884
+ placeholder: "you@example.com",
16885
+ validate: (value) => {
16886
+ if (!value)
16887
+ return;
16888
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
16889
+ if (!emailRegex.test(value))
16890
+ return "Please enter a valid email";
16891
+ }
16892
+ });
16893
+ if (isCancelled(email3))
16894
+ return null;
16895
+ if (email3) {
16896
+ const s = Y2();
16897
+ s.start("Validating email...");
16898
+ const result = await validateEmailWithTimeback(environment, clientId, clientSecret, email3);
16899
+ if (!result.valid) {
16900
+ s.stop(red("Email validation failed"));
16901
+ M2.error(result.error ?? "Unknown error");
16902
+ M2.info("Please contact a Timeback admin to set up your account.");
16903
+ return null;
16904
+ }
16905
+ s.stop(green("Email validated"));
16906
+ }
16907
+ return { clientId, clientSecret, email: email3 || undefined };
16908
+ }
16909
+ async function ensureCredentials(options) {
16910
+ const { env: env2, credentials, introTitle = "Timeback", skipIntro = false } = options;
16911
+ const existing = credentials[env2];
16912
+ if (existing)
16913
+ return existing;
16914
+ if (!skipIntro) {
16915
+ intro(introTitle);
16916
+ }
16917
+ Me(`No credentials configured for ${env2}.`, "Credential setup required");
16918
+ const newCreds = await promptForCredentials(env2);
16919
+ if (!newCreds)
16920
+ return null;
16921
+ const saved = await saveCredentials(env2, newCreds);
16922
+ if (saved) {
16923
+ M2.success(`${env2} credentials saved`);
16924
+ Me(`Saved to ${dim(getCredentialsPath())}`, green("Setup complete"));
16925
+ } else {
16926
+ M2.warn(`Credentials not saved`);
16927
+ }
16928
+ credentials[env2] = newCreds;
16929
+ return newCreds;
16891
16930
  }
16892
- var outro = {
16893
- success: (message = "Done") => Se(green(message)),
16894
- cancelled: () => Se(dim("Cancelled")),
16895
- error: (message) => Se(red(message)),
16896
- warn: (message) => Se(yellow(message)),
16897
- info: (message) => Se(dim(message))
16898
- };
16899
16931
  // ../internal/cli-infra/src/config/playcademy.ts
16900
16932
  var FILE_PATTERNS = ["playcademy.config.ts", "playcademy.config.js", "playcademy.config.json"];
16901
16933
  function parse5() {
@@ -16905,18 +16937,18 @@ function parse5() {
16905
16937
  });
16906
16938
  }
16907
16939
  function printError(error48) {
16908
- console.log();
16940
+ console.log("");
16909
16941
  console.log(` ${red("✖")} ${bold("Configuration Error")}`);
16910
- console.log();
16942
+ console.log("");
16911
16943
  console.log(` ${error48}`);
16912
- console.log();
16944
+ console.log("");
16913
16945
  console.log(` ${dim("Example playcademy.config.ts:")}`);
16914
- console.log();
16946
+ console.log("");
16915
16947
  console.log(` ${yellow("export default {")}`);
16916
16948
  console.log(` ${yellow(" name: 'My Playcademy App',")}`);
16917
16949
  console.log(` ${yellow(" // TODO: Define schema")}`);
16918
16950
  console.log(` ${yellow("}")}`);
16919
- console.log();
16951
+ console.log("");
16920
16952
  }
16921
16953
  var playcademyParser = {
16922
16954
  name: "playcademy",
@@ -16927,8 +16959,7 @@ var playcademyParser = {
16927
16959
 
16928
16960
  // ../internal/cli-infra/src/config/timeback.ts
16929
16961
  import { readFile as readFile2 } from "node:fs/promises";
16930
- import { basename, extname, relative, resolve } from "node:path";
16931
- import { loadConfig as c12LoadConfig } from "c12";
16962
+ import { resolve as resolve2 } from "node:path";
16932
16963
  // ../types/src/zod/primitives.ts
16933
16964
  var IsoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
16934
16965
  var IsoDateTimeString = exports_external.string().min(1).regex(IsoDateTimeRegex, "must be a valid ISO 8601 datetime");
@@ -17294,18 +17325,24 @@ var TimebackConfig = exports_external.object({
17294
17325
  message: "Duplicate courseCode found; each must be unique",
17295
17326
  path: ["courses"]
17296
17327
  }).refine((config2) => {
17297
- return config2.courses.every((c) => c.sensor !== undefined || config2.sensor !== undefined);
17298
- }, {
17299
- message: "Each course must have an effective sensor; set a top-level `sensor` or per-course `sensor`",
17300
- path: ["courses"]
17301
- }).refine((config2) => {
17302
- return config2.courses.every((c) => c.launchUrl !== undefined || config2.launchUrl !== undefined);
17328
+ return config2.courses.every((c) => {
17329
+ if (c.sensor !== undefined || config2.sensor !== undefined) {
17330
+ return true;
17331
+ }
17332
+ const launchUrls = [
17333
+ c.launchUrl,
17334
+ config2.launchUrl,
17335
+ c.overrides?.staging?.launchUrl,
17336
+ c.overrides?.production?.launchUrl
17337
+ ].filter(Boolean);
17338
+ return launchUrls.length > 0;
17339
+ });
17303
17340
  }, {
17304
- message: "Each course must have an effective launchUrl; set a top-level `launchUrl` or per-course `launchUrl`",
17341
+ message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
17305
17342
  path: ["courses"]
17306
17343
  });
17307
17344
  // ../types/src/zod/edubridge.ts
17308
- var EdubridgeDateString = exports_external.union([IsoDateString, IsoDateTimeString]);
17345
+ var EdubridgeDateString = IsoDateTimeString;
17309
17346
  var EduBridgeEnrollment = exports_external.object({
17310
17347
  id: exports_external.string(),
17311
17348
  role: exports_external.string(),
@@ -17369,12 +17406,9 @@ var EmailOrStudentId = exports_external.object({
17369
17406
  });
17370
17407
  var SubjectTrackInput = exports_external.object({
17371
17408
  subject: NonEmptyString,
17372
- gradeLevel: NonEmptyString,
17373
- targetCourseId: NonEmptyString,
17374
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
17375
- });
17376
- var SubjectTrackUpsertInput = SubjectTrackInput.extend({
17377
- id: NonEmptyString
17409
+ grade: NonEmptyString,
17410
+ courseId: NonEmptyString,
17411
+ orgSourcedId: NonEmptyString.optional()
17378
17412
  });
17379
17413
  var EdubridgeListEnrollmentsParams = exports_external.object({
17380
17414
  userId: NonEmptyString
@@ -18287,28 +18321,51 @@ var QtiLessonFeedbackInput = exports_external.object({
18287
18321
  lessonId: exports_external.string().min(1),
18288
18322
  humanApproved: exports_external.boolean().optional()
18289
18323
  }).strict();
18290
- // ../internal/cli-infra/src/config/timeback.ts
18324
+ // ../internal/cli-infra/src/config/constants.ts
18291
18325
  var CONFIG_FILENAME = "timeback.config.json";
18292
- var FILE_PATTERNS2 = [CONFIG_FILENAME];
18326
+ var CONFIG_SCHEMA_URL = "https://timeback.dev/schema.json";
18327
+ var DEFAULT_VERSION = "0.0.0";
18328
+ var JSON_INDENT = 2;
18329
+ var TOP_LEVEL_KEY_ORDER = [
18330
+ "$schema",
18331
+ "name",
18332
+ "launchUrl",
18333
+ "sensor",
18334
+ "defaults",
18335
+ "courses"
18336
+ ];
18337
+ var COURSE_KEY_ORDER = [
18338
+ "subject",
18339
+ "grade",
18340
+ "courseCode",
18341
+ "level",
18342
+ "sensor",
18343
+ "launchUrl",
18344
+ "ids",
18345
+ "metadata",
18346
+ "overrides"
18347
+ ];
18348
+ var IDS_KEY_ORDER = ["staging", "production"];
18349
+ var METADATA_KEY_ORDER = [
18350
+ "courseType",
18351
+ "isSupplemental",
18352
+ "isCustom",
18353
+ "publishStatus",
18354
+ "contactEmail",
18355
+ "primaryApp",
18356
+ "goals",
18357
+ "metrics"
18358
+ ];
18359
+ var DEFAULTS_KEY_ORDER = ["courseCode", "level", "metadata"];
18360
+ var OVERRIDES_KEY_ORDER = ["staging", "production"];
18361
+ var ENV_OVERRIDES_KEY_ORDER = ["level", "sensor", "launchUrl", "metadata"];
18362
+
18363
+ // ../internal/cli-infra/src/config/loader.ts
18364
+ import { basename, extname, relative, resolve } from "node:path";
18365
+ import { loadConfig as c12LoadConfig } from "c12";
18293
18366
  function isJsonConfigPath(configPath) {
18294
18367
  return extname(configPath).toLowerCase() === ".json";
18295
18368
  }
18296
- function deriveCourseIds(config3) {
18297
- const result = { staging: [], production: [] };
18298
- for (const env2 of ENVIRONMENTS) {
18299
- result[env2] = config3.courses.map((course) => course.ids?.[env2]).filter((id) => !!id);
18300
- }
18301
- return result;
18302
- }
18303
- async function readPackageVersion(cwd) {
18304
- try {
18305
- const pkgPath = resolve(cwd, "package.json");
18306
- const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
18307
- return pkg.version ?? "0.0.0";
18308
- } catch {
18309
- return "0.0.0";
18310
- }
18311
- }
18312
18369
  async function loadWithC12(cwd, configPath) {
18313
18370
  if (configPath && !isJsonConfigPath(configPath)) {
18314
18371
  throw new Error(`Config file must be JSON (.json): ${configPath}`);
@@ -18339,6 +18396,28 @@ async function loadWithC12(cwd, configPath) {
18339
18396
  configFile: result.configFile ?? resolve(cwd, configPath ?? CONFIG_FILENAME)
18340
18397
  };
18341
18398
  }
18399
+ function getRelativeConfigPath(configPath) {
18400
+ return relative(process.cwd(), configPath);
18401
+ }
18402
+
18403
+ // ../internal/cli-infra/src/config/timeback.ts
18404
+ var FILE_PATTERNS2 = [CONFIG_FILENAME];
18405
+ function deriveCourseIds(config3) {
18406
+ const result = { staging: [], production: [] };
18407
+ for (const env2 of ENVIRONMENTS) {
18408
+ result[env2] = config3.courses.map((course) => course.ids?.[env2]).filter((id) => !!id);
18409
+ }
18410
+ return result;
18411
+ }
18412
+ async function readPackageVersion(cwd) {
18413
+ try {
18414
+ const pkgPath = resolve2(cwd, "package.json");
18415
+ const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
18416
+ return pkg.version ?? DEFAULT_VERSION;
18417
+ } catch {
18418
+ return DEFAULT_VERSION;
18419
+ }
18420
+ }
18342
18421
  async function parse6() {
18343
18422
  const cwd = process.cwd();
18344
18423
  try {
@@ -18378,23 +18457,22 @@ ${err instanceof Error ? err.message : String(err)}`
18378
18457
  }
18379
18458
  }
18380
18459
  function printError2(error48) {
18381
- console.log();
18460
+ console.log("");
18382
18461
  console.log(` ${red("✖")} ${bold("Configuration Error")}`);
18383
- console.log();
18462
+ console.log("");
18384
18463
  console.log(` ${error48}`);
18385
- console.log();
18464
+ console.log("");
18386
18465
  console.log(` ${dim("Example timeback.config.json:")}`);
18387
- console.log();
18466
+ console.log("");
18388
18467
  console.log(` ${yellow("{")}`);
18389
18468
  console.log(` ${yellow(' "$schema": "https://timeback.dev/schema.json",')}`);
18390
18469
  console.log(` ${yellow(' "name": "My Timeback App",')}`);
18391
18470
  console.log(` ${yellow(' "launchUrl": "https://example.com/play",')}`);
18392
- console.log(` ${yellow(' "sensor": "https://example.com/sensor",')}`);
18393
18471
  console.log(` ${yellow(' "courses": [')}`);
18394
18472
  console.log(` ${yellow(' { "subject": "Math", "grade": 3 }')}`);
18395
18473
  console.log(` ${yellow(" ]")}`);
18396
18474
  console.log(` ${yellow("}")}`);
18397
- console.log();
18475
+ console.log("");
18398
18476
  }
18399
18477
  var timebackParser = {
18400
18478
  name: "timeback",
@@ -18402,7 +18480,446 @@ var timebackParser = {
18402
18480
  parse: parse6,
18403
18481
  printError: printError2
18404
18482
  };
18405
- // ../internal/cli-infra/src/config/utils.ts
18483
+ // ../internal/cli-infra/src/config/generate.ts
18484
+ function orderKeys(obj, keyOrder) {
18485
+ const result = {};
18486
+ const objKeys = Object.keys(obj);
18487
+ const source = obj;
18488
+ for (const key of keyOrder) {
18489
+ if (key in source && source[key] !== undefined) {
18490
+ result[key] = source[key];
18491
+ }
18492
+ }
18493
+ for (const key of objKeys) {
18494
+ if (!(key in result) && source[key] !== undefined) {
18495
+ result[key] = source[key];
18496
+ }
18497
+ }
18498
+ return result;
18499
+ }
18500
+ function filterUndefined(obj) {
18501
+ const result = {};
18502
+ for (const [key, value] of Object.entries(obj)) {
18503
+ if (value !== undefined) {
18504
+ result[key] = value;
18505
+ }
18506
+ }
18507
+ return result;
18508
+ }
18509
+ function formatCourseIds(ids) {
18510
+ if (!ids)
18511
+ return;
18512
+ const filtered = filterUndefined(ids);
18513
+ if (Object.keys(filtered).length === 0)
18514
+ return;
18515
+ return orderKeys(filtered, IDS_KEY_ORDER);
18516
+ }
18517
+ function formatMetadata(metadata) {
18518
+ if (!metadata)
18519
+ return;
18520
+ const filtered = filterUndefined(metadata);
18521
+ if (Object.keys(filtered).length === 0)
18522
+ return;
18523
+ return orderKeys(filtered, METADATA_KEY_ORDER);
18524
+ }
18525
+ function formatDefaults(defaults) {
18526
+ if (!defaults)
18527
+ return;
18528
+ const result = {};
18529
+ if (defaults.courseCode)
18530
+ result.courseCode = defaults.courseCode;
18531
+ if (defaults.level)
18532
+ result.level = defaults.level;
18533
+ if (defaults.metadata) {
18534
+ const meta3 = formatMetadata(defaults.metadata);
18535
+ if (meta3)
18536
+ result.metadata = meta3;
18537
+ }
18538
+ if (Object.keys(result).length === 0)
18539
+ return;
18540
+ return orderKeys(result, DEFAULTS_KEY_ORDER);
18541
+ }
18542
+ function formatEnvOverrides(overrides) {
18543
+ if (!overrides)
18544
+ return;
18545
+ const result = {};
18546
+ if (overrides.level)
18547
+ result.level = overrides.level;
18548
+ if (overrides.sensor)
18549
+ result.sensor = overrides.sensor;
18550
+ if (overrides.launchUrl)
18551
+ result.launchUrl = overrides.launchUrl;
18552
+ if (overrides.metadata) {
18553
+ const meta3 = formatMetadata(overrides.metadata);
18554
+ if (meta3)
18555
+ result.metadata = meta3;
18556
+ }
18557
+ if (Object.keys(result).length === 0)
18558
+ return;
18559
+ return orderKeys(result, ENV_OVERRIDES_KEY_ORDER);
18560
+ }
18561
+ function formatOverrides(overrides) {
18562
+ if (!overrides) {
18563
+ return;
18564
+ }
18565
+ const result = {};
18566
+ if (overrides.staging) {
18567
+ const staging = formatEnvOverrides(overrides.staging);
18568
+ if (staging)
18569
+ result.staging = staging;
18570
+ }
18571
+ if (overrides.production) {
18572
+ const production = formatEnvOverrides(overrides.production);
18573
+ if (production)
18574
+ result.production = production;
18575
+ }
18576
+ if (Object.keys(result).length === 0) {
18577
+ return;
18578
+ }
18579
+ return orderKeys(result, OVERRIDES_KEY_ORDER);
18580
+ }
18581
+ function formatCourse(course) {
18582
+ const result = {
18583
+ subject: course.subject
18584
+ };
18585
+ if (course.grade !== undefined) {
18586
+ result.grade = course.grade;
18587
+ }
18588
+ if (course.courseCode) {
18589
+ result.courseCode = course.courseCode;
18590
+ }
18591
+ if (course.level) {
18592
+ result.level = course.level;
18593
+ }
18594
+ if (course.sensor) {
18595
+ result.sensor = course.sensor;
18596
+ }
18597
+ if (course.launchUrl) {
18598
+ result.launchUrl = course.launchUrl;
18599
+ }
18600
+ const ids = formatCourseIds(course.ids);
18601
+ if (ids) {
18602
+ result.ids = ids;
18603
+ }
18604
+ const metadata = formatMetadata(course.metadata);
18605
+ if (metadata) {
18606
+ result.metadata = metadata;
18607
+ }
18608
+ const overrides = formatOverrides(course.overrides);
18609
+ if (overrides) {
18610
+ result.overrides = overrides;
18611
+ }
18612
+ return orderKeys(result, COURSE_KEY_ORDER);
18613
+ }
18614
+ function generateConfigContent(config3) {
18615
+ const output = {
18616
+ $schema: CONFIG_SCHEMA_URL,
18617
+ name: config3.name
18618
+ };
18619
+ if (config3.launchUrl) {
18620
+ output.launchUrl = config3.launchUrl;
18621
+ }
18622
+ if (config3.sensor) {
18623
+ output.sensor = config3.sensor;
18624
+ }
18625
+ const defaults = formatDefaults(config3.defaults);
18626
+ if (defaults) {
18627
+ output.defaults = defaults;
18628
+ }
18629
+ output.courses = config3.courses.map(formatCourse);
18630
+ const ordered = orderKeys(output, TOP_LEVEL_KEY_ORDER);
18631
+ return JSON.stringify(ordered, null, JSON_INDENT) + `
18632
+ `;
18633
+ }
18634
+ // ../internal/utils/src/server/spinner.ts
18635
+ import { stdout as stdout2 } from "node:process";
18636
+
18637
+ // ../internal/utils/src/server/terminal.ts
18638
+ import { stdout } from "node:process";
18639
+ var isInteractive = stdout.isTTY ?? false;
18640
+ var cursor = {
18641
+ hide: "\x1B[?25l",
18642
+ show: "\x1B[?25h",
18643
+ up: (n) => `\x1B[${n}A`,
18644
+ down: (n) => `\x1B[${n}B`,
18645
+ forward: (n) => `\x1B[${n}C`,
18646
+ back: (n) => `\x1B[${n}D`,
18647
+ clearLine: "\x1B[K",
18648
+ clearScreen: "\x1B[2J",
18649
+ home: "\x1B[H"
18650
+ };
18651
+ function stripAnsi(str) {
18652
+ return str.replaceAll(/\u001B\[[0-9;]*[a-zA-Z]/g, "");
18653
+ }
18654
+
18655
+ // ../internal/utils/src/server/spinner.ts
18656
+ var SPINNER_FRAMES = [
18657
+ 10251,
18658
+ 10265,
18659
+ 10297,
18660
+ 10296,
18661
+ 10300,
18662
+ 10292,
18663
+ 10278,
18664
+ 10279,
18665
+ 10247,
18666
+ 10255
18667
+ ].map((code) => String.fromCodePoint(code));
18668
+ var SPINNER_INTERVAL = 80;
18669
+ var CHECK_MARK = "✔";
18670
+ var CROSS_MARK = "✖";
18671
+ var STATUS_LABELS = {
18672
+ pending: "[PENDING]",
18673
+ running: "[RUNNING]",
18674
+ success: "[SUCCESS]",
18675
+ error: "[ERROR]"
18676
+ };
18677
+
18678
+ class Spinner {
18679
+ tasks = new Map;
18680
+ frameIndex = 0;
18681
+ intervalId = null;
18682
+ previousLineCount = 0;
18683
+ printedTasks = new Set;
18684
+ constructor(taskIds, texts) {
18685
+ for (const [index, id] of taskIds.entries()) {
18686
+ this.tasks.set(id, {
18687
+ text: texts[index] ?? "",
18688
+ status: "pending"
18689
+ });
18690
+ }
18691
+ }
18692
+ start() {
18693
+ if (!isInteractive)
18694
+ return;
18695
+ stdout2.write(cursor.hide);
18696
+ this.render();
18697
+ this.intervalId = setInterval(() => {
18698
+ this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
18699
+ this.render();
18700
+ }, SPINNER_INTERVAL);
18701
+ }
18702
+ updateTask(taskId, status, finalText) {
18703
+ const task = this.tasks.get(taskId);
18704
+ if (!task)
18705
+ return;
18706
+ task.status = status;
18707
+ if (finalText)
18708
+ task.finalText = finalText;
18709
+ if (!isInteractive) {
18710
+ this.renderNonInteractive(taskId, task);
18711
+ }
18712
+ }
18713
+ stop() {
18714
+ if (this.intervalId) {
18715
+ clearInterval(this.intervalId);
18716
+ this.intervalId = null;
18717
+ }
18718
+ if (isInteractive) {
18719
+ this.render();
18720
+ stdout2.write(cursor.show);
18721
+ }
18722
+ }
18723
+ clear() {
18724
+ if (this.intervalId) {
18725
+ clearInterval(this.intervalId);
18726
+ this.intervalId = null;
18727
+ }
18728
+ if (isInteractive && this.previousLineCount > 0) {
18729
+ stdout2.write(cursor.up(this.previousLineCount));
18730
+ for (let i = 0;i < this.previousLineCount; i++) {
18731
+ stdout2.write(`\r${cursor.clearLine}
18732
+ `);
18733
+ }
18734
+ stdout2.write(cursor.up(this.previousLineCount));
18735
+ stdout2.write(cursor.show);
18736
+ }
18737
+ this.previousLineCount = 0;
18738
+ }
18739
+ render() {
18740
+ if (this.previousLineCount > 0) {
18741
+ stdout2.write(cursor.up(this.previousLineCount));
18742
+ }
18743
+ const spinner = SPINNER_FRAMES[this.frameIndex];
18744
+ const visibleTasks = [...this.tasks.values()].filter((t) => t.status !== "pending");
18745
+ for (const task of visibleTasks) {
18746
+ stdout2.write(`\r${cursor.clearLine}`);
18747
+ console.log(this.formatLine(task, spinner));
18748
+ }
18749
+ this.previousLineCount = visibleTasks.length;
18750
+ }
18751
+ formatLine(task, spinner) {
18752
+ switch (task.status) {
18753
+ case "running":
18754
+ return `${blue(spinner ?? "○")} ${task.text}`;
18755
+ case "success":
18756
+ return `${green(CHECK_MARK)} ${task.finalText ?? task.text}`;
18757
+ case "error":
18758
+ return `${red(CROSS_MARK)} Failed: ${task.text}`;
18759
+ default:
18760
+ return task.text;
18761
+ }
18762
+ }
18763
+ renderNonInteractive(taskId, task) {
18764
+ const key = `${taskId}-${task.status}`;
18765
+ if (this.printedTasks.has(key))
18766
+ return;
18767
+ this.printedTasks.add(key);
18768
+ const text = task.status === "success" ? task.finalText ?? task.text : task.text;
18769
+ console.log(`${STATUS_LABELS[task.status]} ${stripAnsi(text)}`);
18770
+ }
18771
+ }
18772
+ // ../internal/utils/src/server/runtime.ts
18773
+ import { join as join2 } from "node:path";
18774
+ var STANDALONE_PATHS = [
18775
+ join2(".timeback", "bin"),
18776
+ join2(".local", "bin"),
18777
+ "/usr/local/bin/timeback",
18778
+ "/opt/homebrew/bin/timeback"
18779
+ ];
18780
+ // ../internal/utils/src/server/project.ts
18781
+ import { existsSync } from "node:fs";
18782
+ import { resolve as resolve3 } from "node:path";
18783
+ function detectPackageManager(cwd) {
18784
+ const candidates = [
18785
+ { pm: "bun", file: "bun.lockb" },
18786
+ { pm: "bun", file: "bun.lock" },
18787
+ { pm: "pnpm", file: "pnpm-lock.yaml" },
18788
+ { pm: "yarn", file: "yarn.lock" },
18789
+ { pm: "npm", file: "package-lock.json" }
18790
+ ];
18791
+ for (const { pm, file: file2 } of candidates) {
18792
+ if (existsSync(resolve3(cwd, file2))) {
18793
+ return { packageManager: pm, reason: "lockfile", lockfile: file2 };
18794
+ }
18795
+ }
18796
+ return { packageManager: "npm", reason: "default" };
18797
+ }
18798
+ function getDlxCommand(pm, tool) {
18799
+ switch (pm) {
18800
+ case "bun":
18801
+ return { cmd: "bunx", args: [tool] };
18802
+ case "pnpm":
18803
+ return { cmd: "pnpm", args: ["dlx", tool] };
18804
+ case "yarn":
18805
+ return { cmd: "yarn", args: ["dlx", tool] };
18806
+ case "npm":
18807
+ default:
18808
+ return { cmd: "npx", args: ["--yes", tool] };
18809
+ }
18810
+ }
18811
+ // ../internal/utils/src/server/prettier.ts
18812
+ import { spawn } from "node:child_process";
18813
+ import { existsSync as existsSync2 } from "node:fs";
18814
+ import { resolve as resolve4 } from "node:path";
18815
+ function run(cmd, args, cwd, silent = false) {
18816
+ return new Promise((resolvePromise) => {
18817
+ const child = spawn(cmd, args, {
18818
+ cwd,
18819
+ stdio: silent ? "ignore" : "inherit",
18820
+ shell: process.platform === "win32"
18821
+ });
18822
+ child.on("close", (code) => resolvePromise(code ?? 1));
18823
+ child.on("error", () => resolvePromise(1));
18824
+ });
18825
+ }
18826
+ function getPrettierBin(cwd) {
18827
+ return resolve4(cwd, "node_modules", ".bin", process.platform === "win32" ? "prettier.cmd" : "prettier");
18828
+ }
18829
+ function runPrettierUsingRunner(cwd, args) {
18830
+ const { packageManager } = detectPackageManager(cwd);
18831
+ const dlx = getDlxCommand(packageManager, "prettier");
18832
+ return run(dlx.cmd, [...dlx.args, ...args], cwd, true);
18833
+ }
18834
+ async function formatWithPrettier(options) {
18835
+ const { cwd, filePath, silent = false, label = "file" } = options;
18836
+ const prettierBin = getPrettierBin(cwd);
18837
+ const args = ["--write", filePath];
18838
+ const s = silent ? null : Y2();
18839
+ s?.start(`Formatting ${label}...`);
18840
+ try {
18841
+ let code;
18842
+ if (existsSync2(prettierBin)) {
18843
+ code = await run(prettierBin, args, cwd, true);
18844
+ } else {
18845
+ code = await runPrettierUsingRunner(cwd, args);
18846
+ }
18847
+ if (code === 0) {
18848
+ s?.stop(`Formatted ${label}`);
18849
+ return true;
18850
+ }
18851
+ s?.stop();
18852
+ if (!silent) {
18853
+ M2.warn(`Failed to format ${label}`);
18854
+ }
18855
+ return false;
18856
+ } catch {
18857
+ s?.stop();
18858
+ if (!silent) {
18859
+ M2.warn(`Failed to format ${label}`);
18860
+ }
18861
+ return false;
18862
+ }
18863
+ }
18864
+ // ../internal/cli-infra/src/config/update.ts
18865
+ import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
18866
+ async function updateTimebackConfigFile(configPath, updates, options = {}) {
18867
+ const { format = false, cwd = process.cwd(), silent = true } = options;
18868
+ try {
18869
+ const raw = await readFile3(configPath, "utf8");
18870
+ const parsed = JSON.parse(raw);
18871
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
18872
+ return { success: false, error: "Config file is not a JSON object." };
18873
+ }
18874
+ const { $schema: _schema, ...withoutSchema } = parsed;
18875
+ const current = TimebackConfig.safeParse(withoutSchema);
18876
+ if (!current.success) {
18877
+ return { success: false, error: "Config file is invalid; cannot update." };
18878
+ }
18879
+ const next = {
18880
+ ...current.data,
18881
+ ...Object.fromEntries(Object.entries(updates).filter(([, v2]) => v2 !== undefined))
18882
+ };
18883
+ const validated = TimebackConfig.safeParse(next);
18884
+ if (!validated.success) {
18885
+ return { success: false, error: "Update would make config invalid; refusing to write." };
18886
+ }
18887
+ await writeFile2(configPath, generateConfigContent(validated.data), "utf8");
18888
+ if (format) {
18889
+ await formatWithPrettier({ cwd, filePath: configPath, silent });
18890
+ }
18891
+ return { success: true, config: validated.data };
18892
+ } catch (err) {
18893
+ return {
18894
+ success: false,
18895
+ error: err instanceof Error ? err.message : "Unknown error"
18896
+ };
18897
+ }
18898
+ }
18899
+ // ../internal/utils/src/shared/format.ts
18900
+ function pluralize(count, singular, plural) {
18901
+ return count === 1 ? singular : plural ?? `${singular}s`;
18902
+ }
18903
+ // ../internal/utils/src/shared/batching.ts
18904
+ var DEFAULT_BATCH_SIZE = 50;
18905
+ function splitIntoBatches(items, options) {
18906
+ const batchSize = options?.batchSize ?? DEFAULT_BATCH_SIZE;
18907
+ if (batchSize <= 0)
18908
+ return [items.slice()];
18909
+ const batches = [];
18910
+ for (let i = 0;i < items.length; i += batchSize) {
18911
+ batches.push(items.slice(i, i + batchSize));
18912
+ }
18913
+ return batches;
18914
+ }
18915
+ // ../internal/utils/src/shared/promise.ts
18916
+ function extractFulfilled(results) {
18917
+ return results.filter((r2) => r2.status === "fulfilled").flatMap((r2) => r2.value);
18918
+ }
18919
+ function extractRejected(results) {
18920
+ return results.filter((r2) => r2.status === "rejected");
18921
+ }
18922
+ // ../internal/cli-infra/src/config/courses.ts
18406
18923
  function toCourseConfig(course, env2) {
18407
18924
  const config3 = {
18408
18925
  subject: course.subjects?.[0] ?? "None",
@@ -18439,7 +18956,53 @@ function toCourseConfig(course, env2) {
18439
18956
  config3.metadata = meta3;
18440
18957
  }
18441
18958
  }
18442
- return config3;
18959
+ return config3;
18960
+ }
18961
+ async function inferLaunchUrl(client, courseId) {
18962
+ try {
18963
+ const scopedCourse = client.oneroster.courses(courseId);
18964
+ const components = await scopedCourse.components({ where: { status: "active" } });
18965
+ const componentResourceIds = [];
18966
+ for (const component of components) {
18967
+ if (!component.sourcedId)
18968
+ continue;
18969
+ const crs = await client.oneroster.courses.componentResources({
18970
+ where: { "courseComponent.sourcedId": component.sourcedId, status: "active" }
18971
+ });
18972
+ for (const cr of crs) {
18973
+ if (cr.resource?.sourcedId) {
18974
+ componentResourceIds.push(cr.resource.sourcedId);
18975
+ }
18976
+ }
18977
+ }
18978
+ if (componentResourceIds.length === 0) {
18979
+ return;
18980
+ }
18981
+ const resourceIds = [...new Set(componentResourceIds)];
18982
+ const batches = splitIntoBatches(resourceIds);
18983
+ const batchResults = await Promise.allSettled(batches.map((batch) => client.oneroster.resources.listAll({
18984
+ where: { sourcedId: { in: batch } }
18985
+ })));
18986
+ const hasFailures = batchResults.some((r2) => r2.status === "rejected");
18987
+ if (hasFailures) {
18988
+ return;
18989
+ }
18990
+ const resources = extractFulfilled(batchResults);
18991
+ const launchUrls = new Set;
18992
+ for (const resource of resources) {
18993
+ const metadata = resource.metadata;
18994
+ const launchUrl = metadata?.launchUrl;
18995
+ if (typeof launchUrl === "string" && launchUrl.length > 0) {
18996
+ launchUrls.add(launchUrl);
18997
+ }
18998
+ }
18999
+ if (launchUrls.size === 1) {
19000
+ return [...launchUrls][0];
19001
+ }
19002
+ return;
19003
+ } catch {
19004
+ return;
19005
+ }
18443
19006
  }
18444
19007
 
18445
19008
  // ../internal/cli-infra/src/config/index.ts
@@ -18447,6 +19010,83 @@ function getParser(opts = {}) {
18447
19010
  return opts.playcademy ? playcademyParser : timebackParser;
18448
19011
  }
18449
19012
 
19013
+ // ../internal/cli-infra/src/sensors/infer.ts
19014
+ function asRecord(value) {
19015
+ return typeof value === "object" && value !== null ? value : undefined;
19016
+ }
19017
+ function extractCourseIdCandidates(event) {
19018
+ const candidates = new Set;
19019
+ const obj = asRecord(event.object);
19020
+ const course = obj ? asRecord(obj.course) : undefined;
19021
+ const rawCourseId = course?.id;
19022
+ if (typeof rawCourseId === "string" && rawCourseId.length > 0) {
19023
+ candidates.add(rawCourseId);
19024
+ const lastSlash = rawCourseId.lastIndexOf("/");
19025
+ if (lastSlash >= 0 && lastSlash < rawCourseId.length - 1) {
19026
+ candidates.add(rawCourseId.slice(lastSlash + 1));
19027
+ }
19028
+ const coursesMarker = "/courses/";
19029
+ const idx = rawCourseId.indexOf(coursesMarker);
19030
+ if (idx >= 0) {
19031
+ const suffix = rawCourseId.slice(idx + coursesMarker.length);
19032
+ if (suffix)
19033
+ candidates.add(suffix);
19034
+ }
19035
+ }
19036
+ const extensions = asRecord(event.extensions);
19037
+ const extCourseId = extensions?.courseId;
19038
+ if (typeof extCourseId === "string" && extCourseId.length > 0) {
19039
+ candidates.add(extCourseId);
19040
+ }
19041
+ return [...candidates];
19042
+ }
19043
+ function toMillis(isoLike) {
19044
+ if (typeof isoLike !== "string") {
19045
+ return;
19046
+ }
19047
+ const ms = Date.parse(isoLike);
19048
+ return Number.isFinite(ms) ? ms : undefined;
19049
+ }
19050
+ async function inferSensorsFromCaliperEvents(client, options) {
19051
+ const { courseIds, maxEvents = 2000, pageSize = 2000, startDate } = options;
19052
+ const target = new Set(courseIds.filter(Boolean));
19053
+ if (target.size === 0) {
19054
+ return [];
19055
+ }
19056
+ const stats = new Map;
19057
+ const limit = Math.min(pageSize, maxEvents);
19058
+ const { events } = await client.caliper.events.list({ limit, startDate });
19059
+ const pageEvents = events ?? [];
19060
+ for (const raw of pageEvents) {
19061
+ const sensor = raw.sensor;
19062
+ if (typeof sensor !== "string" || sensor.length === 0) {
19063
+ continue;
19064
+ }
19065
+ const candidates = extractCourseIdCandidates(raw);
19066
+ const matches = candidates.some((c) => target.has(c));
19067
+ if (!matches) {
19068
+ continue;
19069
+ }
19070
+ const latestMs = toMillis(raw.eventTime);
19071
+ const current = stats.get(sensor);
19072
+ if (current) {
19073
+ current.count += 1;
19074
+ if (latestMs !== undefined) {
19075
+ current.latestMs = Math.max(current.latestMs ?? 0, latestMs);
19076
+ }
19077
+ } else {
19078
+ stats.set(sensor, { count: 1, latestMs });
19079
+ }
19080
+ }
19081
+ const sorted = [...stats.entries()].sort((a, b3) => {
19082
+ const countDiff = b3[1].count - a[1].count;
19083
+ if (countDiff !== 0)
19084
+ return countDiff;
19085
+ return (b3[1].latestMs ?? 0) - (a[1].latestMs ?? 0);
19086
+ }).map(([sensor]) => sensor);
19087
+ return sorted;
19088
+ }
19089
+
18450
19090
  // ../internal/cli-infra/src/search/search.ts
18451
19091
  function searchCourses(client, query, max = 50) {
18452
19092
  return client.oneroster.courses.listAll({
@@ -18469,11 +19109,11 @@ async function promptSelectEnv(configuredEnvs) {
18469
19109
  }
18470
19110
  async function promptAppName() {
18471
19111
  const name = await he({
18472
- message: "App name",
19112
+ message: "Search for your app",
18473
19113
  placeholder: "My Timeback App",
18474
19114
  validate: (value) => {
18475
19115
  if (!value.trim())
18476
- return "App name is required";
19116
+ return "Please enter a search term";
18477
19117
  }
18478
19118
  });
18479
19119
  if (pD(name))
@@ -18578,6 +19218,48 @@ async function selectCourses(client, initialResults, filterFn) {
18578
19218
  results = filter(moreResults);
18579
19219
  }
18580
19220
  }
19221
+ async function enrichCoursesWithLaunchUrls(client, courses) {
19222
+ if (courses.length === 0)
19223
+ return { courses, inferredSensors: [] };
19224
+ const s = Y2();
19225
+ let didStop = false;
19226
+ const stopOnce = (message) => {
19227
+ if (didStop)
19228
+ return;
19229
+ s.stop(message);
19230
+ didStop = true;
19231
+ };
19232
+ s.start("Fetching course details...");
19233
+ const enriched = [];
19234
+ let inferredSensors = [];
19235
+ try {
19236
+ for (const course of courses) {
19237
+ const courseId = course.ids?.staging ?? course.ids?.production;
19238
+ if (!courseId) {
19239
+ enriched.push(course);
19240
+ continue;
19241
+ }
19242
+ const launchUrl = await inferLaunchUrl(client, courseId);
19243
+ if (launchUrl && !course.launchUrl) {
19244
+ enriched.push({ ...course, launchUrl });
19245
+ } else {
19246
+ enriched.push(course);
19247
+ }
19248
+ }
19249
+ const courseIds = enriched.map((c) => c.ids?.staging ?? c.ids?.production).filter((id) => typeof id === "string" && id.length > 0);
19250
+ if (courseIds.length > 0) {
19251
+ const startDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString();
19252
+ try {
19253
+ inferredSensors = await inferSensorsFromCaliperEvents(client, { courseIds, startDate });
19254
+ } catch {}
19255
+ }
19256
+ stopOnce("Course details loaded");
19257
+ return { courses: enriched, inferredSensors };
19258
+ } catch (error48) {
19259
+ stopOnce("Failed to load course details");
19260
+ throw error48;
19261
+ }
19262
+ }
18581
19263
  async function searchAndSelectCourses(options) {
18582
19264
  const { client, environment, appName: existingAppName, excludeCourseIds = [] } = options;
18583
19265
  const filterExcluded = (courses) => {
@@ -18600,19 +19282,20 @@ async function searchAndSelectCourses(options) {
18600
19282
  return { success: false, cancelled: true };
18601
19283
  }
18602
19284
  const courseConfigs = courses.map((c) => toCourseConfig(c, environment));
18603
- M2.success(`Selected ${courseConfigs.length} course${courseConfigs.length === 1 ? "" : "s"}`);
19285
+ const { courses: enrichedConfigs, inferredSensors } = await enrichCoursesWithLaunchUrls(client, courseConfigs);
18604
19286
  return {
18605
19287
  success: true,
18606
19288
  appName,
18607
- courses: courseConfigs,
18608
- environment
19289
+ courses: enrichedConfigs,
19290
+ environment,
19291
+ inferredSensors
18609
19292
  };
18610
19293
  } catch (error48) {
18611
19294
  const message = error48 instanceof Error ? error48.message : "Unknown error";
18612
19295
  return { success: false, error: message };
18613
19296
  }
18614
19297
  }
18615
- // ../internal/cli-infra/src/sensors/prompts.ts
19298
+ // ../internal/cli-infra/src/sensors/utils.ts
18616
19299
  function isValidUrl(url2) {
18617
19300
  try {
18618
19301
  return Boolean(new URL(url2));
@@ -18620,15 +19303,60 @@ function isValidUrl(url2) {
18620
19303
  return false;
18621
19304
  }
18622
19305
  }
19306
+ function getEffectiveLaunchUrl(course, config3, env2) {
19307
+ const envOverride = course.overrides?.[env2]?.launchUrl;
19308
+ if (envOverride)
19309
+ return envOverride;
19310
+ if (course.launchUrl)
19311
+ return course.launchUrl;
19312
+ return config3.launchUrl;
19313
+ }
19314
+ function getEffectiveSensorForCourse(course, config3, env2) {
19315
+ const envSensor = course.overrides?.[env2]?.sensor;
19316
+ if (envSensor) {
19317
+ return envSensor;
19318
+ }
19319
+ if (course.sensor) {
19320
+ return course.sensor;
19321
+ }
19322
+ if (config3.sensor) {
19323
+ return config3.sensor;
19324
+ }
19325
+ const launchUrl = getEffectiveLaunchUrl(course, config3, env2);
19326
+ if (launchUrl) {
19327
+ return deriveSensorFromLaunchUrl(launchUrl);
19328
+ }
19329
+ return;
19330
+ }
19331
+ function deriveEffectiveSensors(config3, env2) {
19332
+ const sensors = new Set;
19333
+ for (const course of config3.courses) {
19334
+ const sensor = getEffectiveSensorForCourse(course, config3, env2);
19335
+ if (sensor) {
19336
+ sensors.add(sensor);
19337
+ }
19338
+ }
19339
+ return Array.from(sensors);
19340
+ }
19341
+
19342
+ // ../internal/cli-infra/src/sensors/prompts.ts
19343
+ function deriveSensorFromLaunchUrl(launchUrl) {
19344
+ try {
19345
+ const url2 = new URL(launchUrl);
19346
+ return url2.origin;
19347
+ } catch {
19348
+ return;
19349
+ }
19350
+ }
18623
19351
  async function promptSensor(options) {
18624
19352
  const { defaultValue } = options ?? {};
18625
19353
  const input = await he({
18626
- message: "Sensor URL (your app origin, used for activity tracking)",
19354
+ message: "Sensor URL (for activity metrics)",
18627
19355
  placeholder: "https://myapp.example.com",
18628
- defaultValue,
19356
+ initialValue: defaultValue,
18629
19357
  validate: (value) => {
18630
19358
  if (!value.trim()) {
18631
- return "Sensor URL is required for activity tracking";
19359
+ return "Sensor URL is required";
18632
19360
  }
18633
19361
  if (!isValidUrl(value.trim())) {
18634
19362
  return "Invalid URL format";
@@ -18641,25 +19369,6 @@ async function promptSensor(options) {
18641
19369
  }
18642
19370
  return input.trim();
18643
19371
  }
18644
- // ../internal/cli-infra/src/sensors/utils.ts
18645
- function deriveSensorsFromConfig(config3) {
18646
- const sensors = new Set;
18647
- if (config3.sensor) {
18648
- sensors.add(config3.sensor);
18649
- }
18650
- for (const course of config3.courses) {
18651
- if (course.sensor) {
18652
- sensors.add(course.sensor);
18653
- }
18654
- if (course.overrides?.staging?.sensor) {
18655
- sensors.add(course.overrides.staging.sensor);
18656
- }
18657
- if (course.overrides?.production?.sensor) {
18658
- sensors.add(course.overrides.production.sensor);
18659
- }
18660
- }
18661
- return Array.from(sensors);
18662
- }
18663
19372
  // src/cli/commands/credentials/add.ts
18664
19373
  async function addCredentials(options = {}) {
18665
19374
  const { exitOnComplete = true, inline = false } = options;
@@ -18962,8 +19671,10 @@ async function showCredentialsMenu(options = {}) {
18962
19671
  }
18963
19672
  }
18964
19673
  // src/cli/commands/credentials/lib/initial.ts
18965
- async function promptInitialCredentials() {
18966
- intro("Timeback Studio");
19674
+ async function promptInitialCredentials(options = {}) {
19675
+ if (!options.skipIntro) {
19676
+ intro("Timeback Studio");
19677
+ }
18967
19678
  Me("No credentials found. You need to configure at least one environment.", "First-time setup");
18968
19679
  const environments = await fe({
18969
19680
  message: "Which environments would you like to configure?",
@@ -18985,12 +19696,12 @@ async function promptInitialCredentials() {
18985
19696
  }
18986
19697
  }
18987
19698
  const configuredEnvs = environments;
18988
- let defaultEnv;
19699
+ let selectedEnv;
18989
19700
  if (configuredEnvs.length === 1) {
18990
- defaultEnv = configuredEnvs[0];
19701
+ selectedEnv = configuredEnvs[0];
18991
19702
  } else {
18992
19703
  const selected = await ve({
18993
- message: "Which environment should be used by default?",
19704
+ message: "Which environment would you like to use for this session?",
18994
19705
  options: configuredEnvs.map((env2) => ({
18995
19706
  value: env2,
18996
19707
  label: env2.charAt(0).toUpperCase() + env2.slice(1)
@@ -19000,11 +19711,10 @@ async function promptInitialCredentials() {
19000
19711
  outro.cancelled();
19001
19712
  process.exit(0);
19002
19713
  }
19003
- defaultEnv = selected;
19714
+ selectedEnv = selected;
19004
19715
  }
19005
- await saveDefaultEnvironment(defaultEnv);
19006
19716
  Me(`Saved to ${dim(getCredentialsPath())}`, green("Setup complete"));
19007
- return defaultEnv;
19717
+ return selectedEnv;
19008
19718
  }
19009
19719
  // src/config/constants.ts
19010
19720
  var configValues = {
@@ -19151,54 +19861,61 @@ async function fetchCourses(creds, env2, ids) {
19151
19861
  client.close();
19152
19862
  }
19153
19863
  }
19154
- // src/cli/lib/credentials.ts
19155
- async function loadAllCredentials() {
19156
- const configuredEnvs = await getConfiguredEnvironments();
19157
- const credentials = {};
19158
- for (const env2 of configuredEnvs) {
19159
- const result = await loadCredentials(env2);
19160
- if (result.success) {
19161
- credentials[env2] = result.credentials;
19864
+ function isManagedCourse(course) {
19865
+ const managedBy = course.metadata?.managedBy;
19866
+ return typeof managedBy === "string" && managedBy.startsWith("timeback@");
19867
+ }
19868
+ async function checkCoursesManaged(creds, env2, ids) {
19869
+ if (ids.length === 0) {
19870
+ return { allManaged: true, unmanagedCourses: [] };
19871
+ }
19872
+ const client = new TimebackClient2({
19873
+ env: env2,
19874
+ auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
19875
+ });
19876
+ try {
19877
+ const s = Y2();
19878
+ s.start(`Fetching remote context...`);
19879
+ const courses = await fetchCoursesByIds(client, ids);
19880
+ s.stop(`Remote context fetched`);
19881
+ const unmanagedCourses = [];
19882
+ const fetchedIds = new Set;
19883
+ const courseMap = new Map;
19884
+ for (const course of courses) {
19885
+ if (course.sourcedId) {
19886
+ fetchedIds.add(course.sourcedId);
19887
+ courseMap.set(course.sourcedId, course);
19888
+ }
19889
+ if (!isManagedCourse(course) && course.sourcedId) {
19890
+ unmanagedCourses.push({ id: course.sourcedId, title: course.title });
19891
+ }
19892
+ }
19893
+ for (const id of ids) {
19894
+ if (!fetchedIds.has(id)) {
19895
+ unmanagedCourses.push({ id });
19896
+ }
19162
19897
  }
19898
+ const allManaged = unmanagedCourses.length === 0;
19899
+ return { allManaged, unmanagedCourses };
19900
+ } finally {
19901
+ client.close();
19163
19902
  }
19164
- return credentials;
19165
19903
  }
19166
- async function resolveDefaultEnvironment(override) {
19167
- if (override)
19168
- return override;
19169
- const saved = await getDefaultEnvironment();
19170
- if (saved)
19171
- return saved;
19172
- const configured = await getConfiguredEnvironments();
19173
- return configured[0] ?? "staging";
19174
- }
19175
- async function handleCredentialSetup() {
19176
- const selectedEnv = await promptInitialCredentials();
19177
- if (!selectedEnv)
19904
+ // src/cli/lib/credentials.ts
19905
+ async function handleCredentialSetup(options = {}) {
19906
+ const selectedEnv = await promptInitialCredentials({ skipIntro: options.skipIntro });
19907
+ if (!selectedEnv) {
19178
19908
  return null;
19909
+ }
19179
19910
  const result = await loadCredentials(selectedEnv);
19180
- if (!result.success)
19911
+ if (!result.success) {
19181
19912
  return null;
19913
+ }
19182
19914
  return {
19183
19915
  credentials: { [selectedEnv]: result.credentials },
19184
- defaultEnvironment: selectedEnv
19916
+ selectedEnvironment: selectedEnv
19185
19917
  };
19186
19918
  }
19187
- async function ensureCredentials(env2, credentials) {
19188
- const existing = credentials[env2];
19189
- if (existing)
19190
- return existing;
19191
- intro("Timeback Studio");
19192
- Me(`No credentials configured for ${env2}.`, "Credential setup required");
19193
- const newCreds = await promptForCredentials(env2);
19194
- if (!newCreds)
19195
- return null;
19196
- await saveCredentials(env2, newCreds);
19197
- M2.success(`${env2} credentials saved`);
19198
- Me(`Saved to ${dim(getCredentialsPath())}`, green("Setup complete"));
19199
- credentials[env2] = newCreds;
19200
- return newCreds;
19201
- }
19202
19919
  // src/cli/lib/onboarding/import.ts
19203
19920
  import { TimebackClient as TimebackClient3 } from "@timeback/core";
19204
19921
  async function promptImportApp(credentials, configuredEnvs) {
@@ -19260,6 +19977,46 @@ async function promptNoConfig(credentials, configuredEnvs, opts = {}) {
19260
19977
  }
19261
19978
  // src/cli/commands/serve/config.ts
19262
19979
  import { basename as basename2 } from "node:path";
19980
+ async function resolveConfigSource(courseIds, opts, defaultEnvironment) {
19981
+ const parserOpts = { playcademy: opts.playcademy };
19982
+ let configuredEnvs = await getConfiguredEnvironments();
19983
+ let credentials = {};
19984
+ if (courseIds.length > 0) {
19985
+ if (configuredEnvs.length > 0) {
19986
+ credentials = await loadAllCredentials();
19987
+ }
19988
+ const resolved2 = await resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnvironment);
19989
+ return resolved2 ? { resolved: resolved2, credentials, configuredEnvs } : null;
19990
+ }
19991
+ const configResult = await loadConfig2(parserOpts);
19992
+ if (configResult.success) {
19993
+ return {
19994
+ resolved: {
19995
+ userConfig: configResult.config,
19996
+ environment: defaultEnvironment,
19997
+ configFile: basename2(configResult.config.path)
19998
+ },
19999
+ credentials,
20000
+ configuredEnvs
20001
+ };
20002
+ }
20003
+ const isMissingConfig = configResult.error.includes("No") && configResult.error.includes("config found");
20004
+ if (!isMissingConfig) {
20005
+ printError3(configResult.error, parserOpts);
20006
+ return null;
20007
+ }
20008
+ if (configuredEnvs.length === 0) {
20009
+ const result = await handleCredentialSetup({ skipIntro: true });
20010
+ if (!result)
20011
+ return null;
20012
+ credentials = result.credentials;
20013
+ configuredEnvs = Object.keys(credentials);
20014
+ } else {
20015
+ credentials = await loadAllCredentials();
20016
+ }
20017
+ const resolved = await resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnvironment);
20018
+ return resolved ? { resolved, credentials, configuredEnvs } : null;
20019
+ }
19263
20020
  function buildUserConfigFromImport(result) {
19264
20021
  const { name, courses, sensor } = result;
19265
20022
  const courseIds = {
@@ -19301,7 +20058,7 @@ function buildUserConfigFromCourses(courses) {
19301
20058
  };
19302
20059
  }
19303
20060
  async function resolveFromCourseIds(courseIds, env2, credentials, configuredEnvs) {
19304
- const creds = await ensureCredentials(env2, credentials);
20061
+ const creds = await ensureCredentials({ env: env2, credentials, skipIntro: true });
19305
20062
  if (!creds) {
19306
20063
  return null;
19307
20064
  }
@@ -19335,7 +20092,7 @@ async function resolveFromConfigOrImport(credentials, configuredEnvs, defaultEnv
19335
20092
  }
19336
20093
  const isMissingConfig = configResult.error.includes("No") && configResult.error.includes("config found");
19337
20094
  if (isMissingConfig) {
19338
- const importResult = await promptNoConfig(credentials, configuredEnvs);
20095
+ const importResult = await promptNoConfig(credentials, configuredEnvs, { skipIntro: true });
19339
20096
  if (!importResult)
19340
20097
  return null;
19341
20098
  return {
@@ -19349,12 +20106,60 @@ async function resolveFromConfigOrImport(credentials, configuredEnvs, defaultEnv
19349
20106
  function parseSensors(sensors) {
19350
20107
  return sensors.split(",").map((s) => s.trim()).filter(Boolean);
19351
20108
  }
19352
- function getEffectiveSensors(config3, opts) {
20109
+ function formatCourseExampleLines(courses, max = 3) {
20110
+ if (courses.length === 0)
20111
+ return [];
20112
+ const shown = courses.slice(0, max);
20113
+ const remaining = courses.length - shown.length;
20114
+ return [
20115
+ ...shown.map((c) => `- ${cyan(c.title ?? c.id)}`),
20116
+ ...remaining > 0 ? [`- ${dim(`(+${remaining} more)`)}`] : []
20117
+ ];
20118
+ }
20119
+ async function resolveSensors(options) {
20120
+ const { config: config3, env: env2, opts, creds } = options;
19353
20121
  if (opts.sensors) {
19354
20122
  return parseSensors(opts.sensors);
19355
20123
  }
19356
- const derived = deriveSensorsFromConfig(config3);
19357
- return derived.length > 0 ? derived : undefined;
20124
+ if (config3.sensor) {
20125
+ return [config3.sensor];
20126
+ }
20127
+ const courseIds = config3.courseIds[env2] ?? [];
20128
+ if (courseIds.length === 0) {
20129
+ return null;
20130
+ }
20131
+ const { allManaged, unmanagedCourses } = await checkCoursesManaged(creds, env2, courseIds);
20132
+ if (allManaged) {
20133
+ const derived = deriveEffectiveSensors(config3, env2);
20134
+ if (derived.length > 0) {
20135
+ return derived;
20136
+ }
20137
+ Me(["Add a top-level `sensor` to `timeback.config.json` or pass `--sensors`."].join(`
20138
+ `), "Next time");
20139
+ M2.error("Could not determine sensor(s) from config file");
20140
+ return null;
20141
+ }
20142
+ const count = unmanagedCourses.length;
20143
+ Me([
20144
+ `${count} ${pluralize(count, "course")} ${count === 1 ? "is" : "are"} unmanaged by Timeback CLI:`,
20145
+ "",
20146
+ ...formatCourseExampleLines(unmanagedCourses)
20147
+ ].join(`
20148
+ `), "Sensor required");
20149
+ const sensor = await promptSensor({
20150
+ defaultValue: config3.launchUrl ? deriveSensorFromLaunchUrl(config3.launchUrl) : undefined
20151
+ });
20152
+ if (sensor === undefined) {
20153
+ return null;
20154
+ }
20155
+ config3.sensor = sensor;
20156
+ if (config3.path?.endsWith(".json")) {
20157
+ const result = await updateTimebackConfigFile(config3.path, { sensor });
20158
+ if (result.success) {
20159
+ M2.success(`Saved sensor to ${basename2(config3.path)}`);
20160
+ }
20161
+ }
20162
+ return [sensor];
19358
20163
  }
19359
20164
  async function resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnv) {
19360
20165
  const parserOpts = { playcademy: opts.playcademy };
@@ -19362,7 +20167,59 @@ async function resolveConfig(courseIds, opts, credentials, configuredEnvs, defau
19362
20167
  return resolved;
19363
20168
  }
19364
20169
 
19365
- // ../../node_modules/.bun/@hono+node-server@1.19.9+fd698e00a3b2abce/node_modules/@hono/node-server/dist/index.mjs
20170
+ // src/cli/commands/serve/env.ts
20171
+ async function resolveServeEnvironment(options) {
20172
+ const { userConfig, hasEnvOverride, envOverride } = options;
20173
+ if (hasEnvOverride && envOverride) {
20174
+ return envOverride;
20175
+ }
20176
+ const stagingCount = userConfig.courseIds.staging?.length ?? 0;
20177
+ const productionCount = userConfig.courseIds.production?.length ?? 0;
20178
+ if (stagingCount === 0 && productionCount === 0) {
20179
+ return null;
20180
+ }
20181
+ if (stagingCount > 0 && productionCount === 0) {
20182
+ return "staging";
20183
+ }
20184
+ if (productionCount > 0 && stagingCount === 0) {
20185
+ return "production";
20186
+ }
20187
+ const selected = await ve({
20188
+ message: "Which environment would you like to use?",
20189
+ options: [
20190
+ {
20191
+ value: "staging",
20192
+ label: `Staging (${stagingCount} course${stagingCount === 1 ? "" : "s"})`
20193
+ },
20194
+ {
20195
+ value: "production",
20196
+ label: `Production (${productionCount} course${productionCount === 1 ? "" : "s"})`
20197
+ }
20198
+ ]
20199
+ });
20200
+ if (isCancelled(selected)) {
20201
+ outro.cancelled();
20202
+ process.exit(0);
20203
+ }
20204
+ return selected;
20205
+ }
20206
+ async function promptEnvironmentForCourseIds(count) {
20207
+ const plural = count !== 1;
20208
+ const selected = await ve({
20209
+ message: plural ? "Which environment are these course IDs from?" : "Which environment is this course ID from?",
20210
+ options: [
20211
+ { value: "staging", label: "Staging" },
20212
+ { value: "production", label: "Production" }
20213
+ ]
20214
+ });
20215
+ if (isCancelled(selected)) {
20216
+ outro.cancelled();
20217
+ process.exit(0);
20218
+ }
20219
+ return selected;
20220
+ }
20221
+
20222
+ // ../../node_modules/.bun/@hono+node-server@1.19.9+115df24086ffac64/node_modules/@hono/node-server/dist/index.mjs
19366
20223
  import { createServer as createServerHTTP } from "http";
19367
20224
  import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
19368
20225
  import { Http2ServerRequest } from "http2";
@@ -19758,7 +20615,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
19758
20615
  });
19759
20616
  if (!chunk) {
19760
20617
  if (i === 1) {
19761
- await new Promise((resolve2) => setTimeout(resolve2));
20618
+ await new Promise((resolve5) => setTimeout(resolve5));
19762
20619
  maxReadCount = 3;
19763
20620
  continue;
19764
20621
  }
@@ -19896,7 +20753,7 @@ var serve = (options, listeningListener) => {
19896
20753
  return server;
19897
20754
  };
19898
20755
 
19899
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/compose.js
20756
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/compose.js
19900
20757
  var compose = (middleware, onError, onNotFound) => {
19901
20758
  return (context, next) => {
19902
20759
  let index = -1;
@@ -19940,10 +20797,10 @@ var compose = (middleware, onError, onNotFound) => {
19940
20797
  };
19941
20798
  };
19942
20799
 
19943
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/request/constants.js
20800
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/request/constants.js
19944
20801
  var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
19945
20802
 
19946
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/utils/body.js
20803
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/body.js
19947
20804
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
19948
20805
  const { all = false, dot = false } = options;
19949
20806
  const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
@@ -20011,7 +20868,7 @@ var handleParsingNestedValues = (form, key, value) => {
20011
20868
  });
20012
20869
  };
20013
20870
 
20014
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/utils/url.js
20871
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/url.js
20015
20872
  var splitPath = (path) => {
20016
20873
  const paths = path.split("/");
20017
20874
  if (paths[0] === "") {
@@ -20209,7 +21066,7 @@ var getQueryParams = (url2, key) => {
20209
21066
  };
20210
21067
  var decodeURIComponent_ = decodeURIComponent;
20211
21068
 
20212
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/request.js
21069
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/request.js
20213
21070
  var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
20214
21071
  var HonoRequest = class {
20215
21072
  raw;
@@ -20320,7 +21177,7 @@ var HonoRequest = class {
20320
21177
  }
20321
21178
  };
20322
21179
 
20323
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/utils/html.js
21180
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/html.js
20324
21181
  var HtmlEscapedCallbackPhase = {
20325
21182
  Stringify: 1,
20326
21183
  BeforeStream: 2,
@@ -20358,7 +21215,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
20358
21215
  }
20359
21216
  };
20360
21217
 
20361
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/context.js
21218
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/context.js
20362
21219
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
20363
21220
  var setDefaultContentType = (contentType, headers) => {
20364
21221
  return {
@@ -20524,7 +21381,7 @@ var Context = class {
20524
21381
  };
20525
21382
  };
20526
21383
 
20527
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router.js
21384
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router.js
20528
21385
  var METHOD_NAME_ALL = "ALL";
20529
21386
  var METHOD_NAME_ALL_LOWERCASE = "all";
20530
21387
  var METHODS = ["get", "post", "put", "delete", "options", "patch"];
@@ -20532,10 +21389,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
20532
21389
  var UnsupportedPathError = class extends Error {
20533
21390
  };
20534
21391
 
20535
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/utils/constants.js
21392
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/constants.js
20536
21393
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
20537
21394
 
20538
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/hono-base.js
21395
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/hono-base.js
20539
21396
  var notFoundHandler = (c) => {
20540
21397
  return c.text("404 Not Found", 404);
20541
21398
  };
@@ -20754,7 +21611,7 @@ var Hono = class _Hono {
20754
21611
  };
20755
21612
  };
20756
21613
 
20757
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/reg-exp-router/matcher.js
21614
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
20758
21615
  var emptyParam = [];
20759
21616
  function match(method, path) {
20760
21617
  const matchers = this.buildAllMatchers();
@@ -20775,7 +21632,7 @@ function match(method, path) {
20775
21632
  return match2(method, path);
20776
21633
  }
20777
21634
 
20778
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/reg-exp-router/node.js
21635
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/node.js
20779
21636
  var LABEL_REG_EXP_STR = "[^/]+";
20780
21637
  var ONLY_WILDCARD_REG_EXP_STR = ".*";
20781
21638
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
@@ -20879,7 +21736,7 @@ var Node = class _Node {
20879
21736
  }
20880
21737
  };
20881
21738
 
20882
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/reg-exp-router/trie.js
21739
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/trie.js
20883
21740
  var Trie = class {
20884
21741
  #context = { varIndex: 0 };
20885
21742
  #root = new Node;
@@ -20935,7 +21792,7 @@ var Trie = class {
20935
21792
  }
20936
21793
  };
20937
21794
 
20938
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/reg-exp-router/router.js
21795
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/router.js
20939
21796
  var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
20940
21797
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
20941
21798
  function buildWildcardRegExp(path) {
@@ -21100,7 +21957,7 @@ var RegExpRouter = class {
21100
21957
  }
21101
21958
  };
21102
21959
 
21103
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
21960
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
21104
21961
  var PreparedRegExpRouter = class {
21105
21962
  name = "PreparedRegExpRouter";
21106
21963
  #matchers;
@@ -21172,7 +22029,7 @@ var PreparedRegExpRouter = class {
21172
22029
  match = match;
21173
22030
  };
21174
22031
 
21175
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/smart-router/router.js
22032
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/smart-router/router.js
21176
22033
  var SmartRouter = class {
21177
22034
  name = "SmartRouter";
21178
22035
  #routers = [];
@@ -21227,7 +22084,7 @@ var SmartRouter = class {
21227
22084
  }
21228
22085
  };
21229
22086
 
21230
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/trie-router/node.js
22087
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/trie-router/node.js
21231
22088
  var emptyParams = /* @__PURE__ */ Object.create(null);
21232
22089
  var Node2 = class _Node2 {
21233
22090
  #methods;
@@ -21381,7 +22238,7 @@ var Node2 = class _Node2 {
21381
22238
  }
21382
22239
  };
21383
22240
 
21384
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/router/trie-router/router.js
22241
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/router/trie-router/router.js
21385
22242
  var TrieRouter = class {
21386
22243
  name = "TrieRouter";
21387
22244
  #node;
@@ -21403,7 +22260,7 @@ var TrieRouter = class {
21403
22260
  }
21404
22261
  };
21405
22262
 
21406
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/hono.js
22263
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/hono.js
21407
22264
  var Hono2 = class extends Hono {
21408
22265
  constructor(options = {}) {
21409
22266
  super(options);
@@ -21413,7 +22270,7 @@ var Hono2 = class extends Hono {
21413
22270
  }
21414
22271
  };
21415
22272
 
21416
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/middleware/cors/index.js
22273
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/middleware/cors/index.js
21417
22274
  var cors = (options) => {
21418
22275
  const defaults = {
21419
22276
  origin: "*",
@@ -21828,10 +22685,6 @@ class Logger {
21828
22685
  function createLogger(options = {}) {
21829
22686
  return new Logger(options);
21830
22687
  }
21831
- // ../internal/utils/src/shared/format.ts
21832
- function pluralize(count, singular, plural) {
21833
- return count === 1 ? singular : plural ?? `${singular}s`;
21834
- }
21835
22688
  // src/server/services/bootstrap.ts
21836
22689
  var log = createLogger({ scope: "studio:service:bootstrap" });
21837
22690
 
@@ -21848,7 +22701,7 @@ class BootstrapService {
21848
22701
  const courses = await this.fetchCourses(courseIds, errors3);
21849
22702
  const courseStructure = await this.fetchCourseStructure(courses, errors3);
21850
22703
  const [enrollmentData, enrichedComponentResources] = await Promise.all([
21851
- this.fetchEnrollments(courseStructure.classes),
22704
+ this.fetchEnrollments(courseStructure.classes, errors3),
21852
22705
  this.fetchResources(courseStructure.componentResources, errors3)
21853
22706
  ]);
21854
22707
  const enrichedEnrollments = await this.enrichEnrollmentsWithUsers(enrollmentData, errors3);
@@ -21940,9 +22793,33 @@ class BootstrapService {
21940
22793
  return enrollmentData.enrollments;
21941
22794
  }
21942
22795
  try {
21943
- const users = await this.client.oneroster.users.listAll({
21944
- where: { sourcedId: { in: studentUserIds } }
22796
+ const batches = splitIntoBatches(studentUserIds);
22797
+ log.debug("Fetching users in batches", {
22798
+ totalIds: studentUserIds.length,
22799
+ batchCount: batches.length
21945
22800
  });
22801
+ const batchResults = await Promise.allSettled(batches.map((batch) => this.client.oneroster.users.listAll({
22802
+ where: { sourcedId: { in: batch } }
22803
+ })));
22804
+ const failed = extractRejected(batchResults);
22805
+ if (failed.length === batches.length) {
22806
+ const firstReason = failed[0]?.reason;
22807
+ const message = firstReason?.message ?? String(firstReason);
22808
+ log.warn("All user batches failed", {
22809
+ failedCount: failed.length,
22810
+ totalBatches: batches.length,
22811
+ reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
22812
+ });
22813
+ errors3.push(createStudioError("USERS_FETCH_FAILED", message));
22814
+ } else if (failed.length > 0) {
22815
+ log.warn("Some user batches failed", {
22816
+ failedCount: failed.length,
22817
+ totalBatches: batches.length,
22818
+ reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
22819
+ });
22820
+ errors3.push(createStudioError("USERS_FETCH_PARTIAL", `${failed.length} of ${batches.length} user batches failed`));
22821
+ }
22822
+ const users = extractFulfilled(batchResults);
21946
22823
  const userMap = new Map(users.map((u2) => [u2.sourcedId, u2]));
21947
22824
  log.debug("Enriched enrollments with user details", { count: users.length });
21948
22825
  return enrollmentData.enrollments.map((enrollment) => ({
@@ -21963,9 +22840,33 @@ class BootstrapService {
21963
22840
  }
21964
22841
  const uniqueResourceIds = [...new Set(resourceIds)];
21965
22842
  try {
21966
- const resources = await this.client.oneroster.resources.listAll({
21967
- where: { sourcedId: { in: uniqueResourceIds } }
22843
+ const batches = splitIntoBatches(uniqueResourceIds);
22844
+ log.debug("Fetching resources in batches", {
22845
+ totalIds: uniqueResourceIds.length,
22846
+ batchCount: batches.length
21968
22847
  });
22848
+ const batchResults = await Promise.allSettled(batches.map((batch) => this.client.oneroster.resources.listAll({
22849
+ where: { sourcedId: { in: batch } }
22850
+ })));
22851
+ const failed = extractRejected(batchResults);
22852
+ if (failed.length === batches.length) {
22853
+ const firstReason = failed[0]?.reason;
22854
+ const message = firstReason?.message ?? String(firstReason);
22855
+ log.warn("All resource batches failed", {
22856
+ failedCount: failed.length,
22857
+ totalBatches: batches.length,
22858
+ reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
22859
+ });
22860
+ errors3.push(createStudioError("RESOURCES_FETCH_FAILED", message));
22861
+ } else if (failed.length > 0) {
22862
+ log.warn("Some resource batches failed", {
22863
+ failedCount: failed.length,
22864
+ totalBatches: batches.length,
22865
+ reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
22866
+ });
22867
+ errors3.push(createStudioError("RESOURCES_FETCH_PARTIAL", `${failed.length} of ${batches.length} resource batches failed`));
22868
+ }
22869
+ const resources = extractFulfilled(batchResults);
21969
22870
  const resourceMap = new Map(resources.map((r2) => [r2.sourcedId, r2]));
21970
22871
  return componentResources.map((cr) => ({
21971
22872
  ...cr,
@@ -22008,8 +22909,10 @@ class BootstrapService {
22008
22909
  return null;
22009
22910
  }
22010
22911
  }
22011
- async fetchEnrollments(classes) {
22012
- const classIds = classes.map((c) => c.sourcedId).filter((id) => !!id);
22912
+ async fetchEnrollments(classes, errors3) {
22913
+ const classIds = [
22914
+ ...new Set(classes.map((c) => c.sourcedId).filter((id) => !!id))
22915
+ ];
22013
22916
  const enrollments = [];
22014
22917
  const studentIds = new Set;
22015
22918
  const teacherIds = new Set;
@@ -22018,11 +22921,35 @@ class BootstrapService {
22018
22921
  return { enrollments, studentIds, teacherIds, studentsByClass };
22019
22922
  }
22020
22923
  try {
22021
- const fetchedEnrollments = await this.client.oneroster.enrollments.listAll({
22022
- where: {
22023
- "class.sourcedId": { in: classIds }
22024
- }
22924
+ const batches = splitIntoBatches(classIds);
22925
+ log.debug("Fetching enrollments in batches", {
22926
+ totalClassIds: classIds.length,
22927
+ batchCount: batches.length
22025
22928
  });
22929
+ const batchResults = await Promise.allSettled(batches.map((batch) => this.client.oneroster.enrollments.listAll({
22930
+ where: {
22931
+ "class.sourcedId": { in: batch }
22932
+ }
22933
+ })));
22934
+ const failed = extractRejected(batchResults);
22935
+ if (failed.length === batches.length) {
22936
+ const firstReason = failed[0]?.reason;
22937
+ const message = firstReason?.message ?? String(firstReason);
22938
+ log.warn("All enrollment batches failed", {
22939
+ failedCount: failed.length,
22940
+ totalBatches: batches.length,
22941
+ reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
22942
+ });
22943
+ errors3.push(createStudioError("ENROLLMENTS_FETCH_FAILED", message));
22944
+ } else if (failed.length > 0) {
22945
+ log.warn("Some enrollment batches failed", {
22946
+ failedCount: failed.length,
22947
+ totalBatches: batches.length,
22948
+ reasons: failed.map((r2) => r2.reason?.message ?? String(r2.reason))
22949
+ });
22950
+ errors3.push(createStudioError("ENROLLMENTS_FETCH_PARTIAL", `${failed.length} of ${batches.length} enrollment batches failed`));
22951
+ }
22952
+ const fetchedEnrollments = extractFulfilled(batchResults);
22026
22953
  enrollments.push(...fetchedEnrollments);
22027
22954
  for (const enrollment of fetchedEnrollments) {
22028
22955
  const userId = enrollment.user?.sourcedId;
@@ -22048,6 +22975,7 @@ class BootstrapService {
22048
22975
  } catch (error48) {
22049
22976
  const message = getErrorMessage(error48);
22050
22977
  log.warn("Failed to fetch enrollments", { error: message });
22978
+ errors3.push(createStudioError("ENROLLMENTS_FETCH_FAILED", message));
22051
22979
  }
22052
22980
  return { enrollments, studentIds, teacherIds, studentsByClass };
22053
22981
  }
@@ -22062,9 +22990,16 @@ class EnrollmentService {
22062
22990
  this.client = client;
22063
22991
  }
22064
22992
  async enroll(options) {
22065
- const { classId, userId } = options;
22993
+ const { classId, courseId, userId } = options;
22066
22994
  const errors3 = [];
22067
- log2.debug("Enrolling student", { classId, userId });
22995
+ if (!classId && !courseId) {
22996
+ errors3.push(createStudioError("ENV_INVALID", "Either classId or courseId is required"));
22997
+ return { success: false, errors: errors3 };
22998
+ }
22999
+ if (courseId && !classId) {
23000
+ return this.enrollViaCourse(userId, courseId, errors3);
23001
+ }
23002
+ log2.debug("Enrolling student via class", { classId, userId });
22068
23003
  try {
22069
23004
  const result = await this.client.oneroster.classes(classId).enroll({
22070
23005
  sourcedId: userId,
@@ -22084,6 +23019,24 @@ class EnrollmentService {
22084
23019
  return this.handleEnrollmentError(error48, "enroll", { classId, userId }, errors3);
22085
23020
  }
22086
23021
  }
23022
+ async enrollViaCourse(userId, courseId, errors3) {
23023
+ log2.debug("Enrolling student via course (EduBridge)", { courseId, userId });
23024
+ try {
23025
+ const enrollment = await this.client.edubridge.enrollments.enroll(userId, courseId);
23026
+ log2.debug("Student enrolled via EduBridge", {
23027
+ courseId,
23028
+ userId,
23029
+ enrollmentId: enrollment.id
23030
+ });
23031
+ return {
23032
+ success: true,
23033
+ enrollmentId: enrollment.id,
23034
+ errors: errors3
23035
+ };
23036
+ } catch (error48) {
23037
+ return this.handleEnrollmentError(error48, "enroll", { courseId, userId }, errors3);
23038
+ }
23039
+ }
22087
23040
  async unenroll(options) {
22088
23041
  const { enrollmentId } = options;
22089
23042
  const errors3 = [];
@@ -22363,7 +23316,7 @@ class StudentSearchService {
22363
23316
  const trimmedQuery = query.trim();
22364
23317
  log5.debug("Searching students", { query: trimmedQuery, max });
22365
23318
  try {
22366
- const users = await this.client.oneroster.students.listAll({
23319
+ const users = await this.client.oneroster.users.listAll({
22367
23320
  search: trimmedQuery,
22368
23321
  where: { status: "active" },
22369
23322
  max
@@ -22441,7 +23394,7 @@ function createManager(ctx) {
22441
23394
  log6.debug("Manager ready", { environments: manager.names });
22442
23395
  return manager;
22443
23396
  }
22444
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/helper/factory/index.js
23397
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/helper/factory/index.js
22445
23398
  var Factory = class {
22446
23399
  initApp;
22447
23400
  #defaultAppOptions;
@@ -22504,7 +23457,7 @@ function createEnvMiddleware(ctx, manager) {
22504
23457
  await next();
22505
23458
  });
22506
23459
  }
22507
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/utils/stream.js
23460
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/utils/stream.js
22508
23461
  var StreamingApi = class {
22509
23462
  writer;
22510
23463
  encoder;
@@ -22570,7 +23523,7 @@ var StreamingApi = class {
22570
23523
  }
22571
23524
  };
22572
23525
 
22573
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/helper/streaming/utils.js
23526
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/helper/streaming/utils.js
22574
23527
  var isOldBunVersion = () => {
22575
23528
  const version2 = typeof Bun !== "undefined" ? Bun.version : undefined;
22576
23529
  if (version2 === undefined) {
@@ -22581,15 +23534,14 @@ var isOldBunVersion = () => {
22581
23534
  return result;
22582
23535
  };
22583
23536
 
22584
- // ../../node_modules/.bun/hono@4.11.4/node_modules/hono/dist/helper/streaming/sse.js
23537
+ // ../../node_modules/.bun/hono@4.11.7/node_modules/hono/dist/helper/streaming/sse.js
22585
23538
  var SSEStreamingApi = class extends StreamingApi {
22586
23539
  constructor(writable, readable) {
22587
23540
  super(writable, readable);
22588
23541
  }
22589
23542
  async writeSSE(message) {
22590
23543
  const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
22591
- const dataLines = data.split(`
22592
- `).map((line) => {
23544
+ const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
22593
23545
  return `data: ${line}`;
22594
23546
  }).join(`
22595
23547
  `);
@@ -22605,7 +23557,7 @@ var SSEStreamingApi = class extends StreamingApi {
22605
23557
  await this.write(sseData);
22606
23558
  }
22607
23559
  };
22608
- var run = async (stream, cb, onError) => {
23560
+ var run2 = async (stream, cb, onError) => {
22609
23561
  try {
22610
23562
  await cb(stream);
22611
23563
  } catch (e2) {
@@ -22638,7 +23590,7 @@ var streamSSE = (c, cb, onError) => {
22638
23590
  c.header("Content-Type", "text/event-stream");
22639
23591
  c.header("Cache-Control", "no-cache");
22640
23592
  c.header("Connection", "keep-alive");
22641
- run(stream, cb, onError);
23593
+ run2(stream, cb, onError);
22642
23594
  return c.newResponse(stream.responseReadable);
22643
23595
  };
22644
23596
 
@@ -22732,8 +23684,11 @@ function runSSE(c, options) {
22732
23684
  // src/server/controllers/enrollment.ts
22733
23685
  var log9 = createLogger({ scope: "studio:route:enrollment" });
22734
23686
  var enrollSchema = exports_external.object({
22735
- classId: exports_external.string().min(1, "classId is required"),
22736
- userId: exports_external.string().min(1, "userId is required")
23687
+ userId: exports_external.string().min(1, "userId is required"),
23688
+ classId: exports_external.string().min(1).optional(),
23689
+ courseId: exports_external.string().min(1).optional()
23690
+ }).refine((data) => data.classId || data.courseId, {
23691
+ message: "Either classId or courseId is required"
22737
23692
  });
22738
23693
  var unenrollSchema = exports_external.object({
22739
23694
  enrollmentId: exports_external.string().min(1, "enrollmentId is required")
@@ -22746,8 +23701,8 @@ async function handleEnroll(c) {
22746
23701
  const error48 = createStudioError("ENV_INVALID", "Invalid request payload");
22747
23702
  return c.json({ success: false, errors: [error48] }, 400);
22748
23703
  }
22749
- const { classId, userId } = parsedBody.data;
22750
- const result = await enrollment.enroll({ classId, userId });
23704
+ const { classId, courseId, userId } = parsedBody.data;
23705
+ const result = await enrollment.enroll({ classId, courseId, userId });
22751
23706
  return c.json(result, result.success ? 200 : 400);
22752
23707
  }
22753
23708
  async function handleUnenroll(c) {
@@ -22998,12 +23953,14 @@ function startServer(ctx, serverConfig, configFile) {
22998
23953
  const app = createApp(ctx);
22999
23954
  serve({ fetch: app.fetch, port: serverConfig.port }, (info) => {
23000
23955
  const url2 = `http://localhost:${info.port}`;
23001
- console.log();
23002
- console.log(` ${green("┌")} ${bold(ctx.userConfig.name)} ${dim(`v${ctx.userConfig.version}`)}`);
23003
- console.log(` ${green("└")} ${cyan(url2)}`);
23004
- console.log();
23005
- console.log(` ${dim("press")} ${bold("h + enter")} ${dim("to show shortcuts")}`);
23006
- console.log();
23956
+ const bar = gray("│");
23957
+ console.log(bar);
23958
+ console.log(bar);
23959
+ console.log(`${bar} ${green("┌")} ${bold(ctx.userConfig.name)} ${dim(`v${ctx.userConfig.version}`)}`);
23960
+ console.log(`${bar} ${green("")} ${cyan(url2)}`);
23961
+ console.log(bar);
23962
+ console.log(`${bar} ${dim("press")} ${bold("h + enter")} ${dim("to show shortcuts")}`);
23963
+ console.log(bar);
23007
23964
  if (configFile) {
23008
23965
  M2.info(`Loaded ${greenBright(configFile)}`);
23009
23966
  }
@@ -23012,33 +23969,124 @@ function startServer(ctx, serverConfig, configFile) {
23012
23969
  }
23013
23970
 
23014
23971
  // src/cli/commands/serve/index.ts
23015
- async function serveCommand(courseIds, opts) {
23016
- const serverConfig = getServerConfig();
23972
+ async function launchServer(serverConfig, userConfig, credentials, env2, opts, configFile) {
23973
+ const creds = await ensureCredentials({ env: env2, credentials, skipIntro: true });
23974
+ if (!creds) {
23975
+ process.exit(1);
23976
+ }
23977
+ const derivedSensors = await resolveSensors({
23978
+ config: userConfig,
23979
+ env: env2,
23980
+ opts,
23981
+ creds
23982
+ });
23983
+ if (derivedSensors === null) {
23984
+ process.exit(1);
23985
+ }
23986
+ const ctx = {
23987
+ serverConfig,
23988
+ userConfig,
23989
+ credentials,
23990
+ defaultEnvironment: env2,
23991
+ derivedSensors
23992
+ };
23993
+ startServer(ctx, serverConfig, configFile);
23994
+ }
23995
+ async function handleCourseIdsCase(courseIds, opts, serverConfig) {
23996
+ const env2 = opts.env ?? await promptEnvironmentForCourseIds(courseIds.length);
23997
+ let configuredEnvs = await getConfiguredEnvironments();
23998
+ let credentials = configuredEnvs.length > 0 ? await loadAllCredentials() : {};
23999
+ const resolvedResult = await resolveConfigSource(courseIds, opts, env2);
24000
+ if (!resolvedResult) {
24001
+ process.exit(1);
24002
+ }
24003
+ const { resolved, credentials: updatedCreds, configuredEnvs: updatedEnvs } = resolvedResult;
24004
+ credentials = updatedCreds;
24005
+ configuredEnvs = updatedEnvs;
24006
+ if (Object.keys(credentials).length === 0 && configuredEnvs.length > 0) {
24007
+ credentials = await loadAllCredentials();
24008
+ }
24009
+ await launchServer(serverConfig, resolved.userConfig, credentials, env2, opts, resolved.configFile);
24010
+ }
24011
+ async function handleConfigFileCase(userConfig, configPath, opts, serverConfig) {
24012
+ const env2 = await resolveServeEnvironment({
24013
+ userConfig,
24014
+ hasEnvOverride: Boolean(opts.env),
24015
+ envOverride: opts.env
24016
+ });
24017
+ if (env2 === null) {
24018
+ Me([
24019
+ `Your config file has no course IDs for ${bold("staging")} or ${bold("production")}.`,
24020
+ "",
24021
+ `Run ${greenBright(bold("timeback resources push"))} to populate course IDs in your config.`,
24022
+ "",
24023
+ `Alternatively, run ${greenBright(bold("timeback studio <courseId...>"))}.`
24024
+ ].join(`
24025
+ `), "Note");
24026
+ M2.error("Config incomplete");
24027
+ console.log("");
24028
+ process.exit(1);
24029
+ }
24030
+ const envCourseIds = userConfig.courseIds[env2] ?? [];
24031
+ if (envCourseIds.length === 0) {
24032
+ const otherEnv = env2 === "staging" ? "production" : "staging";
24033
+ Me([
24034
+ `Your config file has no course IDs for ${bold(env2)}.`,
24035
+ "",
24036
+ `Try: ${greenBright(bold(`timeback studio --env ${otherEnv}`))}`,
24037
+ "",
24038
+ `Or run ${greenBright(bold("timeback resources push"))} to populate IDs for ${bold(env2)}.`
24039
+ ].join(`
24040
+ `), "Note");
24041
+ M2.error("Config incomplete");
24042
+ console.log("");
24043
+ process.exit(1);
24044
+ }
24045
+ const credentials = await loadAllCredentials();
24046
+ await launchServer(serverConfig, userConfig, credentials, env2, opts, configPath);
24047
+ }
24048
+ async function handleImportFlowCase(opts, serverConfig) {
23017
24049
  let configuredEnvs = await getConfiguredEnvironments();
23018
- let credentials;
23019
- let defaultEnvironment;
24050
+ let credentials = {};
24051
+ let selectedEnv;
23020
24052
  if (configuredEnvs.length === 0) {
23021
- const result = await handleCredentialSetup();
23022
- if (!result)
24053
+ const result = await handleCredentialSetup({ skipIntro: true });
24054
+ if (!result) {
23023
24055
  process.exit(1);
24056
+ }
23024
24057
  credentials = result.credentials;
23025
- defaultEnvironment = result.defaultEnvironment;
23026
24058
  configuredEnvs = Object.keys(credentials);
24059
+ selectedEnv = result.selectedEnvironment;
23027
24060
  } else {
23028
24061
  credentials = await loadAllCredentials();
23029
- defaultEnvironment = await resolveDefaultEnvironment(opts.env);
23030
24062
  }
23031
- const resolved = await resolveConfig(courseIds, opts, credentials, configuredEnvs, defaultEnvironment);
23032
- if (!resolved)
24063
+ const env2 = selectedEnv ?? configuredEnvs[0] ?? "staging";
24064
+ const resolvedResult = await resolveConfigSource([], opts, env2);
24065
+ if (!resolvedResult) {
23033
24066
  process.exit(1);
23034
- const ctx = {
23035
- serverConfig,
23036
- userConfig: resolved.userConfig,
23037
- credentials,
23038
- defaultEnvironment: resolved.environment,
23039
- derivedSensors: getEffectiveSensors(resolved.userConfig, opts)
23040
- };
23041
- startServer(ctx, serverConfig, resolved.configFile);
24067
+ }
24068
+ const { resolved } = resolvedResult;
24069
+ await launchServer(serverConfig, resolved.userConfig, credentials, env2, opts, resolved.configFile);
24070
+ }
24071
+ async function serveCommand(courseIds, opts) {
24072
+ intro("Timeback Studio");
24073
+ const serverConfig = getServerConfig();
24074
+ const parserOpts = { playcademy: opts.playcademy };
24075
+ if (courseIds.length > 0) {
24076
+ await handleCourseIdsCase(courseIds, opts, serverConfig);
24077
+ return;
24078
+ }
24079
+ const configResult = await loadConfig2(parserOpts);
24080
+ if (configResult.success) {
24081
+ await handleConfigFileCase(configResult.config, getRelativeConfigPath(configResult.config.path), opts, serverConfig);
24082
+ return;
24083
+ }
24084
+ const isMissingConfig = configResult.error.includes("No") && configResult.error.includes("config found");
24085
+ if (!isMissingConfig) {
24086
+ M2.error(configResult.error);
24087
+ process.exit(1);
24088
+ }
24089
+ await handleImportFlowCase(opts, serverConfig);
23042
24090
  }
23043
24091
 
23044
24092
  // src/cli/index.ts