@praeviso/code-env-switch 0.1.5 → 0.1.6

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.
@@ -16,6 +16,7 @@ exports.syncUsageFromStatuslineInput = syncUsageFromStatuslineInput;
16
16
  exports.logProfileUse = logProfileUse;
17
17
  exports.logSessionBinding = logSessionBinding;
18
18
  exports.readSessionBindingIndex = readSessionBindingIndex;
19
+ exports.clearUsageHistory = clearUsageHistory;
19
20
  exports.readUsageRecords = readUsageRecords;
20
21
  exports.syncUsageFromSessions = syncUsageFromSessions;
21
22
  /**
@@ -26,6 +27,7 @@ const path = require("path");
26
27
  const os = require("os");
27
28
  const utils_1 = require("../shell/utils");
28
29
  const type_1 = require("../profile/type");
30
+ const debug_1 = require("../statusline/debug");
29
31
  const pricing_1 = require("./pricing");
30
32
  function resolveProfileForRecord(config, type, record) {
31
33
  if (record.profileKey && config.profiles && config.profiles[record.profileKey]) {
@@ -393,17 +395,32 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
393
395
  let deltaCacheRead = cacheReadTokens - prevCacheRead;
394
396
  let deltaCacheWrite = cacheWriteTokens - prevCacheWrite;
395
397
  let deltaTotal = totalTokens - prevTotal;
396
- if (deltaTotal < 0 ||
397
- deltaInput < 0 ||
398
- deltaOutput < 0 ||
399
- deltaCacheRead < 0 ||
400
- deltaCacheWrite < 0) {
398
+ if (deltaTotal < 0) {
399
+ // Session reset: treat current totals as fresh usage.
401
400
  deltaInput = inputTokens;
402
401
  deltaOutput = outputTokens;
403
402
  deltaCacheRead = cacheReadTokens;
404
403
  deltaCacheWrite = cacheWriteTokens;
405
404
  deltaTotal = totalTokens;
406
405
  }
406
+ else {
407
+ // Clamp negatives caused by reclassification (e.g. cache splits).
408
+ if (deltaInput < 0)
409
+ deltaInput = 0;
410
+ if (deltaOutput < 0)
411
+ deltaOutput = 0;
412
+ if (deltaCacheRead < 0)
413
+ deltaCacheRead = 0;
414
+ if (deltaCacheWrite < 0)
415
+ deltaCacheWrite = 0;
416
+ const breakdownTotal = deltaInput + deltaOutput + deltaCacheRead + deltaCacheWrite;
417
+ if (deltaTotal === 0 && breakdownTotal === 0) {
418
+ deltaTotal = 0;
419
+ }
420
+ else if (breakdownTotal > deltaTotal) {
421
+ deltaTotal = breakdownTotal;
422
+ }
423
+ }
407
424
  if (deltaTotal > 0) {
408
425
  const record = {
409
426
  ts: new Date().toISOString(),
@@ -434,6 +451,7 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
434
451
  model: resolvedModel,
435
452
  };
436
453
  state.sessions = sessions;
454
+ updateUsageStateMetadata(state, usagePath);
437
455
  writeUsageState(statePath, state);
438
456
  }
439
457
  finally {
@@ -588,7 +606,15 @@ function readUsageState(statePath) {
588
606
  }
589
607
  const files = parsed.files && typeof parsed.files === "object" ? parsed.files : {};
590
608
  const sessions = parsed.sessions && typeof parsed.sessions === "object" ? parsed.sessions : {};
591
- return { version: 1, files, sessions };
609
+ const usageMtimeMs = Number(parsed.usageMtimeMs);
610
+ const usageSize = Number(parsed.usageSize);
611
+ return {
612
+ version: 1,
613
+ files,
614
+ sessions,
615
+ usageMtimeMs: Number.isFinite(usageMtimeMs) ? usageMtimeMs : undefined,
616
+ usageSize: Number.isFinite(usageSize) ? usageSize : undefined,
617
+ };
592
618
  }
593
619
  catch {
594
620
  return { version: 1, files: {}, sessions: {} };
@@ -599,7 +625,93 @@ function writeUsageState(statePath, state) {
599
625
  if (!fs.existsSync(dir)) {
600
626
  fs.mkdirSync(dir, { recursive: true });
601
627
  }
602
- fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
628
+ const payload = `${JSON.stringify(state, null, 2)}\n`;
629
+ const tmpPath = `${statePath}.tmp`;
630
+ try {
631
+ fs.writeFileSync(tmpPath, payload, "utf8");
632
+ fs.renameSync(tmpPath, statePath);
633
+ }
634
+ catch {
635
+ try {
636
+ if (fs.existsSync(tmpPath))
637
+ fs.unlinkSync(tmpPath);
638
+ }
639
+ catch {
640
+ // ignore cleanup failures
641
+ }
642
+ fs.writeFileSync(statePath, payload, "utf8");
643
+ }
644
+ }
645
+ function addSiblingBackupPaths(targets, filePath) {
646
+ if (!filePath)
647
+ return;
648
+ const dir = path.dirname(filePath);
649
+ let entries = [];
650
+ try {
651
+ entries = fs.readdirSync(dir, { withFileTypes: true });
652
+ }
653
+ catch {
654
+ return;
655
+ }
656
+ const base = path.basename(filePath);
657
+ for (const entry of entries) {
658
+ if (!entry.isFile())
659
+ continue;
660
+ if (entry.name === base)
661
+ continue;
662
+ if (entry.name.startsWith(`${base}.`)) {
663
+ targets.add(path.join(dir, entry.name));
664
+ }
665
+ }
666
+ }
667
+ function clearUsageHistory(config, configPath) {
668
+ const targets = new Set();
669
+ const usagePath = getUsagePath(config, configPath);
670
+ if (usagePath) {
671
+ targets.add(usagePath);
672
+ addSiblingBackupPaths(targets, usagePath);
673
+ }
674
+ const statePath = usagePath ? getUsageStatePath(usagePath, config) : null;
675
+ if (statePath) {
676
+ targets.add(statePath);
677
+ targets.add(`${statePath}.lock`);
678
+ addSiblingBackupPaths(targets, statePath);
679
+ }
680
+ const profileLogPath = getProfileLogPath(config, configPath);
681
+ if (profileLogPath) {
682
+ targets.add(profileLogPath);
683
+ addSiblingBackupPaths(targets, profileLogPath);
684
+ }
685
+ const debugPath = (0, debug_1.getStatuslineDebugPath)(configPath);
686
+ if (debugPath) {
687
+ targets.add(debugPath);
688
+ addSiblingBackupPaths(targets, debugPath);
689
+ }
690
+ const removed = [];
691
+ const missing = [];
692
+ const failed = [];
693
+ for (const target of targets) {
694
+ if (!target)
695
+ continue;
696
+ if (!fs.existsSync(target)) {
697
+ missing.push(target);
698
+ continue;
699
+ }
700
+ try {
701
+ const stat = fs.statSync(target);
702
+ if (!stat.isFile())
703
+ continue;
704
+ fs.unlinkSync(target);
705
+ removed.push(target);
706
+ }
707
+ catch (err) {
708
+ failed.push({
709
+ path: target,
710
+ error: err instanceof Error ? err.message : String(err),
711
+ });
712
+ }
713
+ }
714
+ return { removed, missing, failed };
603
715
  }
604
716
  function collectSessionFiles(root) {
605
717
  if (!root || !fs.existsSync(root))
@@ -708,10 +820,12 @@ function parseCodexSessionFile(filePath) {
708
820
  let maxTotal = 0;
709
821
  let maxInput = 0;
710
822
  let maxOutput = 0;
823
+ let maxCachedInput = 0;
711
824
  let hasTotal = false;
712
825
  let sumLast = 0;
713
826
  let sumLastInput = 0;
714
827
  let sumLastOutput = 0;
828
+ let sumLastCachedInput = 0;
715
829
  const tsRange = { start: null, end: null };
716
830
  let cwd = null;
717
831
  let sessionId = null;
@@ -759,12 +873,16 @@ function parseCodexSessionFile(filePath) {
759
873
  maxTotal = totalTokens;
760
874
  const totalInput = Number(totalUsage.input_tokens);
761
875
  const totalOutput = Number(totalUsage.output_tokens);
876
+ const totalCached = Number(totalUsage.cached_input_tokens);
762
877
  if (Number.isFinite(totalInput) && totalInput > maxInput) {
763
878
  maxInput = totalInput;
764
879
  }
765
880
  if (Number.isFinite(totalOutput) && totalOutput > maxOutput) {
766
881
  maxOutput = totalOutput;
767
882
  }
883
+ if (Number.isFinite(totalCached) && totalCached > maxCachedInput) {
884
+ maxCachedInput = totalCached;
885
+ }
768
886
  }
769
887
  else {
770
888
  const lastTokens = Number(lastUsage.total_tokens);
@@ -772,10 +890,13 @@ function parseCodexSessionFile(filePath) {
772
890
  sumLast += lastTokens;
773
891
  const lastInput = Number(lastUsage.input_tokens);
774
892
  const lastOutput = Number(lastUsage.output_tokens);
893
+ const lastCached = Number(lastUsage.cached_input_tokens);
775
894
  if (Number.isFinite(lastInput))
776
895
  sumLastInput += lastInput;
777
896
  if (Number.isFinite(lastOutput))
778
897
  sumLastOutput += lastOutput;
898
+ if (Number.isFinite(lastCached))
899
+ sumLastCachedInput += lastCached;
779
900
  }
780
901
  }
781
902
  catch {
@@ -786,13 +907,18 @@ function parseCodexSessionFile(filePath) {
786
907
  maxTotal = sumLast;
787
908
  maxInput = sumLastInput;
788
909
  maxOutput = sumLastOutput;
910
+ maxCachedInput = sumLastCachedInput;
789
911
  }
912
+ const cacheReadTokens = Math.max(0, maxCachedInput);
913
+ const inputTokens = cacheReadTokens > 0 ? Math.max(0, maxInput - cacheReadTokens) : maxInput;
914
+ const computedTotal = inputTokens + maxOutput + cacheReadTokens;
915
+ const totalTokens = Math.max(maxTotal, computedTotal);
790
916
  return {
791
- inputTokens: maxInput,
917
+ inputTokens,
792
918
  outputTokens: maxOutput,
793
- cacheReadTokens: 0,
919
+ cacheReadTokens,
794
920
  cacheWriteTokens: 0,
795
- totalTokens: maxTotal,
921
+ totalTokens,
796
922
  startTs: tsRange.start,
797
923
  endTs: tsRange.end,
798
924
  cwd,
@@ -955,6 +1081,88 @@ function releaseLock(lockPath, fd) {
955
1081
  // ignore
956
1082
  }
957
1083
  }
1084
+ function readUsageFileStat(usagePath) {
1085
+ if (!usagePath || !fs.existsSync(usagePath))
1086
+ return null;
1087
+ try {
1088
+ return fs.statSync(usagePath);
1089
+ }
1090
+ catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ function buildUsageRecordKey(record) {
1095
+ var _a, _b, _c, _d;
1096
+ return JSON.stringify([
1097
+ record.ts,
1098
+ record.type,
1099
+ (_a = record.profileKey) !== null && _a !== void 0 ? _a : null,
1100
+ (_b = record.profileName) !== null && _b !== void 0 ? _b : null,
1101
+ (_c = record.model) !== null && _c !== void 0 ? _c : null,
1102
+ (_d = record.sessionId) !== null && _d !== void 0 ? _d : null,
1103
+ toUsageNumber(record.inputTokens),
1104
+ toUsageNumber(record.outputTokens),
1105
+ toUsageNumber(record.cacheReadTokens),
1106
+ toUsageNumber(record.cacheWriteTokens),
1107
+ toUsageNumber(record.totalTokens),
1108
+ ]);
1109
+ }
1110
+ function buildUsageSessionsFromRecords(records) {
1111
+ const sessions = {};
1112
+ const seen = new Set();
1113
+ for (const record of records) {
1114
+ if (!record.sessionId)
1115
+ continue;
1116
+ const normalizedType = normalizeUsageType(record.type);
1117
+ if (!normalizedType)
1118
+ continue;
1119
+ const recordKey = buildUsageRecordKey(record);
1120
+ if (seen.has(recordKey))
1121
+ continue;
1122
+ seen.add(recordKey);
1123
+ const sessionKey = buildSessionKey(normalizedType, record.sessionId);
1124
+ let entry = sessions[sessionKey];
1125
+ if (!entry) {
1126
+ entry = {
1127
+ type: normalizedType,
1128
+ inputTokens: 0,
1129
+ outputTokens: 0,
1130
+ cacheReadTokens: 0,
1131
+ cacheWriteTokens: 0,
1132
+ totalTokens: 0,
1133
+ startTs: null,
1134
+ endTs: null,
1135
+ cwd: null,
1136
+ model: record.model || null,
1137
+ };
1138
+ sessions[sessionKey] = entry;
1139
+ }
1140
+ entry.inputTokens += toUsageNumber(record.inputTokens);
1141
+ entry.outputTokens += toUsageNumber(record.outputTokens);
1142
+ entry.cacheReadTokens += toUsageNumber(record.cacheReadTokens);
1143
+ entry.cacheWriteTokens += toUsageNumber(record.cacheWriteTokens);
1144
+ entry.totalTokens += toUsageNumber(record.totalTokens);
1145
+ if (!entry.model && record.model)
1146
+ entry.model = record.model;
1147
+ if (record.ts) {
1148
+ const range = { start: entry.startTs, end: entry.endTs };
1149
+ updateMinMaxTs(range, record.ts);
1150
+ entry.startTs = range.start;
1151
+ entry.endTs = range.end;
1152
+ }
1153
+ }
1154
+ return sessions;
1155
+ }
1156
+ function updateUsageStateMetadata(state, usagePath) {
1157
+ const stat = readUsageFileStat(usagePath);
1158
+ if (!stat || !stat.isFile()) {
1159
+ state.usageMtimeMs = undefined;
1160
+ state.usageSize = undefined;
1161
+ return;
1162
+ }
1163
+ state.usageMtimeMs = stat.mtimeMs;
1164
+ state.usageSize = stat.size;
1165
+ }
958
1166
  function appendUsageRecord(usagePath, record) {
959
1167
  const dir = path.dirname(usagePath);
960
1168
  if (!fs.existsSync(dir)) {
@@ -969,6 +1177,7 @@ function readUsageRecords(usagePath) {
969
1177
  const raw = fs.readFileSync(usagePath, "utf8");
970
1178
  const lines = raw.split(/\r?\n/);
971
1179
  const records = [];
1180
+ const seen = new Set();
972
1181
  for (const line of lines) {
973
1182
  const trimmed = line.trim();
974
1183
  if (!trimmed)
@@ -977,19 +1186,29 @@ function readUsageRecords(usagePath) {
977
1186
  const parsed = JSON.parse(trimmed);
978
1187
  if (!parsed || typeof parsed !== "object")
979
1188
  continue;
980
- const input = Number((_a = parsed.inputTokens) !== null && _a !== void 0 ? _a : 0);
981
- const output = Number((_b = parsed.outputTokens) !== null && _b !== void 0 ? _b : 0);
982
- const cacheRead = Number((_e = (_d = (_c = parsed.cacheReadTokens) !== null && _c !== void 0 ? _c : parsed.cache_read_input_tokens) !== null && _d !== void 0 ? _d : parsed.cacheReadInputTokens) !== null && _e !== void 0 ? _e : 0);
983
- const cacheWrite = Number((_j = (_h = (_g = (_f = parsed.cacheWriteTokens) !== null && _f !== void 0 ? _f : parsed.cache_creation_input_tokens) !== null && _g !== void 0 ? _g : parsed.cache_write_input_tokens) !== null && _h !== void 0 ? _h : parsed.cacheWriteInputTokens) !== null && _j !== void 0 ? _j : 0);
1189
+ const type = (0, type_1.normalizeType)(parsed.type) || String((_a = parsed.type) !== null && _a !== void 0 ? _a : "unknown");
1190
+ let input = Number((_b = parsed.inputTokens) !== null && _b !== void 0 ? _b : 0);
1191
+ const output = Number((_c = parsed.outputTokens) !== null && _c !== void 0 ? _c : 0);
1192
+ const cacheRead = Number((_f = (_e = (_d = parsed.cacheReadTokens) !== null && _d !== void 0 ? _d : parsed.cache_read_input_tokens) !== null && _e !== void 0 ? _e : parsed.cacheReadInputTokens) !== null && _f !== void 0 ? _f : 0);
1193
+ const cacheWrite = Number((_k = (_j = (_h = (_g = parsed.cacheWriteTokens) !== null && _g !== void 0 ? _g : parsed.cache_creation_input_tokens) !== null && _h !== void 0 ? _h : parsed.cache_write_input_tokens) !== null && _j !== void 0 ? _j : parsed.cacheWriteInputTokens) !== null && _k !== void 0 ? _k : 0);
1194
+ if (type === "codex" &&
1195
+ Number.isFinite(cacheRead) &&
1196
+ cacheRead > 0 &&
1197
+ Number.isFinite(input) &&
1198
+ Number.isFinite(output)) {
1199
+ const rawTotal = Number(parsed.totalTokens);
1200
+ if (Number.isFinite(rawTotal) && rawTotal <= input + output) {
1201
+ input = Math.max(0, input - cacheRead);
1202
+ }
1203
+ }
984
1204
  const computedTotal = (Number.isFinite(input) ? input : 0) +
985
1205
  (Number.isFinite(output) ? output : 0) +
986
1206
  (Number.isFinite(cacheRead) ? cacheRead : 0) +
987
1207
  (Number.isFinite(cacheWrite) ? cacheWrite : 0);
988
- const total = Number((_k = parsed.totalTokens) !== null && _k !== void 0 ? _k : computedTotal);
1208
+ const total = Number((_l = parsed.totalTokens) !== null && _l !== void 0 ? _l : computedTotal);
989
1209
  const finalTotal = Number.isFinite(total)
990
1210
  ? Math.max(total, computedTotal)
991
1211
  : computedTotal;
992
- const type = (0, type_1.normalizeType)(parsed.type) || String((_l = parsed.type) !== null && _l !== void 0 ? _l : "unknown");
993
1212
  const model = normalizeModelValue(parsed.model) ||
994
1213
  normalizeModelValue(parsed.model_name) ||
995
1214
  normalizeModelValue(parsed.modelName) ||
@@ -1001,7 +1220,7 @@ function readUsageRecords(usagePath) {
1001
1220
  parsed.sessionID ||
1002
1221
  parsed.session ||
1003
1222
  null;
1004
- records.push({
1223
+ const record = {
1005
1224
  ts: String((_m = parsed.ts) !== null && _m !== void 0 ? _m : ""),
1006
1225
  type,
1007
1226
  profileKey: parsed.profileKey ? String(parsed.profileKey) : null,
@@ -1013,7 +1232,12 @@ function readUsageRecords(usagePath) {
1013
1232
  cacheReadTokens: Number.isFinite(cacheRead) ? cacheRead : 0,
1014
1233
  cacheWriteTokens: Number.isFinite(cacheWrite) ? cacheWrite : 0,
1015
1234
  totalTokens: Number.isFinite(finalTotal) ? finalTotal : 0,
1016
- });
1235
+ };
1236
+ const key = buildUsageRecordKey(record);
1237
+ if (seen.has(key))
1238
+ continue;
1239
+ seen.add(key);
1240
+ records.push(record);
1017
1241
  }
1018
1242
  catch {
1019
1243
  // ignore invalid lines
@@ -1022,6 +1246,7 @@ function readUsageRecords(usagePath) {
1022
1246
  return records;
1023
1247
  }
1024
1248
  function syncUsageFromSessions(config, configPath, usagePath) {
1249
+ var _a, _b;
1025
1250
  const statePath = getUsageStatePath(usagePath, config);
1026
1251
  const lockPath = `${statePath}.lock`;
1027
1252
  const lockFd = acquireLock(lockPath);
@@ -1032,7 +1257,23 @@ function syncUsageFromSessions(config, configPath, usagePath) {
1032
1257
  const logEntries = readProfileLogEntries([profileLogPath]);
1033
1258
  const state = readUsageState(statePath);
1034
1259
  const files = state.files || {};
1035
- const sessions = state.sessions || {};
1260
+ let sessions = state.sessions || {};
1261
+ const usageStat = readUsageFileStat(usagePath);
1262
+ const hasUsageData = !!usageStat && usageStat.isFile() && usageStat.size > 0;
1263
+ const sessionsEmpty = Object.keys(sessions).length === 0;
1264
+ const hasUsageMeta = Number.isFinite((_a = state.usageMtimeMs) !== null && _a !== void 0 ? _a : Number.NaN) &&
1265
+ Number.isFinite((_b = state.usageSize) !== null && _b !== void 0 ? _b : Number.NaN);
1266
+ const usageOutOfSync = hasUsageData &&
1267
+ (!hasUsageMeta ||
1268
+ state.usageMtimeMs !== usageStat.mtimeMs ||
1269
+ state.usageSize !== usageStat.size);
1270
+ if (hasUsageData && (sessionsEmpty || usageOutOfSync)) {
1271
+ const records = readUsageRecords(usagePath);
1272
+ if (records.length > 0) {
1273
+ sessions = buildUsageSessionsFromRecords(records);
1274
+ }
1275
+ }
1276
+ state.sessions = sessions;
1036
1277
  const codexFiles = collectSessionFiles(getCodexSessionsPath(config));
1037
1278
  const claudeFiles = collectSessionFiles(getClaudeSessionsPath(config));
1038
1279
  const processFile = (filePath, type) => {
@@ -1179,6 +1420,7 @@ function syncUsageFromSessions(config, configPath, usagePath) {
1179
1420
  processFile(filePath, "claude");
1180
1421
  state.files = files;
1181
1422
  state.sessions = sessions;
1423
+ updateUsageStateMetadata(state, usagePath);
1182
1424
  writeUsageState(statePath, state);
1183
1425
  }
1184
1426
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praeviso/code-env-switch",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Switch between Claude Code and Codex environment variables from a single CLI",
5
5
  "bin": {
6
6
  "codenv": "bin/index.js"
package/src/cli/args.ts CHANGED
@@ -5,6 +5,7 @@ import type {
5
5
  ParsedArgs,
6
6
  InitArgs,
7
7
  AddArgs,
8
+ UsageResetArgs,
8
9
  ProfileType,
9
10
  StatuslineArgs,
10
11
  } from "../types";
@@ -156,6 +157,19 @@ export function parseAddArgs(args: string[]): AddArgs {
156
157
  return result;
157
158
  }
158
159
 
160
+ export function parseUsageResetArgs(args: string[]): UsageResetArgs {
161
+ const result: UsageResetArgs = { yes: false };
162
+ for (let i = 0; i < args.length; i++) {
163
+ const arg = args[i];
164
+ if (arg === "-y" || arg === "--yes") {
165
+ result.yes = true;
166
+ continue;
167
+ }
168
+ throw new Error(`Unknown usage-reset argument: ${arg}`);
169
+ }
170
+ return result;
171
+ }
172
+
159
173
  function parseNumberFlag(value: string | null | undefined, flag: string): number {
160
174
  if (value === null || value === undefined || value === "") {
161
175
  throw new Error(`Missing value for ${flag}.`);
package/src/cli/help.ts CHANGED
@@ -27,6 +27,7 @@ Usage:
27
27
  codenv launch <codex|claude> [--] [args...]
28
28
  codenv init
29
29
  codenv statusline [options]
30
+ codenv usage-reset [--yes]
30
31
 
31
32
  Options:
32
33
  -c, --config <path> Path to config JSON
@@ -57,6 +58,9 @@ Statusline options:
57
58
  --usage-output <n> Set output token usage
58
59
  --sync-usage Sync usage from sessions before reading
59
60
 
61
+ Usage reset options:
62
+ -y, --yes Skip confirmation prompt
63
+
60
64
  Examples:
61
65
  codenv init
62
66
  codenv use codex primary
@@ -67,6 +71,7 @@ Examples:
67
71
  codenv remove --all
68
72
  codenv launch codex -- --help
69
73
  codenv statusline --format json
74
+ codenv usage-reset --yes
70
75
  CODE_ENV_CONFIG=~/.config/code-env/config.json codenv use claude default
71
76
  codenv add --type codex primary OPENAI_BASE_URL=https://api.example.com/v1 OPENAI_API_KEY=YOUR_API_KEY
72
77
  codenv add
package/src/cli/index.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  /**
2
2
  * CLI module exports
3
3
  */
