timeback 0.2.2-beta.20260401223329 → 0.2.2-beta.20260402000431

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +281 -120
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -35125,8 +35125,13 @@ var CredentialsSchema = exports_external.object({
35125
35125
  clientSecret: NonEmptyString2,
35126
35126
  email: exports_external.email("Valid email is required").optional()
35127
35127
  });
35128
- var ClientIdSchema = exports_external.string().min(1, "Client ID is required").regex(/^[a-z0-9]+$/, "Client ID must contain only lowercase letters and numbers");
35129
- 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");
35128
+ var StoredCredentialsSchema = exports_external.object({
35129
+ clientId: NonEmptyString2.optional(),
35130
+ clientSecret: NonEmptyString2.optional(),
35131
+ email: exports_external.email("Valid email is required").optional()
35132
+ }).refine((value) => value.email || value.clientId && value.clientSecret, "Saved credentials must include an email or a client ID/client secret pair").refine((value) => Boolean(value.clientId) === Boolean(value.clientSecret), "Saved credentials must include both client ID and client secret");
35133
+ var ClientIdSchema = exports_external.string().min(1, "Client ID is required").max(128, "Client ID must be at most 128 characters").regex(/^[\w+]+$/, "Client ID must contain only letters, digits, underscores, or +");
35134
+ var ClientSecretSchema = exports_external.string().min(24, "Client secret must be at least 24 characters").max(64, "Client secret must be at most 64 characters").regex(/^[\w+]+$/, "Client secret must contain only letters, digits, underscores, or +");
35130
35135
  var MasteryTrackCredentialsSchema = exports_external.object({
35131
35136
  apiKey: NonEmptyString2,
35132
35137
  email: exports_external.email("Valid email is required")
@@ -36358,24 +36363,41 @@ async function writeCredentialsStore(store) {
36358
36363
  await writeFile(CREDENTIALS_FILE, JSON.stringify(cleaned, null, 2), { mode: 384 });
36359
36364
  return true;
36360
36365
  }
36361
- async function getSavedCredentials(environment) {
36362
- const store = await readCredentialsStore();
36366
+ function getStoredCredentialsFromStore(store, environment) {
36363
36367
  const namespaced = store.timeback?.[environment];
36364
36368
  if (namespaced) {
36365
- const result = CredentialsSchema.safeParse(namespaced);
36369
+ const result = StoredCredentialsSchema.safeParse(namespaced);
36366
36370
  if (result.success) {
36367
36371
  return result.data;
36368
36372
  }
36369
36373
  }
36370
36374
  const legacy = store[environment];
36371
36375
  if (legacy) {
36372
- const result = CredentialsSchema.safeParse(legacy);
36376
+ const result = StoredCredentialsSchema.safeParse(legacy);
36373
36377
  if (result.success) {
36374
36378
  return result.data;
36375
36379
  }
36376
36380
  }
36377
36381
  return null;
36378
36382
  }
36383
+ async function getStoredCredentials(environment) {
36384
+ const store = await readCredentialsStore();
36385
+ return getStoredCredentialsFromStore(store, environment);
36386
+ }
36387
+ function toCredentials(stored) {
36388
+ if (stored?.clientId && stored.clientSecret) {
36389
+ return {
36390
+ clientId: stored.clientId,
36391
+ clientSecret: stored.clientSecret,
36392
+ email: stored.email
36393
+ };
36394
+ }
36395
+ return null;
36396
+ }
36397
+ async function getSavedCredentials(environment) {
36398
+ const stored = await getStoredCredentials(environment);
36399
+ return toCredentials(stored);
36400
+ }
36379
36401
  async function saveCredentials(environment, credentials) {
36380
36402
  const store = await readCredentialsStore();
36381
36403
  store.timeback = store.timeback ?? {};
@@ -36383,6 +36405,44 @@ async function saveCredentials(environment, credentials) {
36383
36405
  delete store[environment];
36384
36406
  return writeCredentialsStore(store);
36385
36407
  }
36408
+ async function saveCredentialEmail(environment, email3) {
36409
+ const store = await readCredentialsStore();
36410
+ const existing = getStoredCredentialsFromStore(store, environment);
36411
+ if (email3) {
36412
+ store.timeback = store.timeback ?? {};
36413
+ store.timeback[environment] = {
36414
+ ...existing ?? {},
36415
+ email: email3
36416
+ };
36417
+ } else if (existing?.clientId && existing.clientSecret) {
36418
+ store.timeback = store.timeback ?? {};
36419
+ store.timeback[environment] = {
36420
+ clientId: existing.clientId,
36421
+ clientSecret: existing.clientSecret
36422
+ };
36423
+ } else {
36424
+ delete store.timeback?.[environment];
36425
+ if (store.timeback && Object.keys(store.timeback).length === 0) {
36426
+ delete store.timeback;
36427
+ }
36428
+ }
36429
+ delete store[environment];
36430
+ return writeCredentialsStore(store);
36431
+ }
36432
+ async function getStoredCredentialEmail(environment) {
36433
+ const stored = await getStoredCredentials(environment);
36434
+ return stored?.email;
36435
+ }
36436
+ async function getEnvironmentsWithStoredEmail() {
36437
+ const store = await readCredentialsStore();
36438
+ const environments = [];
36439
+ for (const env2 of ENVIRONMENTS) {
36440
+ if (getStoredCredentialsFromStore(store, env2)?.email) {
36441
+ environments.push(env2);
36442
+ }
36443
+ }
36444
+ return environments;
36445
+ }
36386
36446
  async function clearCredentials(environment) {
36387
36447
  const store = await readCredentialsStore();
36388
36448
  if (environment) {
@@ -36403,18 +36463,23 @@ async function getConfiguredEnvironments() {
36403
36463
  const store = await readCredentialsStore();
36404
36464
  const configured = [];
36405
36465
  for (const env2 of ENVIRONMENTS) {
36406
- const namespaced = store.timeback?.[env2];
36407
- if (namespaced && CredentialsSchema.safeParse(namespaced).success) {
36408
- configured.push(env2);
36409
- continue;
36410
- }
36411
- const legacy = store[env2];
36412
- if (legacy && CredentialsSchema.safeParse(legacy).success) {
36466
+ if (toCredentials(getStoredCredentialsFromStore(store, env2))) {
36413
36467
  configured.push(env2);
36414
36468
  }
36415
36469
  }
36416
36470
  return configured;
36417
36471
  }
36472
+ async function getAvailableEnvironments() {
36473
+ const available = await getConfiguredEnvironments();
36474
+ if (getEnvCredentials()) {
36475
+ for (const env2 of ENVIRONMENTS) {
36476
+ if (!available.includes(env2)) {
36477
+ available.push(env2);
36478
+ }
36479
+ }
36480
+ }
36481
+ return available;
36482
+ }
36418
36483
  function getEnvCredentials() {
36419
36484
  const clientId = process.env.TIMEBACK_API_CLIENT_ID ?? process.env.TIMEBACK_CLIENT_ID;
36420
36485
  const clientSecret = process.env.TIMEBACK_API_CLIENT_SECRET ?? process.env.TIMEBACK_CLIENT_SECRET;
@@ -36428,10 +36493,12 @@ function getEnvCredentials() {
36428
36493
  }
36429
36494
  async function loadCredentials(environment) {
36430
36495
  const envCreds = getEnvCredentials();
36496
+ const storedCreds = await getStoredCredentials(environment);
36431
36497
  if (envCreds) {
36432
- return { success: true, credentials: envCreds, source: "env" };
36498
+ const merged = storedCreds?.email ? { ...envCreds, email: storedCreds.email } : envCreds;
36499
+ return { success: true, credentials: merged, source: "env" };
36433
36500
  }
36434
- const savedCreds = await getSavedCredentials(environment);
36501
+ const savedCreds = toCredentials(storedCreds);
36435
36502
  if (savedCreds) {
36436
36503
  return { success: true, credentials: savedCreds, source: "saved" };
36437
36504
  }
@@ -39316,7 +39383,7 @@ You need to configure your Timeback API credentials first.`, "Credentials Setup"
39316
39383
  }
39317
39384
  async function runImportFlow(opts = {}) {
39318
39385
  const { format: format2 = true } = opts;
39319
- let configuredEnvs = await getConfiguredEnvironments();
39386
+ let configuredEnvs = await getAvailableEnvironments();
39320
39387
  if (configuredEnvs.length === 0) {
39321
39388
  const setupResult = await setupCredentials();
39322
39389
  if (setupResult === "cancelled") {
@@ -39325,7 +39392,7 @@ async function runImportFlow(opts = {}) {
39325
39392
  if (setupResult === "error") {
39326
39393
  return { success: false, error: "Credential validation failed" };
39327
39394
  }
39328
- configuredEnvs = await getConfiguredEnvironments();
39395
+ configuredEnvs = await getAvailableEnvironments();
39329
39396
  if (configuredEnvs.length === 0) {
39330
39397
  return { success: false, error: "Failed to configure credentials" };
39331
39398
  }
@@ -47245,11 +47312,15 @@ function exitCancelled(isOverwriting, exitOnComplete) {
47245
47312
  // src/commands/credentials/email.ts
47246
47313
  async function updateEmail(options = {}) {
47247
47314
  const { exitOnComplete = true, env: envOption } = options;
47248
- const configuredEnvs = [];
47249
- for (const env2 of ENVIRONMENTS) {
47250
- const creds = await getSavedCredentials(env2);
47251
- if (creds)
47252
- configuredEnvs.push(env2);
47315
+ const emailEnvs = await getEnvironmentsWithStoredEmail();
47316
+ const requestedEnv = envOption && ENVIRONMENTS.includes(envOption) ? envOption : undefined;
47317
+ let configuredEnvs;
47318
+ if (requestedEnv) {
47319
+ const hasStoredEmail = await getStoredCredentialEmail(requestedEnv);
47320
+ const hasUsableCredentials = (await loadCredentials(requestedEnv)).success;
47321
+ configuredEnvs = hasStoredEmail || hasUsableCredentials ? [requestedEnv] : [];
47322
+ } else {
47323
+ configuredEnvs = [...new Set([...await getAvailableEnvironments(), ...emailEnvs])];
47253
47324
  }
47254
47325
  if (configuredEnvs.length === 0) {
47255
47326
  M2.warn("No credentials configured");
@@ -47279,15 +47350,8 @@ async function updateEmail(options = {}) {
47279
47350
  }
47280
47351
  targetEnv = selected;
47281
47352
  }
47282
- const currentCreds = await getSavedCredentials(targetEnv);
47283
- if (!currentCreds) {
47284
- M2.error("Credentials not found");
47285
- outro.error("Credentials not found");
47286
- if (exitOnComplete)
47287
- process.exit(0);
47288
- return false;
47289
- }
47290
- const currentEmail = currentCreds.email;
47353
+ const currentCredsResult = await loadCredentials(targetEnv);
47354
+ const currentEmail = currentCredsResult.success ? currentCredsResult.credentials.email : await getStoredCredentialEmail(targetEnv);
47291
47355
  const message = currentEmail ? `Email ${dim(`(current: ${currentEmail})`)}` : `Email ${dim("(not set)")}`;
47292
47356
  const email3 = await he({
47293
47357
  message,
@@ -47316,6 +47380,14 @@ async function updateEmail(options = {}) {
47316
47380
  return true;
47317
47381
  }
47318
47382
  if (normalizedEmail) {
47383
+ if (!currentCredsResult.success) {
47384
+ M2.error("Credentials required to set email");
47385
+ outro.error("Credentials required to set email");
47386
+ if (exitOnComplete)
47387
+ process.exit(0);
47388
+ return false;
47389
+ }
47390
+ const currentCreds = currentCredsResult.credentials;
47319
47391
  const s = Y2();
47320
47392
  s.start("Validating email...");
47321
47393
  const result = await validateEmailWithTimeback(targetEnv, currentCreds.clientId, currentCreds.clientSecret, normalizedEmail);
@@ -47336,10 +47408,7 @@ async function updateEmail(options = {}) {
47336
47408
  email: normalizedEmail
47337
47409
  });
47338
47410
  if (accountCreated) {
47339
- await saveCredentials(targetEnv, {
47340
- ...currentCreds,
47341
- email: normalizedEmail
47342
- });
47411
+ await saveCredentialEmail(targetEnv, normalizedEmail);
47343
47412
  M2.success(`Email saved for ${targetEnv}`);
47344
47413
  outro.success();
47345
47414
  } else {
@@ -47349,20 +47418,14 @@ async function updateEmail(options = {}) {
47349
47418
  process.exit(0);
47350
47419
  return accountCreated;
47351
47420
  }
47352
- await saveCredentials(targetEnv, {
47353
- ...currentCreds,
47354
- email: normalizedEmail
47355
- });
47421
+ await saveCredentialEmail(targetEnv, normalizedEmail);
47356
47422
  s.stop(green(`Email updated for ${targetEnv}`));
47357
47423
  outro.success();
47358
47424
  if (exitOnComplete)
47359
47425
  process.exit(0);
47360
47426
  return true;
47361
47427
  }
47362
- await saveCredentials(targetEnv, {
47363
- ...currentCreds,
47364
- email: undefined
47365
- });
47428
+ await saveCredentialEmail(targetEnv, undefined);
47366
47429
  outro.success(`Email cleared for ${targetEnv}`);
47367
47430
  if (exitOnComplete)
47368
47431
  process.exit(0);
@@ -47380,13 +47443,19 @@ async function listCredentials(options = {}) {
47380
47443
  const { exitOnComplete = true } = options;
47381
47444
  const timebackLines = [];
47382
47445
  const masteryTrackLines = [];
47446
+ const envCreds = getEnvCredentials();
47383
47447
  let hasAnyTimeback = false;
47384
47448
  let hasAnyMasteryTrack = false;
47449
+ let hasSavedTimeback = false;
47450
+ const hasEnvTimeback = Boolean(envCreds);
47385
47451
  for (const env2 of ENVIRONMENTS) {
47386
47452
  const creds = await getSavedCredentials(env2);
47387
- if (creds) {
47453
+ if (creds && hasEnvTimeback) {
47454
+ timebackLines.push(`${dim("●")} ${env2}: ${creds.clientId}`);
47455
+ hasSavedTimeback = true;
47456
+ } else if (creds) {
47388
47457
  timebackLines.push(`${green("●")} ${env2}: ${creds.clientId}`);
47389
- hasAnyTimeback = true;
47458
+ hasSavedTimeback = true;
47390
47459
  } else {
47391
47460
  timebackLines.push(`${dim("○")} ${env2}: not configured`);
47392
47461
  }
@@ -47398,12 +47467,26 @@ async function listCredentials(options = {}) {
47398
47467
  masteryTrackLines.push(`${dim("○")} ${env2}: not configured`);
47399
47468
  }
47400
47469
  }
47470
+ hasAnyTimeback = hasSavedTimeback || hasEnvTimeback;
47471
+ if (envCreds) {
47472
+ timebackLines.push(`${green("●")} env: ${envCreds.clientId} ${yellow("[override]")}`);
47473
+ }
47401
47474
  Me(timebackLines.join(`
47402
47475
  `), "Timeback");
47403
47476
  Me(masteryTrackLines.join(`
47404
47477
  `), "MasteryTrack");
47405
47478
  if (hasAnyTimeback || hasAnyMasteryTrack) {
47406
- outro.info(`Credentials stored in ${blueBright(underline(tildify(getCredentialsPath())))}`);
47479
+ const details = [];
47480
+ if (hasSavedTimeback || hasAnyMasteryTrack) {
47481
+ details.push(`Credentials stored in ${blueBright(underline(tildify(getCredentialsPath())))}`);
47482
+ }
47483
+ if (hasEnvTimeback) {
47484
+ details.push("Timeback env credentials are active");
47485
+ }
47486
+ if (details.length > 0) {
47487
+ outro.info(details.join(`
47488
+ `));
47489
+ }
47407
47490
  } else {
47408
47491
  const shouldAdd = await ye({
47409
47492
  message: "No credentials configured. Add now?"
@@ -47445,7 +47528,7 @@ async function removeCredentials(options = {}) {
47445
47528
  configuredEnvs.push(env2);
47446
47529
  }
47447
47530
  if (configuredEnvs.length === 0) {
47448
- M2.warn(provider === "masterytrack" ? "No MasteryTrack credentials configured" : "No credentials configured");
47531
+ M2.warn(provider === "masterytrack" ? "No saved MasteryTrack credentials configured" : "No saved credentials configured");
47449
47532
  if (exitOnComplete)
47450
47533
  process.exit(0);
47451
47534
  return;
@@ -48264,7 +48347,7 @@ You need to configure your Timeback API credentials first.`, "Credentials Setup"
48264
48347
  return "ok";
48265
48348
  }
48266
48349
  async function ensureConfiguredEnvironments(targetEnv) {
48267
- let configuredEnvs = await getConfiguredEnvironments();
48350
+ let configuredEnvs = await getAvailableEnvironments();
48268
48351
  const needsSetup = targetEnv ? !configuredEnvs.includes(targetEnv) : configuredEnvs.length === 0;
48269
48352
  if (needsSetup) {
48270
48353
  const setupResult = await setupCredentials2(targetEnv);
@@ -48274,7 +48357,7 @@ async function ensureConfiguredEnvironments(targetEnv) {
48274
48357
  if (setupResult === "error") {
48275
48358
  return { status: "error" };
48276
48359
  }
48277
- configuredEnvs = await getConfiguredEnvironments();
48360
+ configuredEnvs = await getAvailableEnvironments();
48278
48361
  const stillMissing = targetEnv ? !configuredEnvs.includes(targetEnv) : configuredEnvs.length === 0;
48279
48362
  if (stillMissing) {
48280
48363
  return { status: "error" };
@@ -52619,8 +52702,8 @@ async function installAndDisplay(options) {
52619
52702
 
52620
52703
  // src/commands/init/lib/postInit.ts
52621
52704
  async function ensureSyncEmail(targetEnv) {
52622
- const creds = await getSavedCredentials(targetEnv);
52623
- if (creds && !creds.email) {
52705
+ const result = await loadCredentials(targetEnv);
52706
+ if (result.success && !result.credentials.email) {
52624
52707
  return await updateEmail({ exitOnComplete: false, env: targetEnv });
52625
52708
  }
52626
52709
  return true;
@@ -78345,8 +78428,13 @@ var CredentialsSchema2 = exports_external2.object({
78345
78428
  clientSecret: NonEmptyString4,
78346
78429
  email: exports_external2.email("Valid email is required").optional()
78347
78430
  });
78348
- var ClientIdSchema2 = exports_external2.string().min(1, "Client ID is required").regex(/^[a-z0-9]+$/, "Client ID must contain only lowercase letters and numbers");
78349
- var ClientSecretSchema2 = exports_external2.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");
78431
+ var StoredCredentialsSchema2 = exports_external2.object({
78432
+ clientId: NonEmptyString4.optional(),
78433
+ clientSecret: NonEmptyString4.optional(),
78434
+ email: exports_external2.email("Valid email is required").optional()
78435
+ }).refine((value) => value.email || value.clientId && value.clientSecret, "Saved credentials must include an email or a client ID/client secret pair").refine((value) => Boolean(value.clientId) === Boolean(value.clientSecret), "Saved credentials must include both client ID and client secret");
78436
+ var ClientIdSchema2 = exports_external2.string().min(1, "Client ID is required").max(128, "Client ID must be at most 128 characters").regex(/^[\w+]+$/, "Client ID must contain only letters, digits, underscores, or +");
78437
+ var ClientSecretSchema2 = exports_external2.string().min(24, "Client secret must be at least 24 characters").max(64, "Client secret must be at most 64 characters").regex(/^[\w+]+$/, "Client secret must contain only letters, digits, underscores, or +");
78350
78438
  var MasteryTrackCredentialsSchema2 = exports_external2.object({
78351
78439
  apiKey: NonEmptyString4,
78352
78440
  email: exports_external2.email("Valid email is required")
@@ -78667,24 +78755,41 @@ async function writeCredentialsStore2(store) {
78667
78755
  await writeFile3(CREDENTIALS_FILE2, JSON.stringify(cleaned, null, 2), { mode: 384 });
78668
78756
  return true;
78669
78757
  }
78670
- async function getSavedCredentials2(environment) {
78671
- const store = await readCredentialsStore2();
78758
+ function getStoredCredentialsFromStore2(store, environment) {
78672
78759
  const namespaced = store.timeback?.[environment];
78673
78760
  if (namespaced) {
78674
- const result = CredentialsSchema2.safeParse(namespaced);
78761
+ const result = StoredCredentialsSchema2.safeParse(namespaced);
78675
78762
  if (result.success) {
78676
78763
  return result.data;
78677
78764
  }
78678
78765
  }
78679
78766
  const legacy = store[environment];
78680
78767
  if (legacy) {
78681
- const result = CredentialsSchema2.safeParse(legacy);
78768
+ const result = StoredCredentialsSchema2.safeParse(legacy);
78682
78769
  if (result.success) {
78683
78770
  return result.data;
78684
78771
  }
78685
78772
  }
78686
78773
  return null;
78687
78774
  }
78775
+ async function getStoredCredentials2(environment) {
78776
+ const store = await readCredentialsStore2();
78777
+ return getStoredCredentialsFromStore2(store, environment);
78778
+ }
78779
+ function toCredentials2(stored) {
78780
+ if (stored?.clientId && stored.clientSecret) {
78781
+ return {
78782
+ clientId: stored.clientId,
78783
+ clientSecret: stored.clientSecret,
78784
+ email: stored.email
78785
+ };
78786
+ }
78787
+ return null;
78788
+ }
78789
+ async function getSavedCredentials2(environment) {
78790
+ const stored = await getStoredCredentials2(environment);
78791
+ return toCredentials2(stored);
78792
+ }
78688
78793
  async function saveCredentials2(environment, credentials) {
78689
78794
  const store = await readCredentialsStore2();
78690
78795
  store.timeback = store.timeback ?? {};
@@ -78692,6 +78797,44 @@ async function saveCredentials2(environment, credentials) {
78692
78797
  delete store[environment];
78693
78798
  return writeCredentialsStore2(store);
78694
78799
  }
78800
+ async function saveCredentialEmail2(environment, email32) {
78801
+ const store = await readCredentialsStore2();
78802
+ const existing = getStoredCredentialsFromStore2(store, environment);
78803
+ if (email32) {
78804
+ store.timeback = store.timeback ?? {};
78805
+ store.timeback[environment] = {
78806
+ ...existing ?? {},
78807
+ email: email32
78808
+ };
78809
+ } else if (existing?.clientId && existing.clientSecret) {
78810
+ store.timeback = store.timeback ?? {};
78811
+ store.timeback[environment] = {
78812
+ clientId: existing.clientId,
78813
+ clientSecret: existing.clientSecret
78814
+ };
78815
+ } else {
78816
+ delete store.timeback?.[environment];
78817
+ if (store.timeback && Object.keys(store.timeback).length === 0) {
78818
+ delete store.timeback;
78819
+ }
78820
+ }
78821
+ delete store[environment];
78822
+ return writeCredentialsStore2(store);
78823
+ }
78824
+ async function getStoredCredentialEmail2(environment) {
78825
+ const stored = await getStoredCredentials2(environment);
78826
+ return stored?.email;
78827
+ }
78828
+ async function getEnvironmentsWithStoredEmail2() {
78829
+ const store = await readCredentialsStore2();
78830
+ const environments = [];
78831
+ for (const env22 of ENVIRONMENTS2) {
78832
+ if (getStoredCredentialsFromStore2(store, env22)?.email) {
78833
+ environments.push(env22);
78834
+ }
78835
+ }
78836
+ return environments;
78837
+ }
78695
78838
  async function clearCredentials2(environment) {
78696
78839
  const store = await readCredentialsStore2();
78697
78840
  if (environment) {
@@ -78712,18 +78855,23 @@ async function getConfiguredEnvironments2() {
78712
78855
  const store = await readCredentialsStore2();
78713
78856
  const configured = [];
78714
78857
  for (const env22 of ENVIRONMENTS2) {
78715
- const namespaced = store.timeback?.[env22];
78716
- if (namespaced && CredentialsSchema2.safeParse(namespaced).success) {
78717
- configured.push(env22);
78718
- continue;
78719
- }
78720
- const legacy = store[env22];
78721
- if (legacy && CredentialsSchema2.safeParse(legacy).success) {
78858
+ if (toCredentials2(getStoredCredentialsFromStore2(store, env22))) {
78722
78859
  configured.push(env22);
78723
78860
  }
78724
78861
  }
78725
78862
  return configured;
78726
78863
  }
78864
+ async function getAvailableEnvironments2() {
78865
+ const available = await getConfiguredEnvironments2();
78866
+ if (getEnvCredentials2()) {
78867
+ for (const env22 of ENVIRONMENTS2) {
78868
+ if (!available.includes(env22)) {
78869
+ available.push(env22);
78870
+ }
78871
+ }
78872
+ }
78873
+ return available;
78874
+ }
78727
78875
  function getEnvCredentials2() {
78728
78876
  const clientId = process.env.TIMEBACK_API_CLIENT_ID ?? process.env.TIMEBACK_CLIENT_ID;
78729
78877
  const clientSecret = process.env.TIMEBACK_API_CLIENT_SECRET ?? process.env.TIMEBACK_CLIENT_SECRET;
@@ -78737,10 +78885,12 @@ function getEnvCredentials2() {
78737
78885
  }
78738
78886
  async function loadCredentials2(environment) {
78739
78887
  const envCreds = getEnvCredentials2();
78888
+ const storedCreds = await getStoredCredentials2(environment);
78740
78889
  if (envCreds) {
78741
- return { success: true, credentials: envCreds, source: "env" };
78890
+ const merged = storedCreds?.email ? { ...envCreds, email: storedCreds.email } : envCreds;
78891
+ return { success: true, credentials: merged, source: "env" };
78742
78892
  }
78743
- const savedCreds = await getSavedCredentials2(environment);
78893
+ const savedCreds = toCredentials2(storedCreds);
78744
78894
  if (savedCreds) {
78745
78895
  return { success: true, credentials: savedCreds, source: "saved" };
78746
78896
  }
@@ -78750,7 +78900,7 @@ async function loadCredentials2(environment) {
78750
78900
  };
78751
78901
  }
78752
78902
  async function loadAllCredentials2() {
78753
- const configuredEnvs = await getConfiguredEnvironments2();
78903
+ const configuredEnvs = await getAvailableEnvironments2();
78754
78904
  const credentials = {};
78755
78905
  for (const env22 of configuredEnvs) {
78756
78906
  const result = await loadCredentials2(env22);
@@ -80264,21 +80414,40 @@ async function addCredentials2(options = {}) {
80264
80414
  async function listCredentials2(options = {}) {
80265
80415
  const { exitOnComplete = true, inline = false } = options;
80266
80416
  const lines = [];
80417
+ const envCreds = getEnvCredentials2();
80267
80418
  let hasAny = false;
80419
+ let hasSaved = false;
80420
+ const hasEnv = Boolean(envCreds);
80268
80421
  for (const env22 of ENVIRONMENTS2) {
80269
80422
  const creds = await getSavedCredentials2(env22);
80270
- if (creds) {
80423
+ if (creds && hasEnv) {
80424
+ lines.push(`${dim2("●")} ${env22}: ${creds.clientId}`);
80425
+ hasSaved = true;
80426
+ } else if (creds) {
80271
80427
  lines.push(`${green2("●")} ${env22}: ${creds.clientId}`);
80272
- hasAny = true;
80428
+ hasSaved = true;
80273
80429
  } else {
80274
- lines.push(`${dim2("○")} ${env22}: ${"not configured"}`);
80430
+ lines.push(`${dim2("○")} ${env22}: not configured`);
80275
80431
  }
80276
80432
  }
80433
+ hasAny = hasSaved || hasEnv;
80434
+ if (envCreds) {
80435
+ lines.push(`${green2("●")} env: ${envCreds.clientId} ${yellow2("[override]")}`);
80436
+ }
80277
80437
  Me2(lines.join(`
80278
80438
  `), "Configured environments");
80279
80439
  if (hasAny) {
80280
- if (!inline)
80281
- outro2.info(`Credentials stored in ${blueBright2(underline2(getCredentialsPath2()))}`);
80440
+ const details = [];
80441
+ if (hasSaved) {
80442
+ details.push(`Credentials stored in ${blueBright2(underline2(getCredentialsPath2()))}`);
80443
+ }
80444
+ if (hasEnv) {
80445
+ details.push("Timeback env credentials are active");
80446
+ }
80447
+ if (details.length > 0 && !inline) {
80448
+ outro2.info(details.join(`
80449
+ `));
80450
+ }
80282
80451
  } else {
80283
80452
  const shouldAdd = await ye2({
80284
80453
  message: "No credentials configured. Add now?"
@@ -80295,12 +80464,12 @@ async function listCredentials2(options = {}) {
80295
80464
  }
80296
80465
  async function updateEmail2(options = {}) {
80297
80466
  const { exitOnComplete = true, inline = false } = options;
80298
- const configuredEnvs = [];
80299
- for (const env22 of ENVIRONMENTS2) {
80300
- const creds = await getSavedCredentials2(env22);
80301
- if (creds)
80302
- configuredEnvs.push(env22);
80303
- }
80467
+ const configuredEnvs = [
80468
+ ...new Set([
80469
+ ...await getAvailableEnvironments2(),
80470
+ ...await getEnvironmentsWithStoredEmail2()
80471
+ ])
80472
+ ];
80304
80473
  if (configuredEnvs.length === 0) {
80305
80474
  M22.warn("No credentials configured");
80306
80475
  if (!inline)
@@ -80329,16 +80498,8 @@ async function updateEmail2(options = {}) {
80329
80498
  }
80330
80499
  targetEnv = selected;
80331
80500
  }
80332
- const currentCreds = await getSavedCredentials2(targetEnv);
80333
- if (!currentCreds) {
80334
- M22.error("Credentials not found");
80335
- if (!inline)
80336
- outro2.error("Credentials not found");
80337
- if (exitOnComplete)
80338
- process.exit(0);
80339
- return;
80340
- }
80341
- const currentEmail = currentCreds.email;
80501
+ const currentCredsResult = await loadCredentials2(targetEnv);
80502
+ const currentEmail = currentCredsResult.success ? currentCredsResult.credentials.email : await getStoredCredentialEmail2(targetEnv);
80342
80503
  const message = currentEmail ? `Email ${dim2(`(current: ${currentEmail})`)}` : `Email ${dim2("(not set)")}`;
80343
80504
  const email32 = await he2({
80344
80505
  message,
@@ -80361,7 +80522,23 @@ async function updateEmail2(options = {}) {
80361
80522
  }
80362
80523
  const normalized = email32 ? normalizeEmail2(email32) : undefined;
80363
80524
  const emailUnchanged = (normalized ?? "") === (currentEmail ?? "");
80525
+ if (emailUnchanged) {
80526
+ if (!inline)
80527
+ outro2.info("Email unchanged");
80528
+ if (exitOnComplete)
80529
+ process.exit(0);
80530
+ return;
80531
+ }
80364
80532
  if (normalized) {
80533
+ if (!currentCredsResult.success) {
80534
+ M22.error("Credentials required to set email");
80535
+ if (!inline)
80536
+ outro2.error("Credentials required to set email");
80537
+ if (exitOnComplete)
80538
+ process.exit(0);
80539
+ return;
80540
+ }
80541
+ const currentCreds = currentCredsResult.credentials;
80365
80542
  const s = Y22();
80366
80543
  s.start("Checking account...");
80367
80544
  const result = await validateEmailWithTimeback2(targetEnv, currentCreds.clientId, currentCreds.clientSecret, normalized);
@@ -80382,11 +80559,8 @@ async function updateEmail2(options = {}) {
80382
80559
  clientSecret: currentCreds.clientSecret,
80383
80560
  email: normalized
80384
80561
  });
80385
- if (accountCreated && !emailUnchanged) {
80386
- await saveCredentials2(targetEnv, {
80387
- ...currentCreds,
80388
- email: normalized
80389
- });
80562
+ if (accountCreated) {
80563
+ await saveCredentialEmail2(targetEnv, normalized);
80390
80564
  M22.success(`Email saved for ${targetEnv}`);
80391
80565
  }
80392
80566
  if (!inline) {
@@ -80401,17 +80575,7 @@ async function updateEmail2(options = {}) {
80401
80575
  return;
80402
80576
  }
80403
80577
  s.stop(green2("Account verified"));
80404
- if (emailUnchanged) {
80405
- if (!inline)
80406
- outro2.info("Email unchanged");
80407
- if (exitOnComplete)
80408
- process.exit(0);
80409
- return;
80410
- }
80411
- await saveCredentials2(targetEnv, {
80412
- ...currentCreds,
80413
- email: normalized
80414
- });
80578
+ await saveCredentialEmail2(targetEnv, normalized);
80415
80579
  M22.success(`Email updated for ${targetEnv}`);
80416
80580
  if (!inline)
80417
80581
  outro2.success();
@@ -80419,10 +80583,7 @@ async function updateEmail2(options = {}) {
80419
80583
  process.exit(0);
80420
80584
  return;
80421
80585
  }
80422
- await saveCredentials2(targetEnv, {
80423
- ...currentCreds,
80424
- email: undefined
80425
- });
80586
+ await saveCredentialEmail2(targetEnv, undefined);
80426
80587
  if (!inline)
80427
80588
  outro2.success(`Email cleared for ${targetEnv}`);
80428
80589
  if (exitOnComplete)
@@ -80437,7 +80598,7 @@ async function removeCredentials2(options = {}) {
80437
80598
  configuredEnvs.push(env22);
80438
80599
  }
80439
80600
  if (configuredEnvs.length === 0) {
80440
- M22.warn("No credentials configured");
80601
+ M22.warn("No saved credentials configured");
80441
80602
  if (exitOnComplete)
80442
80603
  process.exit(0);
80443
80604
  return;
@@ -80643,7 +80804,7 @@ async function promptNoConfig(credentials, configuredEnvs, opts = {}) {
80643
80804
  }
80644
80805
  async function resolveConfigSource(courseIds, opts, defaultEnvironment) {
80645
80806
  const parserOpts = { playcademy: opts.playcademy };
80646
- let configuredEnvs = await getConfiguredEnvironments2();
80807
+ let configuredEnvs = await getAvailableEnvironments2();
80647
80808
  let credentials = {};
80648
80809
  if (courseIds.length > 0) {
80649
80810
  if (configuredEnvs.length > 0) {
@@ -82973,8 +83134,8 @@ var cors = (options) => {
82973
83134
  async function handleBootstrap(c, ctx) {
82974
83135
  const { bootstrap } = c.get("services");
82975
83136
  const env22 = c.get("env");
82976
- const freshCredentials = await getSavedCredentials2(env22);
82977
- const email32 = freshCredentials?.email;
83137
+ const freshCredentials = await loadCredentials2(env22);
83138
+ const email32 = freshCredentials.success ? freshCredentials.credentials.email : undefined;
82978
83139
  const courseIds = ctx.userConfig.courseIds[env22];
82979
83140
  const result = await bootstrap.getBootstrap({ email: email32, courseIds });
82980
83141
  return c.json(result);
@@ -84003,18 +84164,18 @@ class StatusService {
84003
84164
  this.ctx = ctx;
84004
84165
  }
84005
84166
  async getStatus() {
84006
- const configuredEnvironments = await getConfiguredEnvironments2();
84167
+ const configuredEnvironments = await getAvailableEnvironments2();
84007
84168
  const [stagingCreds, productionCreds] = await Promise.all([
84008
- getSavedCredentials2("staging"),
84009
- getSavedCredentials2("production")
84169
+ loadCredentials2("staging"),
84170
+ loadCredentials2("production")
84010
84171
  ]);
84011
- const defaultCreds = this.ctx.defaultEnvironment === "staging" ? stagingCreds : this.ctx.defaultEnvironment === "production" ? productionCreds : null;
84012
- const clientId = defaultCreds?.clientId ?? stagingCreds?.clientId ?? productionCreds?.clientId ?? this.ctx.credentials.staging?.clientId ?? this.ctx.credentials.production?.clientId;
84172
+ const defaultCreds = this.ctx.defaultEnvironment === "staging" ? stagingCreds.success ? stagingCreds.credentials : null : this.ctx.defaultEnvironment === "production" ? productionCreds.success ? productionCreds.credentials : null : null;
84173
+ const clientId = defaultCreds?.clientId ?? (stagingCreds.success ? stagingCreds.credentials.clientId : undefined) ?? (productionCreds.success ? productionCreds.credentials.clientId : undefined) ?? this.ctx.credentials.staging?.clientId ?? this.ctx.credentials.production?.clientId;
84013
84174
  return {
84014
84175
  config: this.ctx.userConfig,
84015
84176
  environment: this.ctx.defaultEnvironment,
84016
84177
  configuredEnvironments,
84017
- hasEmail: !!stagingCreds?.email || !!productionCreds?.email,
84178
+ hasEmail: stagingCreds.success && Boolean(stagingCreds.credentials.email) || productionCreds.success && Boolean(productionCreds.credentials.email),
84018
84179
  clientId,
84019
84180
  sensors: this.ctx.derivedSensors
84020
84181
  };
@@ -84664,7 +84825,7 @@ async function launchServer(serverConfig, userConfig, credentials, env22, opts,
84664
84825
  }
84665
84826
  async function handleCourseIdsCase(courseIds, opts, serverConfig) {
84666
84827
  const env22 = opts.env ?? await promptEnvironmentForCourseIds(courseIds.length);
84667
- let configuredEnvs = await getConfiguredEnvironments2();
84828
+ let configuredEnvs = await getAvailableEnvironments2();
84668
84829
  let credentials = configuredEnvs.length > 0 ? await loadAllCredentials2() : {};
84669
84830
  const resolvedResult = await resolveConfigSource(courseIds, opts, env22);
84670
84831
  if (!resolvedResult) {
@@ -84716,7 +84877,7 @@ async function handleConfigFileCase(userConfig, configPath, opts, serverConfig)
84716
84877
  await launchServer(serverConfig, userConfig, credentials, env22, opts, configPath);
84717
84878
  }
84718
84879
  async function handleImportFlowCase(opts, serverConfig) {
84719
- let configuredEnvs = await getConfiguredEnvironments2();
84880
+ let configuredEnvs = await getAvailableEnvironments2();
84720
84881
  let credentials = {};
84721
84882
  let selectedEnv;
84722
84883
  if (configuredEnvs.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "timeback",
3
- "version": "0.2.2-beta.20260401223329",
3
+ "version": "0.2.2-beta.20260402000431",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "timeback": "./dist/cli.js"