archondev 2.19.57 → 3.0.0

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.
@@ -1,10 +1,8 @@
1
- import {
2
- createAuthedSupabaseClient
3
- } from "./chunk-Q3GIFHIQ.js";
4
1
  import {
5
2
  debugLog,
6
- getDebugLogPath
7
- } from "./chunk-R664NEAA.js";
3
+ getDebugLogPath,
4
+ summarizeLocalUsage
5
+ } from "./chunk-I3BBA7MB.js";
8
6
  import {
9
7
  findModel,
10
8
  getAllActiveModels,
@@ -20,16 +18,8 @@ import {
20
18
  keyManager
21
19
  } from "./chunk-RDG5BUED.js";
22
20
  import {
23
- login
24
- } from "./chunk-6TBYNRNF.js";
25
- import {
26
- SUPABASE_ANON_KEY,
27
- SUPABASE_URL
28
- } from "./chunk-M4LGRTLC.js";
29
- import {
30
- getApiUrl,
31
- getAuthToken,
32
- loadConfig
21
+ loadConfig,
22
+ saveConfig
33
23
  } from "./chunk-GGRW4NTA.js";
34
24
 
35
25
  // src/cli/preferences.ts
@@ -414,24 +404,24 @@ var SmartModelRouter = class {
414
404
 
415
405
  // src/cli/preferences.ts
416
406
  var PREF_KEYS = ["fast-model", "thinking-model", "primary-adversarial", "secondary-adversarial"];
417
- function prefKeyToDbColumn(key) {
407
+ function prefKeyToConfigField(key) {
418
408
  const map = {
419
- "fast-model": "pref_fast_model",
420
- "thinking-model": "pref_thinking_model",
421
- "primary-adversarial": "pref_primary_adversarial",
422
- "secondary-adversarial": "pref_secondary_adversarial"
409
+ "fast-model": "fastModel",
410
+ "thinking-model": "thinkingModel",
411
+ "primary-adversarial": "primaryAdversarialModel",
412
+ "secondary-adversarial": "secondaryAdversarialModel"
423
413
  };
424
414
  return map[key];
425
415
  }
426
416
  function isPrefKey(key) {
427
417
  return PREF_KEYS.includes(key);
428
418
  }
429
- function tierPrefKeyToDbColumn(key) {
419
+ function tierPrefKeyToConfigField(key) {
430
420
  const map = {
431
- "planning-model": "pref_planning_model",
432
- "reasoning-model": "pref_reasoning_model",
433
- "execution-model": "pref_execution_model",
434
- "provider": "pref_provider"
421
+ "planning-model": "planningModel",
422
+ "reasoning-model": "reasoningModel",
423
+ "execution-model": "executionModel",
424
+ "provider": "preferredProvider"
435
425
  };
436
426
  return map[key];
437
427
  }
@@ -475,24 +465,20 @@ async function setExecutionPreference(key, value, cwd = process.cwd()) {
475
465
  if (!existing.execution) {
476
466
  existing.execution = {};
477
467
  }
478
- const validKeys = ["parallel.mode", "cloud.mode", "max-parallel-agents"];
468
+ const validKeys = ["parallel.mode", "max-parallel-agents"];
479
469
  if (!validKeys.includes(key)) {
480
470
  console.error(chalk.red(`Invalid execution preference key: ${key}`));
481
471
  console.log(chalk.dim(`Valid keys: ${validKeys.join(", ")}`));
482
472
  process.exit(1);
483
473
  }
484
- if (key === "parallel.mode" || key === "cloud.mode") {
474
+ if (key === "parallel.mode") {
485
475
  const validModes = ["always", "ask", "never"];
486
476
  if (!validModes.includes(value)) {
487
477
  console.error(chalk.red(`Invalid value: ${value}`));
488
478
  console.log(chalk.dim(`Valid values: ${validModes.join(", ")}`));
489
479
  process.exit(1);
490
480
  }
491
- if (key === "parallel.mode") {
492
- existing.execution.parallelMode = value;
493
- } else {
494
- existing.execution.cloudMode = value;
495
- }
481
+ existing.execution.parallelMode = value;
496
482
  } else if (key === "max-parallel-agents") {
497
483
  const num = parseInt(value, 10);
498
484
  if (isNaN(num) || num < 1 || num > 10) {
@@ -510,57 +496,25 @@ async function showExecutionPreferences(cwd = process.cwd()) {
510
496
  console.log(chalk.green("Execution Preferences:"));
511
497
  console.log();
512
498
  console.log(` ${chalk.blue("Parallel Mode:")} ${prefs.parallelMode}`);
513
- console.log(` ${chalk.blue("Cloud Mode:")} ${prefs.cloudMode}`);
514
499
  console.log(` ${chalk.blue("Max Parallel Agents:")} ${prefs.maxParallelAgents}`);
515
500
  }
516
501
  async function fetchPreferences() {
517
502
  const config = await loadConfig();
518
- const authToken = getAuthToken(config);
519
- if (!authToken) {
520
- return null;
521
- }
522
- const apiUrl = getApiUrl(config);
523
- try {
524
- const response = await fetch(`${apiUrl}/api/preferences`, {
525
- headers: {
526
- "Authorization": `Bearer ${authToken}`
527
- }
528
- });
529
- if (!response.ok) {
530
- return null;
531
- }
532
- const data = await response.json();
533
- return {
534
- fastModel: data.pref_fast_model,
535
- thinkingModel: data.pref_thinking_model,
536
- primaryAdversarial: data.pref_primary_adversarial,
537
- secondaryAdversarial: data.pref_secondary_adversarial
538
- };
539
- } catch {
540
- return null;
541
- }
503
+ return {
504
+ fastModel: config.fastModel ?? null,
505
+ thinkingModel: config.thinkingModel ?? null,
506
+ primaryAdversarial: config.primaryAdversarialModel ?? null,
507
+ secondaryAdversarial: config.secondaryAdversarialModel ?? null
508
+ };
542
509
  }
543
510
  async function updatePreference(key, value) {
544
- const config = await loadConfig();
545
- const authToken = getAuthToken(config);
546
- if (!authToken) {
547
- return { success: false, error: "Not logged in. Run: archon login" };
548
- }
549
- const apiUrl = getApiUrl(config);
550
- const column = prefKeyToDbColumn(key);
551
511
  try {
552
- const response = await fetch(`${apiUrl}/api/preferences`, {
553
- method: "PATCH",
554
- headers: {
555
- "Authorization": `Bearer ${authToken}`,
556
- "Content-Type": "application/json"
557
- },
558
- body: JSON.stringify({ [column]: value })
512
+ const config = await loadConfig();
513
+ const field = prefKeyToConfigField(key);
514
+ await saveConfig({
515
+ ...config,
516
+ [field]: value ?? void 0
559
517
  });
560
- if (!response.ok) {
561
- const text = await response.text();
562
- return { success: false, error: text };
563
- }
564
518
  return { success: true };
565
519
  } catch (err) {
566
520
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -689,54 +643,42 @@ async function listModels() {
689
643
  }
690
644
  async function fetchUserProfile() {
691
645
  const config = await loadConfig();
692
- const authToken = getAuthToken(config);
693
- if (!authToken) return null;
694
- const apiUrl = getApiUrl(config);
695
- try {
696
- const response = await fetch(`${apiUrl}/api/preferences`, {
697
- headers: { "Authorization": `Bearer ${authToken}` }
698
- });
699
- if (!response.ok) return null;
700
- return await response.json();
701
- } catch {
702
- return null;
703
- }
646
+ return {
647
+ tier: config.tier ?? "FREE",
648
+ pref_fast_model: config.fastModel ?? null,
649
+ pref_thinking_model: config.thinkingModel ?? null,
650
+ pref_primary_adversarial: config.primaryAdversarialModel ?? null,
651
+ pref_secondary_adversarial: config.secondaryAdversarialModel ?? null,
652
+ pref_planning_model: config.planningModel ?? null,
653
+ pref_reasoning_model: config.reasoningModel ?? null,
654
+ pref_execution_model: config.executionModel ?? null,
655
+ pref_provider: config.preferredProvider ?? null
656
+ };
704
657
  }
705
658
  async function fetchUsageSummary() {
706
- const config = await loadConfig();
707
- const authToken = getAuthToken(config);
708
- if (!authToken) return null;
709
- const apiUrl = getApiUrl(config);
710
659
  try {
711
- const response = await fetch(`${apiUrl}/api/usage`, {
712
- headers: { "Authorization": `Bearer ${authToken}` }
660
+ const now = /* @__PURE__ */ new Date();
661
+ const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
662
+ const summary = await summarizeLocalUsage({
663
+ start: monthStart,
664
+ end: now
713
665
  });
714
- if (!response.ok) return null;
715
- return await response.json();
666
+ return {
667
+ totalInputTokens: summary.totalInputTokens,
668
+ totalOutputTokens: summary.totalOutputTokens,
669
+ totalBaseCost: summary.totalBaseCost,
670
+ totalMarkedUpCost: summary.totalBaseCost,
671
+ periodStart: monthStart.toISOString(),
672
+ periodEnd: now.toISOString()
673
+ };
716
674
  } catch {
717
675
  return null;
718
676
  }
719
677
  }
720
678
  async function updateUserTier(tier) {
721
- const config = await loadConfig();
722
- const authToken = getAuthToken(config);
723
- if (!authToken) {
724
- return { success: false, error: "Not logged in" };
725
- }
726
- const apiUrl = getApiUrl(config);
727
679
  try {
728
- const response = await fetch(`${apiUrl}/api/preferences`, {
729
- method: "PATCH",
730
- headers: {
731
- "Authorization": `Bearer ${authToken}`,
732
- "Content-Type": "application/json"
733
- },
734
- body: JSON.stringify({ tier })
735
- });
736
- if (!response.ok) {
737
- const text = await response.text();
738
- return { success: false, error: text };
739
- }
680
+ const config = await loadConfig();
681
+ await saveConfig({ ...config, tier, tierConfirmed: true });
740
682
  return { success: true };
741
683
  } catch (err) {
742
684
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -755,23 +697,6 @@ function prompt(question) {
755
697
  });
756
698
  });
757
699
  }
758
- function promptYesNo(question, defaultValue) {
759
- return new Promise((resolve) => {
760
- const rl = readline.createInterface({
761
- input: process.stdin,
762
- output: process.stdout
763
- });
764
- const hint = defaultValue ? "(Y/n)" : "(y/N)";
765
- rl.question(`${chalk.cyan("?")} ${question} ${hint}: `, (answer) => {
766
- rl.close();
767
- if (answer.trim() === "") {
768
- resolve(defaultValue);
769
- } else {
770
- resolve(answer.toLowerCase().startsWith("y"));
771
- }
772
- });
773
- });
774
- }
775
700
  function formatCost(dollars) {
776
701
  if (dollars < 0.01) {
777
702
  return `$${dollars.toFixed(4)}`;
@@ -797,18 +722,13 @@ async function interactiveSettings() {
797
722
  ]);
798
723
  spinner.stop();
799
724
  if (!profile) {
800
- console.log(chalk.yellow("Not logged in.\n"));
801
- const shouldLogin = await promptYesNo("Would you like to login now?", true);
802
- if (shouldLogin) {
803
- await login("github");
804
- await interactiveSettings();
805
- }
725
+ console.log(chalk.yellow("Unable to load local settings.\n"));
806
726
  return;
807
727
  }
808
728
  await displayCurrentSettings(profile, usage, providers);
809
729
  console.log(chalk.bold("\nOptions:\n"));
810
730
  console.log(chalk.dim("Type naturally (recommended) or use a shortcut:\n"));
811
- console.log(` ${chalk.cyan("1")}) Change billing mode (BYOK / Credits)`);
731
+ console.log(` ${chalk.cyan("1")}) Change AI mode (Local governance / BYOK)`);
812
732
  console.log(` ${chalk.cyan("2")}) Set default models`);
813
733
  console.log(` ${chalk.cyan("x")}) Smart Routing (cost optimization)`);
814
734
  console.log(` ${chalk.cyan("3")}) Manage API keys`);
@@ -853,8 +773,8 @@ async function interactiveSettings() {
853
773
  }
854
774
  async function displayCurrentSettings(profile, usage, providers) {
855
775
  const defaults = getDefaultAdversarialModels();
856
- const tierDisplay = profile.tier === "BYOK" ? chalk.cyan("BYOK (Bring Your Own Key)") : profile.tier === "CREDITS" ? chalk.green("Credits (Pay via ArchonDev)") : chalk.dim("Free Tier");
857
- console.log(chalk.blue("Billing Mode:"), tierDisplay);
776
+ const tierDisplay = profile.tier === "BYOK" ? chalk.cyan("BYOK (your provider keys)") : chalk.dim("Local governance only");
777
+ console.log(chalk.blue("AI Mode:"), tierDisplay);
858
778
  if (providers.length > 0) {
859
779
  console.log(chalk.blue("API Keys:"), providers.map((p) => chalk.green(p)).join(", "));
860
780
  } else {
@@ -864,14 +784,9 @@ async function displayCurrentSettings(profile, usage, providers) {
864
784
  console.log();
865
785
  console.log(chalk.blue("Current Period Usage:"));
866
786
  console.log(` Tokens: ${chalk.white(formatTokens(usage.totalInputTokens))} in / ${chalk.white(formatTokens(usage.totalOutputTokens))} out`);
867
- if (profile.tier === "CREDITS") {
868
- const billed = usage.totalMarkedUpCost ?? usage.totalBaseCost;
869
- console.log(` Credits Used: ${chalk.white(formatCost(billed))}`);
870
- } else {
871
- console.log(` Estimated Cost: ${chalk.white(formatCost(usage.totalBaseCost))}`);
872
- if (profile.tier === "BYOK") {
873
- console.log(chalk.dim(" (You pay your provider directly)"));
874
- }
787
+ console.log(` Estimated Cost: ${chalk.white(formatCost(usage.totalBaseCost))}`);
788
+ if (profile.tier === "BYOK") {
789
+ console.log(chalk.dim(" (You pay your provider directly)"));
875
790
  }
876
791
  }
877
792
  console.log();
@@ -882,40 +797,40 @@ async function displayCurrentSettings(profile, usage, providers) {
882
797
  console.log(` Secondary: ${formatModelDisplay(profile.pref_secondary_adversarial, defaults.secondary)}`);
883
798
  }
884
799
  async function changeBillingMode(currentTier) {
885
- console.log(chalk.bold("\n-- Billing Mode --\n"));
886
- console.log(chalk.dim("Choose how you want to pay for AI usage:\n"));
800
+ console.log(chalk.bold("\n-- AI Mode --\n"));
801
+ console.log(chalk.dim("Choose whether Archon should stay governance-only or use your provider keys.\n"));
802
+ const freeSelected = currentTier !== "BYOK";
887
803
  const byokSelected = currentTier === "BYOK";
888
- const creditsSelected = currentTier === "CREDITS";
889
- console.log(` ${chalk.cyan("1")}) ${byokSelected ? chalk.green("\u25CF ") : "\u25CB "}BYOK - Bring Your Own Key`);
804
+ console.log(` ${chalk.cyan("1")}) ${freeSelected ? chalk.green("\u25CF ") : "\u25CB "}Local governance only`);
805
+ console.log(chalk.dim(" Plan, review, and govern locally without making provider AI calls.\n"));
806
+ console.log(` ${chalk.cyan("2")}) ${byokSelected ? chalk.green("\u25CF ") : "\u25CB "}BYOK - Bring Your Own Key`);
890
807
  console.log(chalk.dim(" Use your own API keys. You pay providers directly."));
891
- console.log(chalk.dim(" We track usage so you can see approximate costs.\n"));
892
- console.log(` ${chalk.cyan("2")}) ${creditsSelected ? chalk.green("\u25CF ") : "\u25CB "}Credits - Pay via ArchonDev`);
893
- console.log(chalk.dim(" We handle API access. Pay only for what you use."));
894
- console.log(chalk.dim(" 10% service fee applied.\n"));
808
+ console.log(chalk.dim(" Archon keeps a local usage ledger for transparency.\n"));
895
809
  console.log(` ${chalk.cyan("b")}) Back`);
896
810
  console.log();
897
- const rawChoice = await prompt("How do you want to pay?");
811
+ const rawChoice = await prompt("Which AI mode do you want?");
898
812
  const choice = resolveBillingChoice(rawChoice);
899
- if (choice === "1" && !byokSelected) {
900
- const spinner = ora("Switching to BYOK...").start();
901
- const result = await updateUserTier("BYOK");
813
+ if (choice === "1" && !freeSelected) {
814
+ const spinner = ora("Switching to local governance mode...").start();
815
+ const result = await updateUserTier("FREE");
902
816
  if (result.success) {
903
- spinner.succeed(chalk.green("Switched to BYOK mode"));
904
- console.log(chalk.dim("Add API keys with: archon keys add <provider>"));
817
+ spinner.succeed(chalk.green("Switched to local governance mode"));
818
+ console.log(chalk.dim("Add provider keys anytime with: archon config ai"));
905
819
  } else {
906
820
  spinner.fail(chalk.red(result.error ?? "Failed to update"));
907
821
  }
908
- } else if (choice === "2" && !creditsSelected) {
909
- const spinner = ora("Switching to Credits...").start();
910
- const result = await updateUserTier("CREDITS");
822
+ } else if (choice === "2" && !byokSelected) {
823
+ const spinner = ora("Switching to BYOK...").start();
824
+ const result = await updateUserTier("BYOK");
911
825
  if (result.success) {
912
- spinner.succeed(chalk.green("Switched to Credits mode"));
826
+ spinner.succeed(chalk.green("Switched to BYOK mode"));
827
+ console.log(chalk.dim("Add API keys with: archon keys add <provider>"));
913
828
  } else {
914
829
  spinner.fail(chalk.red(result.error ?? "Failed to update"));
915
830
  }
916
831
  } else if (choice !== "b") {
917
- if (choice === "1" && byokSelected || choice === "2" && creditsSelected) {
918
- console.log(chalk.dim("Already using this billing mode."));
832
+ if (choice === "1" && freeSelected || choice === "2" && byokSelected) {
833
+ console.log(chalk.dim("Already using this AI mode."));
919
834
  } else {
920
835
  console.log(chalk.yellow("I did not catch that."));
921
836
  }
@@ -948,11 +863,9 @@ async function setDefaultModelsMenu(profile, providers) {
948
863
  return;
949
864
  }
950
865
  const models = getAllActiveModels();
951
- const availableModels = models.filter(
952
- (m) => providers.includes(m.provider) || profile.tier === "CREDITS"
953
- );
866
+ const availableModels = models.filter((m) => providers.includes(m.provider));
954
867
  if (availableModels.length === 0) {
955
- console.log(chalk.yellow("\nNo models available. Add API keys or switch to Credits mode."));
868
+ console.log(chalk.yellow("\nNo models available. Add at least one provider API key first."));
956
869
  return;
957
870
  }
958
871
  console.log(chalk.bold("\nAvailable models:\n"));
@@ -1038,11 +951,9 @@ async function setSmartRoutingMenu(profile, providers) {
1038
951
  const selectedTier = tiers[tierIndex];
1039
952
  if (!selectedTier) return;
1040
953
  const models = getAllActiveModels();
1041
- const availableModels = models.filter(
1042
- (m) => providers.includes(m.provider) || profile.tier === "CREDITS"
1043
- );
954
+ const availableModels = models.filter((m) => providers.includes(m.provider));
1044
955
  if (availableModels.length === 0) {
1045
- console.log(chalk.yellow("\nNo models available. Add API keys or switch to Credits mode."));
956
+ console.log(chalk.yellow("\nNo models available. Add at least one provider API key first."));
1046
957
  return;
1047
958
  }
1048
959
  console.log(chalk.bold(`
@@ -1071,28 +982,14 @@ Select model for ${selectedTier.tier} tier:
1071
982
  }
1072
983
  }
1073
984
  async function setTierPreference(key, value) {
1074
- const config = await loadConfig();
1075
- const token = getAuthToken(config);
1076
- if (!token) {
1077
- console.error(chalk.red("Not logged in. Run: archon auth login"));
1078
- return;
1079
- }
1080
985
  const spinner = ora(`Updating ${key}...`).start();
1081
986
  try {
1082
- const apiUrl = getApiUrl(config);
1083
- const dbColumn = tierPrefKeyToDbColumn(key);
1084
- const response = await fetch(`${apiUrl}/api/user/preferences`, {
1085
- method: "POST",
1086
- headers: {
1087
- "Content-Type": "application/json",
1088
- "Authorization": `Bearer ${token}`
1089
- },
1090
- body: JSON.stringify({ [dbColumn]: value })
987
+ const config = await loadConfig();
988
+ const field = tierPrefKeyToConfigField(key);
989
+ await saveConfig({
990
+ ...config,
991
+ [field]: value ?? void 0
1091
992
  });
1092
- if (!response.ok) {
1093
- const error = await response.text();
1094
- throw new Error(error || "Failed to update preference");
1095
- }
1096
993
  spinner.succeed(chalk.green(`Set ${key} = ${value || "(auto)"}`));
1097
994
  } catch (error) {
1098
995
  spinner.fail(chalk.red(`Failed to update: ${error instanceof Error ? error.message : "Unknown error"}`));
@@ -1165,8 +1062,8 @@ function resolveSettingsChoice(input) {
1165
1062
  function resolveBillingChoice(input) {
1166
1063
  const normalized = input.trim().toLowerCase();
1167
1064
  if (!normalized) return "";
1168
- if (normalized === "1" || normalized.includes("byok") || normalized.includes("own key")) return "1";
1169
- if (normalized === "2" || normalized.includes("credit") || normalized.includes("managed")) return "2";
1065
+ if (normalized === "1" || normalized.includes("free") || normalized.includes("local") || normalized.includes("governance")) return "1";
1066
+ if (normalized === "2" || normalized.includes("byok") || normalized.includes("own key")) return "2";
1170
1067
  if (normalized === "b" || normalized.includes("back")) return "b";
1171
1068
  return normalized;
1172
1069
  }
@@ -1237,21 +1134,6 @@ var __preferencesChoiceHelpers = {
1237
1134
  resolveProviderIndexChoice,
1238
1135
  resolveModelChoice
1239
1136
  };
1240
- function formatLocalDateTime(value) {
1241
- const date = new Date(value);
1242
- if (isNaN(date.getTime())) {
1243
- return value;
1244
- }
1245
- return date.toLocaleString();
1246
- }
1247
- function formatUsagePeriodLabel(periodSource) {
1248
- if (periodSource === "credit_purchase") return "Since last top-up";
1249
- if (periodSource === "month") return "Current month to date";
1250
- return "Current billing period";
1251
- }
1252
- function sumModelCost(byModel) {
1253
- return (byModel ?? []).reduce((sum, row) => sum + row.cost, 0);
1254
- }
1255
1137
  function renderModelBreakdown(rows, options = {}) {
1256
1138
  const indent = options.indent ?? " ";
1257
1139
  if (rows.length === 0) {
@@ -1269,62 +1151,6 @@ function renderModelBreakdown(rows, options = {}) {
1269
1151
  console.log(`${indent}${name}: ${formatTokens(tokenTotal)} tokens | ${formatCost(row.cost)} (${share}%)${note}`);
1270
1152
  }
1271
1153
  }
1272
- async function resolveAuthAndProfileIds(authToken, fallbackAuthId) {
1273
- const supabase = createAuthedSupabaseClient(SUPABASE_URL, SUPABASE_ANON_KEY, authToken);
1274
- let authId = fallbackAuthId ?? null;
1275
- if (!authId) {
1276
- const { data, error } = await supabase.auth.getUser();
1277
- if (error || !data.user?.id) {
1278
- return { authId: null, profileId: null };
1279
- }
1280
- authId = data.user.id;
1281
- }
1282
- const { data: profileRow, error: profileError } = await supabase.from("user_profiles").select("id").eq("auth_id", authId).single();
1283
- if (profileError || !profileRow?.id) {
1284
- return { authId, profileId: null };
1285
- }
1286
- return { authId, profileId: profileRow.id };
1287
- }
1288
- async function fetchUsageRows(authToken, usageUserIds, startIso, endIso) {
1289
- if (usageUserIds.length === 0) {
1290
- return [];
1291
- }
1292
- const supabase = createAuthedSupabaseClient(SUPABASE_URL, SUPABASE_ANON_KEY, authToken);
1293
- const usageQuery = supabase.from("token_usage").select("model, input_tokens, output_tokens, base_cost, created_at").gte("created_at", startIso).lte("created_at", endIso);
1294
- const scopedQuery = usageUserIds.length === 1 ? usageQuery.eq("user_id", usageUserIds[0]) : usageQuery.in("user_id", usageUserIds);
1295
- const { data, error } = await scopedQuery;
1296
- if (error || !data) {
1297
- return null;
1298
- }
1299
- return data;
1300
- }
1301
- function aggregateUsageRows(rows, start, end) {
1302
- let totalInputTokens = 0;
1303
- let totalOutputTokens = 0;
1304
- let totalCost = 0;
1305
- const byModelMap = /* @__PURE__ */ new Map();
1306
- for (const row of rows) {
1307
- const ts = new Date(row.created_at);
1308
- if (ts < start || ts > end) continue;
1309
- totalInputTokens += row.input_tokens;
1310
- totalOutputTokens += row.output_tokens;
1311
- totalCost += row.base_cost;
1312
- const existing = byModelMap.get(row.model);
1313
- if (existing) {
1314
- existing.inputTokens += row.input_tokens;
1315
- existing.outputTokens += row.output_tokens;
1316
- existing.cost += row.base_cost;
1317
- } else {
1318
- byModelMap.set(row.model, {
1319
- inputTokens: row.input_tokens,
1320
- outputTokens: row.output_tokens,
1321
- cost: row.base_cost
1322
- });
1323
- }
1324
- }
1325
- const byModel = Array.from(byModelMap.entries()).map(([model, stats]) => ({ model, ...stats })).sort((a, b) => b.cost - a.cost);
1326
- return { totalInputTokens, totalOutputTokens, totalCost, byModel };
1327
- }
1328
1154
  async function showUsageDetails(options = {}) {
1329
1155
  const { pauseForInput = false, debug = false } = options;
1330
1156
  console.log(chalk.bold("\n-- Usage Details --\n"));
@@ -1337,131 +1163,54 @@ async function showUsageDetails(options = {}) {
1337
1163
  }
1338
1164
  }
1339
1165
  const spinner = ora("Loading usage data...").start();
1340
- const config = await loadConfig();
1341
- const authToken = getAuthToken(config);
1342
- const authId = config.userId;
1343
- if (debug) {
1344
- debugLog("usage", "start", "Usage command started", {
1345
- authId: authId ?? null,
1346
- hasToken: !!authToken
1347
- });
1348
- }
1349
- if (!authToken) {
1350
- spinner.fail("Not logged in");
1351
- return;
1352
- }
1353
- const apiUrl = getApiUrl(config);
1354
1166
  try {
1355
- const response = await fetch(`${apiUrl}/api/usage`, {
1356
- headers: { "Authorization": `Bearer ${authToken}` }
1357
- });
1167
+ spinner.stop();
1168
+ const now = /* @__PURE__ */ new Date();
1169
+ const yearStart = new Date(now.getFullYear(), 0, 1);
1170
+ const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1171
+ const startOf24h = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
1172
+ const startOf7d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
1173
+ const startOf30d = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
1358
1174
  if (debug) {
1359
- debugLog("usage", "api.response", "Received /api/usage response", {
1360
- status: response.status
1175
+ debugLog("usage", "start", "Usage command started from local ledger", {
1176
+ periodStart: yearStart.toISOString(),
1177
+ periodEnd: now.toISOString()
1361
1178
  });
1362
1179
  }
1363
- spinner.stop();
1364
- if (!response.ok) {
1365
- console.log(chalk.yellow("Unable to fetch usage data."));
1366
- return;
1367
- }
1368
- const data = await response.json();
1369
- if (data.tier === "BYOK") {
1370
- const ids = await resolveAuthAndProfileIds(authToken, authId);
1371
- const usageUserIds = Array.from(new Set([ids.profileId, ids.authId].filter((id) => Boolean(id))));
1372
- if (debug) {
1373
- debugLog("usage", "byok.identity", "Resolved auth/profile IDs for BYOK usage command", ids);
1374
- }
1375
- if (usageUserIds.length === 0) {
1376
- console.log(chalk.yellow("Unable to resolve user ID for usage details."));
1377
- return;
1378
- }
1379
- const now = /* @__PURE__ */ new Date();
1380
- const yearStart = new Date(now.getFullYear(), 0, 1);
1381
- const usageRows = await fetchUsageRows(authToken, usageUserIds, yearStart.toISOString(), now.toISOString());
1180
+ const periods = [
1181
+ { label: "Today", start: startOfToday, end: now },
1182
+ { label: "Last 24 Hours", start: startOf24h, end: now },
1183
+ { label: "Last 7 Days", start: startOf7d, end: now },
1184
+ { label: "Last 30 Days", start: startOf30d, end: now },
1185
+ { label: "Year to Date", start: yearStart, end: now }
1186
+ ];
1187
+ console.log(chalk.blue("Local BYOK Usage (local time):"));
1188
+ console.log(chalk.dim("Estimated provider spend from the local usage ledger. No usage data is sent to Archon-managed servers."));
1189
+ for (const period of periods) {
1190
+ const summary = await summarizeLocalUsage({ start: period.start, end: period.end });
1382
1191
  if (debug) {
1383
- debugLog("usage", "byok.rows", "Fetched BYOK token usage rows", {
1384
- usageUserIds,
1385
- rowCount: usageRows?.length ?? 0,
1386
- start: yearStart.toISOString(),
1387
- end: now.toISOString()
1192
+ debugLog("usage", "period.summary", "Computed local usage period", {
1193
+ label: period.label,
1194
+ totalInputTokens: summary.totalInputTokens,
1195
+ totalOutputTokens: summary.totalOutputTokens,
1196
+ totalBaseCost: summary.totalBaseCost,
1197
+ byModelCount: summary.byModel.length
1388
1198
  });
1389
1199
  }
1390
- if (!usageRows) {
1391
- console.log(chalk.yellow("Unable to fetch BYOK usage data."));
1392
- return;
1393
- }
1394
- const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1395
- const startOf24h = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
1396
- const startOf7d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
1397
- const startOf30d = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
1398
- const periods = [
1399
- { label: "Today", start: startOfToday, end: now },
1400
- { label: "Last 24 Hours", start: startOf24h, end: now },
1401
- { label: "Last 7 Days", start: startOf7d, end: now },
1402
- { label: "Last 30 Days", start: startOf30d, end: now },
1403
- { label: "Year to Date", start: yearStart, end: now }
1404
- ];
1405
- console.log(chalk.blue("BYOK Usage (local time):"));
1406
- console.log(chalk.dim("Estimated provider spend by model (you are billed directly by provider)."));
1407
- for (const period of periods) {
1408
- const summary = aggregateUsageRows(usageRows, period.start, period.end);
1409
- console.log();
1410
- console.log(chalk.bold(`${period.label}:`));
1411
- console.log(` ${period.start.toLocaleDateString()} \u2192 ${period.end.toLocaleDateString()}`);
1412
- console.log(` Tokens: ${formatTokens(summary.totalInputTokens + summary.totalOutputTokens)} (in ${formatTokens(summary.totalInputTokens)} / out ${formatTokens(summary.totalOutputTokens)})`);
1413
- console.log(` Estimated Spend: ${formatCost(summary.totalCost)} ${chalk.dim("(paid directly to provider)")}`);
1414
- console.log(chalk.dim(" By Model:"));
1415
- renderModelBreakdown(summary.byModel, { indent: " " });
1416
- }
1417
- } else {
1418
- const periodLabel = formatUsagePeriodLabel(data.periodSource);
1419
- const byModel = [...data.byModel ?? []].sort((a, b) => b.cost - a.cost);
1420
- const modelBilledTotal = sumModelCost(byModel);
1421
- const modelBaseTotal = data.totalBaseCost;
1422
- const modelFeeTotal = Math.max(0, modelBilledTotal - modelBaseTotal);
1423
- console.log(chalk.blue(`${periodLabel}:`));
1424
- console.log(` ${formatLocalDateTime(data.periodStart)} \u2192 ${formatLocalDateTime(data.periodEnd)}`);
1425
- console.log();
1426
- console.log(chalk.blue("Token Usage:"));
1427
- console.log(` Input: ${formatTokens(data.totalInputTokens)} tokens`);
1428
- console.log(` Output: ${formatTokens(data.totalOutputTokens)} tokens`);
1429
- console.log(` Total: ${formatTokens(data.totalInputTokens + data.totalOutputTokens)} tokens`);
1430
1200
  console.log();
1431
- console.log(chalk.blue("Cost:"));
1432
- if (data.tier === "CREDITS") {
1433
- console.log(` Base Model Cost: ${formatCost(modelBaseTotal)}`);
1434
- console.log(` Platform Fee: ${formatCost(modelFeeTotal)}`);
1435
- console.log(` Credits Deducted:${formatCost(modelBilledTotal)}`);
1436
- } else {
1437
- console.log(` Estimated Cost: ${formatCost(modelBaseTotal)}`);
1438
- }
1439
- if (byModel.length > 0) {
1440
- console.log();
1441
- console.log(chalk.blue("By Model:"));
1442
- renderModelBreakdown(
1443
- byModel.map((row) => ({
1444
- model: row.model,
1445
- inputTokens: row.inputTokens,
1446
- outputTokens: row.outputTokens,
1447
- cost: row.cost
1448
- }))
1449
- );
1450
- }
1451
- if (data.byOperation && data.byOperation.length > 0) {
1452
- console.log();
1453
- console.log(chalk.blue("By Operation:"));
1454
- for (const stats of [...data.byOperation].sort((a, b) => b.cost - a.cost)) {
1455
- console.log(` ${stats.operation}: ${formatTokens(stats.tokenCount)} tokens (${formatCost(stats.cost)})`);
1456
- }
1457
- }
1201
+ console.log(chalk.bold(`${period.label}:`));
1202
+ console.log(` ${period.start.toLocaleDateString()} \u2192 ${period.end.toLocaleDateString()}`);
1203
+ console.log(` Tokens: ${formatTokens(summary.totalInputTokens + summary.totalOutputTokens)} (in ${formatTokens(summary.totalInputTokens)} / out ${formatTokens(summary.totalOutputTokens)})`);
1204
+ console.log(` Estimated Spend: ${formatCost(summary.totalBaseCost)} ${chalk.dim("(paid directly to provider)")}`);
1205
+ console.log(chalk.dim(" By Model:"));
1206
+ renderModelBreakdown(summary.byModel, { indent: " " });
1458
1207
  }
1459
1208
  } catch {
1460
1209
  if (debug) {
1461
- debugLog("usage", "error", "Usage command threw exception");
1210
+ debugLog("usage", "error", "Usage command threw exception while reading local ledger");
1462
1211
  }
1463
1212
  spinner.stop();
1464
- console.log(chalk.yellow("Error fetching usage data."));
1213
+ console.log(chalk.yellow("Error reading local usage data."));
1465
1214
  }
1466
1215
  if (pauseForInput) {
1467
1216
  console.log();