4
- export { parseArgs, parseInitArgs, parseAddArgs, parseStatuslineArgs } from "./args";
4
+ export {
5
+ parseArgs,
6
+ parseInitArgs,
7
+ parseAddArgs,
8
+ parseUsageResetArgs,
9
+ parseStatuslineArgs,
10
+ } from "./args";
5
11
  export { printHelp } from "./help";
@@ -8,3 +8,4 @@ export { printShow } from "./show";
8
8
  export { printUnset } from "./unset";
9
9
  export { runLaunch } from "./launch";
10
10
  export { printStatusline } from "./statusline";
11
+ export { runUsageReset } from "./usage";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Usage history reset command
3
+ */
4
+ import type { Config, UsageResetArgs } from "../types";
5
+ import { clearUsageHistory } from "../usage";
6
+ import { askConfirm, createReadline } from "../ui";
7
+
8
+ export async function runUsageReset(
9
+ config: Config,
10
+ configPath: string | null,
11
+ args: UsageResetArgs
12
+ ): Promise<void> {
13
+ if (!args.yes) {
14
+ const rl = createReadline();
15
+ try {
16
+ const confirmed = await askConfirm(
17
+ rl,
18
+ "Clear all usage history files? This cannot be undone. (y/N): "
19
+ );
20
+ if (!confirmed) return;
21
+ } finally {
22
+ rl.close();
23
+ }
24
+ }
25
+
26
+ const result = clearUsageHistory(config, configPath);
27
+ const removed = result.removed.sort();
28
+ const missing = result.missing.sort();
29
+ const failed = result.failed.sort((a, b) => a.path.localeCompare(b.path));
30
+
31
+ if (removed.length === 0 && failed.length === 0) {
32
+ console.log("No usage files found.");
33
+ return;
34
+ }
35
+
36
+ if (removed.length > 0) {
37
+ console.log(`Removed ${removed.length} file(s):`);
38
+ for (const filePath of removed) {
39
+ console.log(`- ${filePath}`);
40
+ }
41
+ }
42
+
43
+ if (missing.length > 0) {
44
+ console.log(`Skipped ${missing.length} missing file(s).`);
45
+ }
46
+
47
+ if (failed.length > 0) {
48
+ for (const failure of failed) {
49
+ console.error(`Failed to remove ${failure.path}: ${failure.error}`);
50
+ }
51
+ process.exitCode = 1;
52
+ }
53
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  parseArgs,
11
11
  parseInitArgs,
12
12
  parseAddArgs,
13
+ parseUsageResetArgs,
13
14
  parseStatuslineArgs,
14
15
  printHelp,
15
16
  } from "./cli";
