timeback-studio 0.1.6 → 0.1.8

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
@@ -16854,13 +16854,14 @@ async function validateEmailWithTimeback(environment, clientId, clientSecret, em
16854
16854
  if (page.data.length === 0) {
16855
16855
  return {
16856
16856
  valid: false,
16857
+ reason: "not_found",
16857
16858
  error: `No user found with email "${email3}" in ${environment}`
16858
16859
  };
16859
16860
  }
16860
16861
  return { valid: true };
16861
16862
  } catch (error48) {
16862
16863
  const message = error48 instanceof Error ? error48.message : "Unknown error";
16863
- return { valid: false, error: `Failed to validate email: ${message}` };
16864
+ return { valid: false, reason: "api_error", error: `Failed to validate email: ${message}` };
16864
16865
  }
16865
16866
  }
16866
16867
 
@@ -17162,6 +17163,8 @@ var ActivityCompletedInput = exports_external.object({
17162
17163
  metricsId: exports_external.string().optional(),
17163
17164
  id: exports_external.string().optional(),
17164
17165
  extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
17166
+ edApp: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
17167
+ session: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
17165
17168
  attempt: exports_external.number().int().min(1).optional(),
17166
17169
  generatedExtensions: exports_external.object({
17167
17170
  pctCompleteApp: exports_external.number().optional()
@@ -17174,7 +17177,9 @@ var TimeSpentInput = exports_external.object({
17174
17177
  eventTime: IsoDateTimeString.optional(),
17175
17178
  metricsId: exports_external.string().optional(),
17176
17179
  id: exports_external.string().optional(),
17177
- extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
17180
+ extensions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
17181
+ edApp: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional(),
17182
+ session: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]).optional()
17178
17183
  }).strict();
17179
17184
  var TimebackEvent = exports_external.union([TimebackActivityEvent, TimebackTimeSpentEvent]);
17180
17185
  var CaliperEnvelope = exports_external.object({
@@ -17364,7 +17369,7 @@ var TimebackConfig = exports_external.object({
17364
17369
  path: ["courses"]
17365
17370
  });
17366
17371
  // ../types/src/zod/edubridge.ts
17367
- var EdubridgeDateString = IsoDateTimeString;
17372
+ var EdubridgeDateString = exports_external.union([IsoDateTimeString, IsoDateString]);
17368
17373
  var EduBridgeEnrollment = exports_external.object({
17369
17374
  id: exports_external.string(),
17370
17375
  role: exports_external.string(),
@@ -19465,6 +19470,184 @@ async function addCredentials(options = {}) {
19465
19470
  if (exitOnComplete)
19466
19471
  process.exit(0);
19467
19472
  }
19473
+ // src/cli/commands/credentials/create-account.ts
19474
+ import { randomUUID } from "node:crypto";
19475
+ import { TimebackClient as TimebackClient2 } from "@timeback/core";
19476
+ async function promptName(label) {
19477
+ const value = await he({
19478
+ message: label,
19479
+ placeholder: "Enter name",
19480
+ validate: (v2) => {
19481
+ if (!v2?.trim())
19482
+ return `${label} is required`;
19483
+ }
19484
+ });
19485
+ if (isCancelled(value))
19486
+ return null;
19487
+ return value.trim();
19488
+ }
19489
+ async function searchOrganizations(client, query) {
19490
+ const allOrgs = await client.oneroster.orgs.listAll({
19491
+ where: { status: "active" },
19492
+ max: 100
19493
+ });
19494
+ if (!query.trim())
19495
+ return allOrgs;
19496
+ const lowerQuery = query.toLowerCase().trim();
19497
+ return allOrgs.filter((org) => org.name?.toLowerCase().includes(lowerQuery));
19498
+ }
19499
+ async function searchForOrganization(client) {
19500
+ const query = await he({
19501
+ message: "Search for organization",
19502
+ placeholder: "Enter organization name to search"
19503
+ });
19504
+ if (isCancelled(query))
19505
+ return null;
19506
+ const s = Y2();
19507
+ s.start("Searching organizations...");
19508
+ let results;
19509
+ try {
19510
+ results = await searchOrganizations(client, query);
19511
+ s.stop(green(`Found ${results.length} result${results.length === 1 ? "" : "s"}`));
19512
+ } catch (error48) {
19513
+ s.stop(red("Failed to search organizations"));
19514
+ M2.error(error48 instanceof Error ? error48.message : "Unknown error");
19515
+ return null;
19516
+ }
19517
+ if (results.length === 0) {
19518
+ M2.warn("No organizations found matching your search");
19519
+ return promptOrganization(client);
19520
+ }
19521
+ const validResults = results.filter((org) => org.sourcedId);
19522
+ if (validResults.length === 0) {
19523
+ M2.warn("No valid organizations found (missing IDs)");
19524
+ return promptOrganization(client);
19525
+ }
19526
+ const options = validResults.map((org) => ({
19527
+ value: org.sourcedId,
19528
+ label: org.name ?? "Unnamed Organization"
19529
+ }));
19530
+ options.push({ value: "__back__", label: `${dim("←")} Back to options` });
19531
+ const selection = await ve({
19532
+ message: "Select an organization",
19533
+ options
19534
+ });
19535
+ if (isCancelled(selection))
19536
+ return null;
19537
+ if (selection === "__back__") {
19538
+ return promptOrganization(client);
19539
+ }
19540
+ return validResults.find((org) => org.sourcedId === selection) ?? null;
19541
+ }
19542
+ async function promptOrganization(client) {
19543
+ const action = await ve({
19544
+ message: "Organization",
19545
+ options: [
19546
+ { value: "search", label: "Search for existing organization" },
19547
+ { value: "create", label: "Create new organization" }
19548
+ ]
19549
+ });
19550
+ if (isCancelled(action))
19551
+ return null;
19552
+ if (action === "create") {
19553
+ return createNewOrganization(client);
19554
+ }
19555
+ return searchForOrganization(client);
19556
+ }
19557
+ async function createNewOrganization(client) {
19558
+ const name = await he({
19559
+ message: "Organization name",
19560
+ placeholder: "Enter organization name",
19561
+ validate: (v2) => {
19562
+ if (!v2?.trim())
19563
+ return "Organization name is required";
19564
+ }
19565
+ });
19566
+ if (isCancelled(name))
19567
+ return null;
19568
+ const s = Y2();
19569
+ s.start("Creating organization...");
19570
+ const sourcedId = randomUUID();
19571
+ try {
19572
+ await client.oneroster.orgs.create({
19573
+ sourcedId,
19574
+ name: name.trim(),
19575
+ type: "school",
19576
+ status: "active"
19577
+ });
19578
+ const organization = await client.oneroster.orgs.get(sourcedId);
19579
+ s.stop(green(`Organization "${name}" created`));
19580
+ return organization;
19581
+ } catch (error48) {
19582
+ s.stop(red("Failed to create organization"));
19583
+ M2.error(error48 instanceof Error ? error48.message : "Unknown error");
19584
+ return null;
19585
+ }
19586
+ }
19587
+ async function createUser(client, email3, givenName, familyName, organizationId) {
19588
+ const s = Y2();
19589
+ s.start("Creating account...");
19590
+ const sourcedId = randomUUID();
19591
+ try {
19592
+ await client.oneroster.users.create({
19593
+ sourcedId,
19594
+ givenName,
19595
+ familyName,
19596
+ email: email3.toLowerCase(),
19597
+ enabledUser: true,
19598
+ status: "active",
19599
+ roles: [
19600
+ {
19601
+ roleType: "primary",
19602
+ role: "administrator",
19603
+ org: { sourcedId: organizationId }
19604
+ }
19605
+ ]
19606
+ });
19607
+ const user = await client.oneroster.users.get(sourcedId);
19608
+ s.stop(green("Account created successfully"));
19609
+ return user;
19610
+ } catch (error48) {
19611
+ s.stop(red("Failed to create account"));
19612
+ M2.error(error48 instanceof Error ? error48.message : "Unknown error");
19613
+ return null;
19614
+ }
19615
+ }
19616
+ async function createAccountFlow(options) {
19617
+ const { environment, clientId, clientSecret, email: email3 } = options;
19618
+ M2.info("");
19619
+ M2.info(`No account found for ${dim(email3)} in ${environment}`);
19620
+ const shouldCreate = await ye({
19621
+ message: "Would you like to create a new account?",
19622
+ initialValue: true
19623
+ });
19624
+ if (isCancelled(shouldCreate)) {
19625
+ return { success: false };
19626
+ }
19627
+ if (!shouldCreate) {
19628
+ return { success: false, declined: true };
19629
+ }
19630
+ const client = new TimebackClient2({
19631
+ env: environment,
19632
+ auth: { clientId, clientSecret }
19633
+ });
19634
+ const givenName = await promptName("First name");
19635
+ if (!givenName)
19636
+ return { success: false };
19637
+ const familyName = await promptName("Last name");
19638
+ if (!familyName)
19639
+ return { success: false };
19640
+ const organization = await promptOrganization(client);
19641
+ if (!organization?.sourcedId)
19642
+ return { success: false };
19643
+ const user = await createUser(client, email3, givenName, familyName, organization.sourcedId);
19644
+ if (!user) {
19645
+ return { success: false };
19646
+ }
19647
+ M2.success(`Account created for ${user.givenName} ${user.familyName}`);
19648
+ return { success: true };
19649
+ }
19650
+
19468
19651
  // src/cli/commands/credentials/email.ts
19469
19652
  async function updateEmail(options = {}) {
19470
19653
  const { exitOnComplete = true, inline = false } = options;
@@ -19533,32 +19716,58 @@ async function updateEmail(options = {}) {
19533
19716
  return;
19534
19717
  }
19535
19718
  const emailUnchanged = email3 === (currentEmail ?? "");
19536
- if (emailUnchanged) {
19537
- if (!inline)
19538
- outro.info("Email unchanged");
19539
- if (exitOnComplete)
19540
- process.exit(0);
19541
- return;
19542
- }
19543
19719
  if (email3) {
19544
19720
  const s = Y2();
19545
- s.start("Validating email...");
19721
+ s.start("Checking account...");
19546
19722
  const result = await validateEmailWithTimeback(targetEnv, currentCreds.clientId, currentCreds.clientSecret, email3);
19547
19723
  if (!result.valid) {
19548
- s.stop(red("Email validation failed"));
19549
- M2.error(result.error ?? "Unknown error");
19550
- M2.info("Please contact a Timeback admin to set up your account.");
19724
+ if (result.reason !== "not_found") {
19725
+ s.stop(red("Account check failed"));
19726
+ M2.error(result.error ?? "Unknown error");
19727
+ if (!inline)
19728
+ outro.error("Account check failed");
19729
+ if (exitOnComplete)
19730
+ process.exit(1);
19731
+ return;
19732
+ }
19733
+ s.stop(red("No account found"));
19734
+ const { success: accountCreated, declined } = await createAccountFlow({
19735
+ environment: targetEnv,
19736
+ clientId: currentCreds.clientId,
19737
+ clientSecret: currentCreds.clientSecret,
19738
+ email: email3
19739
+ });
19740
+ if (!emailUnchanged) {
19741
+ await saveCredentials(targetEnv, {
19742
+ ...currentCreds,
19743
+ email: email3
19744
+ });
19745
+ M2.success(`Email saved for ${targetEnv}`);
19746
+ }
19747
+ if (!inline) {
19748
+ if (accountCreated || declined) {
19749
+ outro.success();
19750
+ } else {
19751
+ outro.info("Setup incomplete - run this command again to finish");
19752
+ }
19753
+ }
19754
+ if (exitOnComplete)
19755
+ process.exit(0);
19756
+ return;
19757
+ }
19758
+ s.stop(green("Account verified"));
19759
+ if (emailUnchanged) {
19551
19760
  if (!inline)
19552
- outro.error("Email validation failed");
19761
+ outro.info("Email unchanged");
19553
19762
  if (exitOnComplete)
19554
- process.exit(1);
19763
+ process.exit(0);
19555
19764
  return;
19556
19765
  }
19557
19766
  await saveCredentials(targetEnv, {
19558
19767
  ...currentCreds,
19559
19768
  email: email3
19560
19769
  });
19561
- s.stop(green(`Email updated for ${targetEnv}`));
19770
+ M2.success(`Email updated for ${targetEnv}`);
19562
19771
  if (!inline)
19563
19772
  outro.success();
19564
19773
  if (exitOnComplete)
@@ -19874,7 +20083,7 @@ function printError3(error48, opts = {}) {
19874
20083
  parser.printError(error48);
19875
20084
  }
19876
20085
  // src/cli/lib/courses.ts
19877
- import { TimebackClient as TimebackClient2 } from "@timeback/core";
20086
+ import { TimebackClient as TimebackClient3 } from "@timeback/core";
19878
20087
  async function fetchCoursesByIds(client, ids) {
19879
20088
  const courses = [];
19880
20089
  for (const id of ids) {
@@ -19886,7 +20095,7 @@ async function fetchCoursesByIds(client, ids) {
19886
20095
  return courses;
19887
20096
  }
19888
20097
  async function fetchCourses(creds, env2, ids) {
19889
- const client = new TimebackClient2({
20098
+ const client = new TimebackClient3({
19890
20099
  env: env2,
19891
20100
  auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
19892
20101
  });
@@ -19908,7 +20117,7 @@ async function checkCoursesManaged(creds, env2, ids) {
19908
20117
  if (ids.length === 0) {
19909
20118
  return { allManaged: true, unmanagedCourses: [] };
19910
20119
  }
19911
- const client = new TimebackClient2({
20120
+ const client = new TimebackClient3({
19912
20121
  env: env2,
19913
20122
  auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
19914
20123
  });
@@ -19956,7 +20165,7 @@ async function handleCredentialSetup(options = {}) {
19956
20165
  };
19957
20166
  }
19958
20167
  // src/cli/lib/onboarding/import.ts
19959
- import { TimebackClient as TimebackClient3 } from "@timeback/core";
20168
+ import { TimebackClient as TimebackClient4 } from "@timeback/core";
19960
20169
  async function promptImportApp(credentials, configuredEnvs) {
19961
20170
  let env2;
19962
20171
  if (configuredEnvs.length === 1 && configuredEnvs[0]) {
@@ -19969,7 +20178,7 @@ async function promptImportApp(credentials, configuredEnvs) {
19969
20178
  env2 = selectedEnv;
19970
20179
  }
19971
20180
  const creds = credentials[env2];
19972
- const client = new TimebackClient3({
20181
+ const client = new TimebackClient4({
19973
20182
  env: env2,
19974
20183
  auth: { clientId: creds.clientId, clientSecret: creds.clientSecret }
19975
20184
  });
@@ -22399,7 +22608,8 @@ var cors = (options) => {
22399
22608
  async function handleBootstrap(c, ctx) {
22400
22609
  const { bootstrap } = c.get("services");
22401
22610
  const env2 = c.get("env");
22402
- const email3 = ctx.credentials[env2]?.email;
22611
+ const freshCredentials = await getSavedCredentials(env2);
22612
+ const email3 = freshCredentials?.email;
22403
22613
  const courseIds = ctx.userConfig.courseIds[env2];
22404
22614
  const result = await bootstrap.getBootstrap({ email: email3, courseIds });
22405
22615
  return c.json(result);
@@ -22727,6 +22937,7 @@ function createLogger(options = {}) {
22727
22937
  }
22728
22938
  // src/server/services/bootstrap.ts
22729
22939
  var log = createLogger({ scope: "studio:service:bootstrap" });
22940
+ var hasLoggedInitialLoad = false;
22730
22941
 
22731
22942
  class BootstrapService {
22732
22943
  client;
@@ -22757,8 +22968,11 @@ class BootstrapService {
22757
22968
  stats,
22758
22969
  errors: errors3.length
22759
22970
  });
22760
- const message = `Loaded ${underline(stats.totalCourses)} ${pluralize(stats.totalCourses, "course")}, ${underline(stats.totalStudents)} ${pluralize(stats.totalStudents, "student")}`;
22761
- M2.success(message);
22971
+ if (!hasLoggedInitialLoad) {
22972
+ const message = `Loaded ${underline(stats.totalCourses)} ${pluralize(stats.totalCourses, "course")}, ${underline(stats.totalStudents)} ${pluralize(stats.totalStudents, "student")}`;
22973
+ M2.success(message);
22974
+ hasLoggedInitialLoad = true;
22975
+ }
22762
22976
  return {
22763
22977
  user,
22764
22978
  courses,
@@ -23238,48 +23452,107 @@ class EventsService {
23238
23452
  return result;
23239
23453
  }
23240
23454
  }
23241
- // src/server/services/live.ts
23242
- var log4 = createLogger({ scope: "studio:service:live" });
23455
+ // src/server/services/live-poller.ts
23456
+ var log4 = createLogger({ scope: "studio:service:live-poller" });
23457
+ var pollers = new Map;
23458
+ function getOrCreatePoller(environment, statusService, eventsService) {
23459
+ let poller = pollers.get(environment);
23460
+ if (!poller) {
23461
+ poller = new LivePoller({ environment, statusService, eventsService });
23462
+ pollers.set(environment, poller);
23463
+ }
23464
+ return poller;
23465
+ }
23243
23466
 
23244
- class LiveService {
23467
+ class LivePoller {
23468
+ environment;
23245
23469
  statusService;
23246
23470
  eventsService;
23471
+ subscribers = new Set;
23472
+ interval = null;
23473
+ state;
23474
+ lastBroadcast;
23475
+ initialTickPromise = null;
23476
+ ticking = false;
23247
23477
  constructor(options) {
23478
+ this.environment = options.environment;
23248
23479
  this.statusService = options.statusService;
23249
23480
  this.eventsService = options.eventsService;
23250
- }
23251
- static createInitialState() {
23252
23481
  const now = Date.now();
23253
- return {
23482
+ this.state = {
23254
23483
  lastStatusHash: "",
23255
23484
  lastEventTime: new Date().toISOString(),
23256
23485
  lastStatusTick: now,
23257
23486
  lastEventsTick: now
23258
23487
  };
23259
23488
  }
23260
- async tick(state) {
23261
- const updates = [];
23262
- const now = Date.now();
23263
- const newState = { ...state };
23264
- const statusUpdate = await this.fetchStatusIfChanged(state.lastStatusHash);
23265
- if (statusUpdate) {
23266
- updates.push(statusUpdate.update);
23267
- newState.lastStatusHash = statusUpdate.hash;
23489
+ subscribe(callback) {
23490
+ this.subscribers.add(callback);
23491
+ if (this.subscribers.size === 1) {
23492
+ this.start();
23268
23493
  }
23269
- newState.lastStatusTick = now;
23270
- const shouldCheckEvents = this.hasIntervalElapsed(state.lastEventsTick, now, constants.events.polling.tickIntervalMs);
23271
- if (shouldCheckEvents) {
23272
- const eventsUpdate = await this.fetchEventsIfNew(state.lastEventTime);
23273
- if (eventsUpdate) {
23274
- updates.push(eventsUpdate.update);
23275
- newState.lastEventTime = eventsUpdate.newEventTime;
23494
+ return () => {
23495
+ this.subscribers.delete(callback);
23496
+ if (this.subscribers.size === 0) {
23497
+ this.stop();
23498
+ pollers.delete(this.environment);
23276
23499
  }
23277
- newState.lastEventsTick = now;
23500
+ };
23501
+ }
23502
+ async getLastBroadcastAfterInitialTick() {
23503
+ if (this.initialTickPromise) {
23504
+ await this.initialTickPromise;
23505
+ }
23506
+ return this.lastBroadcast;
23507
+ }
23508
+ start() {
23509
+ log4.debug("Poller started", { environment: this.environment });
23510
+ this.initialTickPromise = this.tick();
23511
+ this.interval = setInterval(() => this.tick(), constants.sse.defaultTickIntervalMs);
23512
+ }
23513
+ stop() {
23514
+ if (this.interval) {
23515
+ clearInterval(this.interval);
23516
+ this.interval = null;
23278
23517
  }
23279
- return { updates, newState };
23518
+ this.lastBroadcast = undefined;
23519
+ this.initialTickPromise = null;
23520
+ log4.debug("Poller stopped", { environment: this.environment });
23280
23521
  }
23281
- hasIntervalElapsed(lastTick, now, intervalMs) {
23282
- return now - lastTick >= intervalMs;
23522
+ async tick() {
23523
+ if (this.ticking)
23524
+ return;
23525
+ this.ticking = true;
23526
+ try {
23527
+ const updates = [];
23528
+ const now = Date.now();
23529
+ const statusUpdate = await this.fetchStatusIfChanged(this.state.lastStatusHash);
23530
+ if (statusUpdate) {
23531
+ updates.push(statusUpdate.update);
23532
+ this.state.lastStatusHash = statusUpdate.hash;
23533
+ }
23534
+ this.state.lastStatusTick = now;
23535
+ const shouldCheckEvents = now - this.state.lastEventsTick >= constants.events.polling.tickIntervalMs;
23536
+ if (shouldCheckEvents) {
23537
+ const eventsUpdate = await this.fetchEventsIfNew(this.state.lastEventTime);
23538
+ if (eventsUpdate) {
23539
+ updates.push(eventsUpdate.update);
23540
+ this.state.lastEventTime = eventsUpdate.newEventTime;
23541
+ }
23542
+ this.state.lastEventsTick = now;
23543
+ }
23544
+ if (updates.length > 0) {
23545
+ const data = JSON.stringify(updates);
23546
+ this.lastBroadcast = data;
23547
+ for (const callback of this.subscribers) {
23548
+ callback(data);
23549
+ }
23550
+ }
23551
+ } catch (error48) {
23552
+ log4.error("Tick failed", { environment: this.environment, error: error48 });
23553
+ } finally {
23554
+ this.ticking = false;
23555
+ }
23283
23556
  }
23284
23557
  async fetchStatusIfChanged(lastHash) {
23285
23558
  try {
@@ -23306,7 +23579,8 @@ class LiveService {
23306
23579
  }
23307
23580
  const message = `${underline(newEvents.length)} new ${pluralize(newEvents.length, "event")}`;
23308
23581
  M2.message(message, { symbol: magenta("◆") });
23309
- const mostRecentTime = newEvents.map((e2) => e2.eventTime).sort().pop();
23582
+ const mostRecentTime = newEvents.map((e2) => new Date(e2.eventTime).getTime()).sort((a, b3) => a - b3).pop();
23583
+ const newEventTime = mostRecentTime === undefined ? lastEventTime : new Date(mostRecentTime + 1).toISOString();
23310
23584
  return {
23311
23585
  update: {
23312
23586
  type: "events",
@@ -23315,7 +23589,7 @@ class LiveService {
23315
23589
  timestamp: new Date().toISOString()
23316
23590
  }
23317
23591
  },
23318
- newEventTime: mostRecentTime ?? lastEventTime
23592
+ newEventTime
23319
23593
  };
23320
23594
  } catch (error48) {
23321
23595
  log4.error("Failed to fetch events", { error: error48 });
@@ -23331,11 +23605,15 @@ class StatusService {
23331
23605
  }
23332
23606
  async getStatus() {
23333
23607
  const configuredEnvironments = await getConfiguredEnvironments();
23608
+ const [stagingCreds, productionCreds] = await Promise.all([
23609
+ getSavedCredentials("staging"),
23610
+ getSavedCredentials("production")
23611
+ ]);
23334
23612
  return {
23335
23613
  config: this.ctx.userConfig,
23336
23614
  environment: this.ctx.defaultEnvironment,
23337
23615
  configuredEnvironments,
23338
- hasEmail: !!this.ctx.credentials.staging?.email || !!this.ctx.credentials.production?.email
23616
+ hasEmail: !!stagingCreds?.email || !!productionCreds?.email
23339
23617
  };
23340
23618
  }
23341
23619
  }
@@ -23716,6 +23994,7 @@ function runSSE(c, options) {
23716
23994
  } catch {
23717
23995
  abort("write error");
23718
23996
  }
23997
+ options.onClose?.();
23719
23998
  unregisterConnection(key, () => abort("superseded"));
23720
23999
  log8.debug("SSE connection closed", { ...meta3, reason: closeReason });
23721
24000
  });
@@ -23818,20 +24097,41 @@ function handleEventsStream(c) {
23818
24097
  }
23819
24098
  // src/server/controllers/live.ts
23820
24099
  function handleLive(c) {
24100
+ const env2 = c.get("env");
23821
24101
  const { status: statusService, events: eventsService } = c.get("services");
23822
- const liveService = new LiveService({ statusService, eventsService });
23823
- let state = LiveService.createInitialState();
24102
+ const poller = getOrCreatePoller(env2, statusService, eventsService);
24103
+ let pending;
24104
+ let isFirstTick = true;
24105
+ const takePending = () => {
24106
+ const data = pending;
24107
+ pending = undefined;
24108
+ return data;
24109
+ };
24110
+ const unsubscribe = poller.subscribe((data) => {
24111
+ pending = data;
24112
+ });
23824
24113
  return runSSE(c, {
23825
24114
  event: "live",
23826
24115
  intervalMs: constants.sse.defaultTickIntervalMs,
23827
24116
  sendInitial: true,
24117
+ onClose: unsubscribe,
23828
24118
  tick: async () => {
23829
- const result = await liveService.tick(state);
23830
- state = result.newState;
23831
- if (result.updates.length === 0) {
23832
- return;
24119
+ if (isFirstTick) {
24120
+ isFirstTick = false;
24121
+ const immediate = takePending();
24122
+ if (immediate !== undefined) {
24123
+ return immediate;
24124
+ }
24125
+ const initial = await poller.getLastBroadcastAfterInitialTick();
24126
+ if (pending !== undefined && pending === initial) {
24127
+ pending = undefined;
24128
+ }
24129
+ if (initial !== undefined) {
24130
+ return initial;
24131
+ }
24132
+ return takePending();
23833
24133
  }
23834
- return JSON.stringify(result.updates);
24134
+ return takePending();
23835
24135
  }
23836
24136
  });
23837
24137
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Account Creation
3
+ *
4
+ * Interactive CLI flow for creating a new Timeback user account
5
+ * when no matching account exists for the provided email.
6
+ */
7
+ import type { CredentialEnvironment } from '@timeback/internal-cli-infra';
8
+ /**
9
+ * Options for creating a new account.
10
+ */
11
+ interface CreateAccountOptions {
12
+ environment: CredentialEnvironment;
13
+ clientId: string;
14
+ clientSecret: string;
15
+ email: string;
16
+ }
17
+ /**
18
+ * Result of the account creation flow.
19
+ */
20
+ interface CreateAccountResult {
21
+ success: boolean;
22
+ /** User explicitly declined to create an account (vs cancelled or failed) */
23
+ declined?: boolean;
24
+ }
25
+ /**
26
+ * Interactive flow to create a new Timeback account.
27
+ *
28
+ * Called when no matching user is found for the provided email.
29
+ * Guides the user through entering their name and selecting/creating
30
+ * an organization.
31
+ *
32
+ * @param options - Account creation options
33
+ * @returns Result indicating success and the created user
34
+ */
35
+ export declare function createAccountFlow(options: CreateAccountOptions): Promise<CreateAccountResult>;
36
+ export {};
37
+ //# sourceMappingURL=create-account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-account.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/create-account.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAGzE;;GAEG;AACH,UAAU,oBAAoB;IAC7B,WAAW,EAAE,qBAAqB,CAAA;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,UAAU,mBAAmB;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AA2ND;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA0CnG"}
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/email.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqH7E"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/credentials/email.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqJ7E"}