@@ -33,6 +34,7 @@ import {
33
34
  printUnset,
34
35
  runLaunch,
35
36
  printStatusline,
37
+ runUsageReset,
36
38
  } from "./commands";
37
39
  import { logProfileUse } from "./usage";
38
40
  import { createReadline, askConfirm, runInteractiveAdd, runInteractiveUse } from "./ui";
@@ -152,6 +154,15 @@ async function main() {
152
154
  return;
153
155
  }
154
156
 
157
+ if (cmd === "usage-reset" || cmd === "reset-usage") {
158
+ const resetArgs = parseUsageResetArgs(args.slice(1));
159
+ const configPath =
160
+ process.env.CODE_ENV_CONFIG_PATH || findConfigPath(parsed.configPath);
161
+ const config = readConfigIfExists(configPath);
162
+ await runUsageReset(config, configPath, resetArgs);
163
+ return;
164
+ }
165
+
155
166
  const configPath = findConfigPath(parsed.configPath);
156
167
  const config = readConfig(configPath!);
157
168
 
@@ -16,7 +16,7 @@ function resolveDefaultConfigDir(configPath: string | null): string {
16
16
  return path.join(os.homedir(), ".config", "code-env");
17
17
  }
18
18
 
19
- function getStatuslineDebugPath(configPath: string | null): string {
19
+ export function getStatuslineDebugPath(configPath: string | null): string {
20
20
  const envPath = resolvePath(process.env.CODE_ENV_STATUSLINE_DEBUG_PATH);
21
21
  if (envPath) return envPath;
22
22
  return path.join(resolveDefaultConfigDir(configPath), "statusline-debug.jsonl");