sidekick-agent-hub 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/sidekick-cli.mjs +1141 -131
- package/package.json +1 -1
package/dist/sidekick-cli.mjs
CHANGED
|
@@ -4865,6 +4865,31 @@ var require_detect = __commonJS({
|
|
|
4865
4865
|
function getCodexHomes() {
|
|
4866
4866
|
return (0, codexProfiles_1.getCodexMonitoringHomes)();
|
|
4867
4867
|
}
|
|
4868
|
+
function hasCodexStateDb(codexHome) {
|
|
4869
|
+
try {
|
|
4870
|
+
return fs9.readdirSync(codexHome).some((entry) => /^state(?:_\d+)?\.sqlite$/.test(entry));
|
|
4871
|
+
} catch {
|
|
4872
|
+
return false;
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
function getCodexStateDbMtime(codexHome) {
|
|
4876
|
+
try {
|
|
4877
|
+
let latest = 0;
|
|
4878
|
+
for (const entry of fs9.readdirSync(codexHome)) {
|
|
4879
|
+
if (!/^state(?:_\d+)?\.sqlite$/.test(entry))
|
|
4880
|
+
continue;
|
|
4881
|
+
try {
|
|
4882
|
+
const mtime = fs9.statSync(path8.join(codexHome, entry)).mtime.getTime();
|
|
4883
|
+
if (mtime > latest)
|
|
4884
|
+
latest = mtime;
|
|
4885
|
+
} catch {
|
|
4886
|
+
}
|
|
4887
|
+
}
|
|
4888
|
+
return latest;
|
|
4889
|
+
} catch {
|
|
4890
|
+
return 0;
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4868
4893
|
function getMostRecentMtime(dir) {
|
|
4869
4894
|
try {
|
|
4870
4895
|
if (!fs9.existsSync(dir))
|
|
@@ -4903,13 +4928,9 @@ var require_detect = __commonJS({
|
|
|
4903
4928
|
function getCodexActivityMtime() {
|
|
4904
4929
|
let latest = 0;
|
|
4905
4930
|
for (const codexHome of getCodexHomes()) {
|
|
4906
|
-
const
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
if (dbMtime > latest)
|
|
4910
|
-
latest = dbMtime;
|
|
4911
|
-
} catch {
|
|
4912
|
-
}
|
|
4931
|
+
const dbMtime = getCodexStateDbMtime(codexHome);
|
|
4932
|
+
if (dbMtime > latest)
|
|
4933
|
+
latest = dbMtime;
|
|
4913
4934
|
const sessionsMtime = getMostRecentMtime(path8.join(codexHome, "sessions"));
|
|
4914
4935
|
if (sessionsMtime > latest)
|
|
4915
4936
|
latest = sessionsMtime;
|
|
@@ -4931,7 +4952,7 @@ var require_detect = __commonJS({
|
|
|
4931
4952
|
const { claudeBase, openCodeDbPath, openCodeStorageDir, codexHomes } = getProviderPaths();
|
|
4932
4953
|
const hasClaude = fs9.existsSync(claudeBase);
|
|
4933
4954
|
const hasOpenCode = fs9.existsSync(openCodeStorageDir) || fs9.existsSync(openCodeDbPath);
|
|
4934
|
-
const hasCodex = codexHomes.some((codexHome) => fs9.existsSync(path8.join(codexHome, "sessions")) ||
|
|
4955
|
+
const hasCodex = codexHomes.some((codexHome) => fs9.existsSync(path8.join(codexHome, "sessions")) || hasCodexStateDb(codexHome));
|
|
4935
4956
|
const available = [];
|
|
4936
4957
|
if (hasClaude)
|
|
4937
4958
|
available.push({ id: "claude-code", mtime: getMostRecentMtime(claudeBase) });
|
|
@@ -4948,7 +4969,7 @@ var require_detect = __commonJS({
|
|
|
4948
4969
|
const { claudeBase, openCodeDbPath, openCodeStorageDir, codexHomes } = getProviderPaths();
|
|
4949
4970
|
const hasClaude = fs9.existsSync(claudeBase);
|
|
4950
4971
|
const hasOpenCode = fs9.existsSync(openCodeStorageDir) || fs9.existsSync(openCodeDbPath);
|
|
4951
|
-
const hasCodex = codexHomes.some((codexHome) => fs9.existsSync(path8.join(codexHome, "sessions")) ||
|
|
4972
|
+
const hasCodex = codexHomes.some((codexHome) => fs9.existsSync(path8.join(codexHome, "sessions")) || hasCodexStateDb(codexHome));
|
|
4952
4973
|
const available = [];
|
|
4953
4974
|
if (hasClaude) {
|
|
4954
4975
|
available.push({ id: "claude-code", mtime: getMostRecentMtime(claudeBase) });
|
|
@@ -8984,7 +9005,7 @@ var require_codexDatabase = __commonJS({
|
|
|
8984
9005
|
dbPath;
|
|
8985
9006
|
sqlite3Available = null;
|
|
8986
9007
|
constructor(codexHome) {
|
|
8987
|
-
this.dbPath = path8.join(codexHome, "state.sqlite");
|
|
9008
|
+
this.dbPath = findLatestStateDatabase(codexHome) ?? path8.join(codexHome, "state.sqlite");
|
|
8988
9009
|
}
|
|
8989
9010
|
isAvailable() {
|
|
8990
9011
|
try {
|
|
@@ -9075,6 +9096,28 @@ var require_codexDatabase = __commonJS({
|
|
|
9075
9096
|
}
|
|
9076
9097
|
};
|
|
9077
9098
|
exports.CodexDatabase = CodexDatabase;
|
|
9099
|
+
function findLatestStateDatabase(codexHome) {
|
|
9100
|
+
try {
|
|
9101
|
+
const entries = fs9.readdirSync(codexHome, { withFileTypes: true });
|
|
9102
|
+
const candidates = [];
|
|
9103
|
+
for (const entry of entries) {
|
|
9104
|
+
if (!entry.isFile() || !/^state(?:_\d+)?\.sqlite$/.test(entry.name))
|
|
9105
|
+
continue;
|
|
9106
|
+
const dbPath = path8.join(codexHome, entry.name);
|
|
9107
|
+
try {
|
|
9108
|
+
const stat = fs9.statSync(dbPath);
|
|
9109
|
+
if (stat.size > 0) {
|
|
9110
|
+
candidates.push({ path: dbPath, mtime: stat.mtime.getTime() });
|
|
9111
|
+
}
|
|
9112
|
+
} catch {
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
candidates.sort((a, b) => b.mtime - a.mtime);
|
|
9116
|
+
return candidates[0]?.path ?? null;
|
|
9117
|
+
} catch {
|
|
9118
|
+
return null;
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
9078
9121
|
function normalizePath(input) {
|
|
9079
9122
|
try {
|
|
9080
9123
|
return fs9.realpathSync(input);
|
|
@@ -11668,15 +11711,15 @@ var require_jsonlWatcher = __commonJS({
|
|
|
11668
11711
|
if (evtType === "token_count") {
|
|
11669
11712
|
const info = payload?.info;
|
|
11670
11713
|
const usage = info?.last_token_usage || info?.total_token_usage;
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11714
|
+
const rl = payload?.rate_limits;
|
|
11715
|
+
const rateLimits = rl ? extractRateLimits(rl) : void 0;
|
|
11716
|
+
if (usage || rateLimits) {
|
|
11674
11717
|
events.push({
|
|
11675
11718
|
providerId: "codex",
|
|
11676
11719
|
type: "system",
|
|
11677
11720
|
timestamp: ts,
|
|
11678
|
-
summary: `Tokens: ${usage.input_tokens ?? 0} in / ${usage.output_tokens ?? 0} out
|
|
11679
|
-
tokens: { input: usage.input_tokens || 0, output: usage.output_tokens || 0 },
|
|
11721
|
+
summary: usage ? `Tokens: ${usage.input_tokens ?? 0} in / ${usage.output_tokens ?? 0} out` : "Rate limits updated",
|
|
11722
|
+
tokens: usage ? { input: usage.input_tokens || 0, output: usage.output_tokens || 0 } : void 0,
|
|
11680
11723
|
rateLimits,
|
|
11681
11724
|
raw
|
|
11682
11725
|
});
|
|
@@ -12056,7 +12099,7 @@ var require_factory = __commonJS({
|
|
|
12056
12099
|
};
|
|
12057
12100
|
}();
|
|
12058
12101
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12059
|
-
exports.createWatcher =
|
|
12102
|
+
exports.createWatcher = createWatcher4;
|
|
12060
12103
|
var os6 = __importStar(__require("os"));
|
|
12061
12104
|
var path8 = __importStar(__require("path"));
|
|
12062
12105
|
var jsonlWatcher_1 = require_jsonlWatcher();
|
|
@@ -12067,7 +12110,7 @@ var require_factory = __commonJS({
|
|
|
12067
12110
|
return path8.join(xdg, "opencode");
|
|
12068
12111
|
return path8.join(os6.homedir(), ".local", "share", "opencode");
|
|
12069
12112
|
}
|
|
12070
|
-
function
|
|
12113
|
+
function createWatcher4(options) {
|
|
12071
12114
|
const { provider, workspacePath, sessionId, callbacks } = options;
|
|
12072
12115
|
const sessions = provider.findAllSessions(workspacePath);
|
|
12073
12116
|
if (sessions.length === 0) {
|
|
@@ -18839,8 +18882,8 @@ var require_quotaSnapshots = __commonJS({
|
|
|
18839
18882
|
};
|
|
18840
18883
|
}();
|
|
18841
18884
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18842
|
-
exports.writeQuotaSnapshot =
|
|
18843
|
-
exports.readQuotaSnapshot =
|
|
18885
|
+
exports.writeQuotaSnapshot = writeQuotaSnapshot;
|
|
18886
|
+
exports.readQuotaSnapshot = readQuotaSnapshot;
|
|
18844
18887
|
var crypto = __importStar(__require("crypto"));
|
|
18845
18888
|
var fs9 = __importStar(__require("fs"));
|
|
18846
18889
|
var path8 = __importStar(__require("path"));
|
|
@@ -18883,7 +18926,7 @@ var require_quotaSnapshots = __commonJS({
|
|
|
18883
18926
|
ensureConfigDir();
|
|
18884
18927
|
atomicWriteJson(getQuotaSnapshotPath(), store);
|
|
18885
18928
|
}
|
|
18886
|
-
function
|
|
18929
|
+
function writeQuotaSnapshot(providerId, accountId, quota) {
|
|
18887
18930
|
const store = readStore();
|
|
18888
18931
|
const snapshot = {
|
|
18889
18932
|
...quota,
|
|
@@ -18905,7 +18948,7 @@ var require_quotaSnapshots = __commonJS({
|
|
|
18905
18948
|
}
|
|
18906
18949
|
writeStore(store);
|
|
18907
18950
|
}
|
|
18908
|
-
function
|
|
18951
|
+
function readQuotaSnapshot(providerId, accountId) {
|
|
18909
18952
|
const store = readStore();
|
|
18910
18953
|
const snapshot = store.snapshots.find((item) => item.providerId === providerId && item.accountId === accountId);
|
|
18911
18954
|
if (!snapshot)
|
|
@@ -18920,28 +18963,767 @@ var require_quotaSnapshots = __commonJS({
|
|
|
18920
18963
|
}
|
|
18921
18964
|
});
|
|
18922
18965
|
|
|
18966
|
+
// ../sidekick-shared/dist/quotaHistory.js
|
|
18967
|
+
var require_quotaHistory = __commonJS({
|
|
18968
|
+
"../sidekick-shared/dist/quotaHistory.js"(exports) {
|
|
18969
|
+
"use strict";
|
|
18970
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
18971
|
+
if (k2 === void 0) k2 = k;
|
|
18972
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18973
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18974
|
+
desc = { enumerable: true, get: function() {
|
|
18975
|
+
return m[k];
|
|
18976
|
+
} };
|
|
18977
|
+
}
|
|
18978
|
+
Object.defineProperty(o, k2, desc);
|
|
18979
|
+
} : function(o, m, k, k2) {
|
|
18980
|
+
if (k2 === void 0) k2 = k;
|
|
18981
|
+
o[k2] = m[k];
|
|
18982
|
+
});
|
|
18983
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
18984
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18985
|
+
} : function(o, v) {
|
|
18986
|
+
o["default"] = v;
|
|
18987
|
+
});
|
|
18988
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
18989
|
+
var ownKeys = function(o) {
|
|
18990
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
18991
|
+
var ar = [];
|
|
18992
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
18993
|
+
return ar;
|
|
18994
|
+
};
|
|
18995
|
+
return ownKeys(o);
|
|
18996
|
+
};
|
|
18997
|
+
return function(mod) {
|
|
18998
|
+
if (mod && mod.__esModule) return mod;
|
|
18999
|
+
var result = {};
|
|
19000
|
+
if (mod != null) {
|
|
19001
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
19002
|
+
}
|
|
19003
|
+
__setModuleDefault(result, mod);
|
|
19004
|
+
return result;
|
|
19005
|
+
};
|
|
19006
|
+
}();
|
|
19007
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19008
|
+
exports.getWorkspaceIdFromPath = getWorkspaceIdFromPath2;
|
|
19009
|
+
exports.appendQuotaHistorySample = appendQuotaHistorySample;
|
|
19010
|
+
exports.readQuotaHistoryRange = readQuotaHistoryRange;
|
|
19011
|
+
exports.readQuotaHistoryDailyBuckets = readQuotaHistoryDailyBuckets2;
|
|
19012
|
+
exports.pruneQuotaHistory = pruneQuotaHistory;
|
|
19013
|
+
exports._resetQuotaHistoryInMemoryStateForTests = _resetQuotaHistoryInMemoryStateForTests;
|
|
19014
|
+
var crypto = __importStar(__require("crypto"));
|
|
19015
|
+
var fs9 = __importStar(__require("fs"));
|
|
19016
|
+
var path8 = __importStar(__require("path"));
|
|
19017
|
+
var paths_1 = require_paths();
|
|
19018
|
+
var quotaSnapshots_1 = require_quotaSnapshots();
|
|
19019
|
+
var DEFAULT_MIN_INTERVAL_MS = 6e4;
|
|
19020
|
+
var DEFAULT_RETENTION_DAYS = 91;
|
|
19021
|
+
var PRUNE_FILESIZE_THRESHOLD = 16 * 1024;
|
|
19022
|
+
var MS_PER_DAY2 = 864e5;
|
|
19023
|
+
var appendChains = /* @__PURE__ */ new Map();
|
|
19024
|
+
var lastWriteCache = /* @__PURE__ */ new Map();
|
|
19025
|
+
function getWorkspaceIdFromPath2(workspacePath) {
|
|
19026
|
+
let resolved;
|
|
19027
|
+
try {
|
|
19028
|
+
resolved = fs9.realpathSync(workspacePath);
|
|
19029
|
+
} catch {
|
|
19030
|
+
resolved = path8.resolve(workspacePath);
|
|
19031
|
+
}
|
|
19032
|
+
return crypto.createHash("sha256").update(resolved).digest("hex").slice(0, 16);
|
|
19033
|
+
}
|
|
19034
|
+
function getHistoryFilePath(workspaceId, provider) {
|
|
19035
|
+
return path8.join((0, paths_1.getConfigDir)(), "quota-history", workspaceId, `${provider}.jsonl`);
|
|
19036
|
+
}
|
|
19037
|
+
function ensureHistoryDir(workspaceId) {
|
|
19038
|
+
fs9.mkdirSync(path8.join((0, paths_1.getConfigDir)(), "quota-history", workspaceId), { recursive: true, mode: 448 });
|
|
19039
|
+
}
|
|
19040
|
+
function parseSampleLine(line) {
|
|
19041
|
+
const trimmed = line.trim();
|
|
19042
|
+
if (!trimmed)
|
|
19043
|
+
return null;
|
|
19044
|
+
try {
|
|
19045
|
+
const parsed = JSON.parse(trimmed);
|
|
19046
|
+
if (parsed && typeof parsed === "object" && typeof parsed.timestamp === "string" && parsed.fiveHour && parsed.sevenDay) {
|
|
19047
|
+
return parsed;
|
|
19048
|
+
}
|
|
19049
|
+
return null;
|
|
19050
|
+
} catch {
|
|
19051
|
+
return null;
|
|
19052
|
+
}
|
|
19053
|
+
}
|
|
19054
|
+
function readLastSampleTimestampMs(filePath) {
|
|
19055
|
+
let fd;
|
|
19056
|
+
try {
|
|
19057
|
+
fd = fs9.openSync(filePath, "r");
|
|
19058
|
+
const stat = fs9.fstatSync(fd);
|
|
19059
|
+
if (stat.size === 0)
|
|
19060
|
+
return null;
|
|
19061
|
+
const chunkSize = 4096;
|
|
19062
|
+
let remaining = stat.size;
|
|
19063
|
+
let buffer = Buffer.alloc(0);
|
|
19064
|
+
while (remaining > 0) {
|
|
19065
|
+
const readSize = Math.min(chunkSize, remaining);
|
|
19066
|
+
const chunk = Buffer.alloc(readSize);
|
|
19067
|
+
fs9.readSync(fd, chunk, 0, readSize, remaining - readSize);
|
|
19068
|
+
buffer = Buffer.concat([chunk, buffer]);
|
|
19069
|
+
remaining -= readSize;
|
|
19070
|
+
const text = buffer.toString("utf8");
|
|
19071
|
+
const trimmedRight = text.replace(/\n+$/, "");
|
|
19072
|
+
const lastNewline = trimmedRight.lastIndexOf("\n");
|
|
19073
|
+
if (lastNewline >= 0) {
|
|
19074
|
+
const lastLine = trimmedRight.slice(lastNewline + 1);
|
|
19075
|
+
const sample = parseSampleLine(lastLine);
|
|
19076
|
+
return sample ? Date.parse(sample.timestamp) || null : null;
|
|
19077
|
+
}
|
|
19078
|
+
if (remaining === 0) {
|
|
19079
|
+
const sample = parseSampleLine(trimmedRight);
|
|
19080
|
+
return sample ? Date.parse(sample.timestamp) || null : null;
|
|
19081
|
+
}
|
|
19082
|
+
}
|
|
19083
|
+
return null;
|
|
19084
|
+
} catch (err) {
|
|
19085
|
+
if (err?.code === "ENOENT")
|
|
19086
|
+
return null;
|
|
19087
|
+
return null;
|
|
19088
|
+
} finally {
|
|
19089
|
+
if (fd !== void 0) {
|
|
19090
|
+
try {
|
|
19091
|
+
fs9.closeSync(fd);
|
|
19092
|
+
} catch {
|
|
19093
|
+
}
|
|
19094
|
+
}
|
|
19095
|
+
}
|
|
19096
|
+
}
|
|
19097
|
+
function atomicRewriteFile(filePath, contents) {
|
|
19098
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.${crypto.randomBytes(8).toString("hex")}.tmp`;
|
|
19099
|
+
try {
|
|
19100
|
+
fs9.writeFileSync(tmp, contents, { encoding: "utf8", mode: 384 });
|
|
19101
|
+
fs9.renameSync(tmp, filePath);
|
|
19102
|
+
} catch (error) {
|
|
19103
|
+
try {
|
|
19104
|
+
fs9.rmSync(tmp, { force: true });
|
|
19105
|
+
} catch {
|
|
19106
|
+
}
|
|
19107
|
+
throw error;
|
|
19108
|
+
}
|
|
19109
|
+
}
|
|
19110
|
+
function pruneFileSync(filePath, retentionDays) {
|
|
19111
|
+
let stat;
|
|
19112
|
+
try {
|
|
19113
|
+
stat = fs9.statSync(filePath);
|
|
19114
|
+
} catch {
|
|
19115
|
+
return { kept: 0, pruned: 0 };
|
|
19116
|
+
}
|
|
19117
|
+
if (stat.size === 0)
|
|
19118
|
+
return { kept: 0, pruned: 0 };
|
|
19119
|
+
const cutoffMs = Date.now() - retentionDays * MS_PER_DAY2;
|
|
19120
|
+
const raw = fs9.readFileSync(filePath, "utf8");
|
|
19121
|
+
const lines = raw.split("\n");
|
|
19122
|
+
const keptLines = [];
|
|
19123
|
+
let pruned = 0;
|
|
19124
|
+
for (const line of lines) {
|
|
19125
|
+
if (!line.trim())
|
|
19126
|
+
continue;
|
|
19127
|
+
const sample = parseSampleLine(line);
|
|
19128
|
+
if (!sample) {
|
|
19129
|
+
pruned += 1;
|
|
19130
|
+
continue;
|
|
19131
|
+
}
|
|
19132
|
+
const ts = Date.parse(sample.timestamp);
|
|
19133
|
+
if (Number.isFinite(ts) && ts >= cutoffMs) {
|
|
19134
|
+
keptLines.push(line);
|
|
19135
|
+
} else {
|
|
19136
|
+
pruned += 1;
|
|
19137
|
+
}
|
|
19138
|
+
}
|
|
19139
|
+
if (pruned > 0) {
|
|
19140
|
+
atomicRewriteFile(filePath, keptLines.length > 0 ? keptLines.join("\n") + "\n" : "");
|
|
19141
|
+
}
|
|
19142
|
+
return { kept: keptLines.length, pruned };
|
|
19143
|
+
}
|
|
19144
|
+
function sampleToQuotaState(sample) {
|
|
19145
|
+
const providerId = sample.runtimeProvider === "claude" ? "claude-code" : "codex";
|
|
19146
|
+
return {
|
|
19147
|
+
fiveHour: { utilization: sample.fiveHour.utilization, resetsAt: sample.fiveHour.resetsAt },
|
|
19148
|
+
sevenDay: { utilization: sample.sevenDay.utilization, resetsAt: sample.sevenDay.resetsAt },
|
|
19149
|
+
available: sample.available,
|
|
19150
|
+
error: sample.error,
|
|
19151
|
+
providerId,
|
|
19152
|
+
source: sample.source ?? "session",
|
|
19153
|
+
capturedAt: sample.timestamp,
|
|
19154
|
+
stale: sample.stale
|
|
19155
|
+
};
|
|
19156
|
+
}
|
|
19157
|
+
async function runAppend(sample, filePath, options) {
|
|
19158
|
+
let lastTs = lastWriteCache.get(filePath);
|
|
19159
|
+
if (lastTs === void 0) {
|
|
19160
|
+
const fromDisk = readLastSampleTimestampMs(filePath);
|
|
19161
|
+
if (fromDisk !== null) {
|
|
19162
|
+
lastTs = fromDisk;
|
|
19163
|
+
lastWriteCache.set(filePath, fromDisk);
|
|
19164
|
+
}
|
|
19165
|
+
}
|
|
19166
|
+
const sampleTs = Date.parse(sample.timestamp);
|
|
19167
|
+
if (lastTs !== void 0 && Number.isFinite(sampleTs) && sampleTs - lastTs < options.minIntervalMs) {
|
|
19168
|
+
return;
|
|
19169
|
+
}
|
|
19170
|
+
ensureHistoryDir(sample.workspaceId);
|
|
19171
|
+
const line = JSON.stringify(sample) + "\n";
|
|
19172
|
+
await fs9.promises.appendFile(filePath, line, { encoding: "utf8", mode: 384 });
|
|
19173
|
+
lastWriteCache.set(filePath, Number.isFinite(sampleTs) ? sampleTs : Date.now());
|
|
19174
|
+
try {
|
|
19175
|
+
const stat = await fs9.promises.stat(filePath);
|
|
19176
|
+
if (stat.size >= PRUNE_FILESIZE_THRESHOLD) {
|
|
19177
|
+
pruneFileSync(filePath, options.retentionDays);
|
|
19178
|
+
}
|
|
19179
|
+
} catch {
|
|
19180
|
+
}
|
|
19181
|
+
try {
|
|
19182
|
+
const providerId = sample.runtimeProvider === "claude" ? "claude-code" : "codex";
|
|
19183
|
+
(0, quotaSnapshots_1.writeQuotaSnapshot)(providerId, sample.providerId, sampleToQuotaState(sample));
|
|
19184
|
+
} catch {
|
|
19185
|
+
}
|
|
19186
|
+
}
|
|
19187
|
+
async function appendQuotaHistorySample(sample, options = {}) {
|
|
19188
|
+
const resolved = {
|
|
19189
|
+
minIntervalMs: options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS,
|
|
19190
|
+
retentionDays: options.retentionDays ?? DEFAULT_RETENTION_DAYS
|
|
19191
|
+
};
|
|
19192
|
+
const filePath = getHistoryFilePath(sample.workspaceId, sample.runtimeProvider);
|
|
19193
|
+
const previous = appendChains.get(filePath) ?? Promise.resolve();
|
|
19194
|
+
const next = previous.then(() => runAppend(sample, filePath, resolved)).catch(() => {
|
|
19195
|
+
});
|
|
19196
|
+
appendChains.set(filePath, next);
|
|
19197
|
+
try {
|
|
19198
|
+
await next;
|
|
19199
|
+
} finally {
|
|
19200
|
+
if (appendChains.get(filePath) === next) {
|
|
19201
|
+
appendChains.delete(filePath);
|
|
19202
|
+
}
|
|
19203
|
+
}
|
|
19204
|
+
}
|
|
19205
|
+
function defaultRangeMs() {
|
|
19206
|
+
const toMs = Date.now();
|
|
19207
|
+
const fromMs = toMs - DEFAULT_RETENTION_DAYS * MS_PER_DAY2;
|
|
19208
|
+
return { fromMs, toMs };
|
|
19209
|
+
}
|
|
19210
|
+
async function readQuotaHistoryRange(options) {
|
|
19211
|
+
const filePath = getHistoryFilePath(options.workspaceId, options.provider);
|
|
19212
|
+
let raw;
|
|
19213
|
+
try {
|
|
19214
|
+
raw = await fs9.promises.readFile(filePath, "utf8");
|
|
19215
|
+
} catch (err) {
|
|
19216
|
+
if (err?.code === "ENOENT")
|
|
19217
|
+
return [];
|
|
19218
|
+
throw err;
|
|
19219
|
+
}
|
|
19220
|
+
const { fromMs: defaultFromMs, toMs: defaultToMs } = defaultRangeMs();
|
|
19221
|
+
const fromMs = options.from ? Date.parse(options.from) : defaultFromMs;
|
|
19222
|
+
const toMs = options.to ? Date.parse(options.to) : defaultToMs;
|
|
19223
|
+
const samples = [];
|
|
19224
|
+
for (const line of raw.split("\n")) {
|
|
19225
|
+
const sample = parseSampleLine(line);
|
|
19226
|
+
if (!sample)
|
|
19227
|
+
continue;
|
|
19228
|
+
const ts = Date.parse(sample.timestamp);
|
|
19229
|
+
if (!Number.isFinite(ts))
|
|
19230
|
+
continue;
|
|
19231
|
+
if (ts < fromMs || ts > toMs)
|
|
19232
|
+
continue;
|
|
19233
|
+
samples.push(sample);
|
|
19234
|
+
}
|
|
19235
|
+
samples.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
19236
|
+
return samples;
|
|
19237
|
+
}
|
|
19238
|
+
function utcDateString(ms) {
|
|
19239
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
19240
|
+
}
|
|
19241
|
+
function addDaysUtc(dateString, days) {
|
|
19242
|
+
const ms = Date.parse(`${dateString}T00:00:00Z`) + days * MS_PER_DAY2;
|
|
19243
|
+
return utcDateString(ms);
|
|
19244
|
+
}
|
|
19245
|
+
async function readQuotaHistoryDailyBuckets2(options) {
|
|
19246
|
+
const samples = await readQuotaHistoryRange(options);
|
|
19247
|
+
const { fromMs: defaultFromMs, toMs: defaultToMs } = defaultRangeMs();
|
|
19248
|
+
const fromMs = options.from ? Date.parse(options.from) : defaultFromMs;
|
|
19249
|
+
const toMs = options.to ? Date.parse(options.to) : defaultToMs;
|
|
19250
|
+
const startDate = utcDateString(fromMs);
|
|
19251
|
+
const endDate = utcDateString(toMs);
|
|
19252
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
19253
|
+
for (const sample of samples) {
|
|
19254
|
+
const day = sample.timestamp.slice(0, 10);
|
|
19255
|
+
const bucket = grouped.get(day);
|
|
19256
|
+
if (bucket) {
|
|
19257
|
+
bucket.push(sample);
|
|
19258
|
+
} else {
|
|
19259
|
+
grouped.set(day, [sample]);
|
|
19260
|
+
}
|
|
19261
|
+
}
|
|
19262
|
+
const buckets = [];
|
|
19263
|
+
let cursor = startDate;
|
|
19264
|
+
for (let i = 0; i <= 366 && cursor <= endDate; i += 1) {
|
|
19265
|
+
const daySamples = grouped.get(cursor);
|
|
19266
|
+
if (!daySamples || daySamples.length === 0) {
|
|
19267
|
+
buckets.push({
|
|
19268
|
+
date: cursor,
|
|
19269
|
+
samples: 0,
|
|
19270
|
+
maxUtilizationFiveHour: 0,
|
|
19271
|
+
maxUtilizationSevenDay: 0,
|
|
19272
|
+
avgUtilizationFiveHour: 0,
|
|
19273
|
+
avgUtilizationSevenDay: 0,
|
|
19274
|
+
anyUnavailable: false
|
|
19275
|
+
});
|
|
19276
|
+
} else {
|
|
19277
|
+
let maxFive = 0;
|
|
19278
|
+
let maxSeven = 0;
|
|
19279
|
+
let sumFive = 0;
|
|
19280
|
+
let sumSeven = 0;
|
|
19281
|
+
let anyUnavailable = false;
|
|
19282
|
+
for (const s of daySamples) {
|
|
19283
|
+
maxFive = Math.max(maxFive, s.fiveHour.utilization);
|
|
19284
|
+
maxSeven = Math.max(maxSeven, s.sevenDay.utilization);
|
|
19285
|
+
sumFive += s.fiveHour.utilization;
|
|
19286
|
+
sumSeven += s.sevenDay.utilization;
|
|
19287
|
+
if (!s.available)
|
|
19288
|
+
anyUnavailable = true;
|
|
19289
|
+
}
|
|
19290
|
+
const n = daySamples.length;
|
|
19291
|
+
buckets.push({
|
|
19292
|
+
date: cursor,
|
|
19293
|
+
samples: n,
|
|
19294
|
+
maxUtilizationFiveHour: maxFive,
|
|
19295
|
+
maxUtilizationSevenDay: maxSeven,
|
|
19296
|
+
avgUtilizationFiveHour: Math.round(sumFive / n * 100) / 100,
|
|
19297
|
+
avgUtilizationSevenDay: Math.round(sumSeven / n * 100) / 100,
|
|
19298
|
+
anyUnavailable
|
|
19299
|
+
});
|
|
19300
|
+
}
|
|
19301
|
+
cursor = addDaysUtc(cursor, 1);
|
|
19302
|
+
}
|
|
19303
|
+
return buckets;
|
|
19304
|
+
}
|
|
19305
|
+
async function pruneQuotaHistory(workspaceId, provider, retentionDays = DEFAULT_RETENTION_DAYS) {
|
|
19306
|
+
const filePath = getHistoryFilePath(workspaceId, provider);
|
|
19307
|
+
return pruneFileSync(filePath, retentionDays);
|
|
19308
|
+
}
|
|
19309
|
+
function _resetQuotaHistoryInMemoryStateForTests() {
|
|
19310
|
+
appendChains.clear();
|
|
19311
|
+
lastWriteCache.clear();
|
|
19312
|
+
}
|
|
19313
|
+
}
|
|
19314
|
+
});
|
|
19315
|
+
|
|
18923
19316
|
// ../sidekick-shared/dist/codexQuota.js
|
|
18924
19317
|
var require_codexQuota = __commonJS({
|
|
18925
19318
|
"../sidekick-shared/dist/codexQuota.js"(exports) {
|
|
18926
19319
|
"use strict";
|
|
19320
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
19321
|
+
if (k2 === void 0) k2 = k;
|
|
19322
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19323
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19324
|
+
desc = { enumerable: true, get: function() {
|
|
19325
|
+
return m[k];
|
|
19326
|
+
} };
|
|
19327
|
+
}
|
|
19328
|
+
Object.defineProperty(o, k2, desc);
|
|
19329
|
+
} : function(o, m, k, k2) {
|
|
19330
|
+
if (k2 === void 0) k2 = k;
|
|
19331
|
+
o[k2] = m[k];
|
|
19332
|
+
});
|
|
19333
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
19334
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19335
|
+
} : function(o, v) {
|
|
19336
|
+
o["default"] = v;
|
|
19337
|
+
});
|
|
19338
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
19339
|
+
var ownKeys = function(o) {
|
|
19340
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
19341
|
+
var ar = [];
|
|
19342
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
19343
|
+
return ar;
|
|
19344
|
+
};
|
|
19345
|
+
return ownKeys(o);
|
|
19346
|
+
};
|
|
19347
|
+
return function(mod) {
|
|
19348
|
+
if (mod && mod.__esModule) return mod;
|
|
19349
|
+
var result = {};
|
|
19350
|
+
if (mod != null) {
|
|
19351
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
19352
|
+
}
|
|
19353
|
+
__setModuleDefault(result, mod);
|
|
19354
|
+
return result;
|
|
19355
|
+
};
|
|
19356
|
+
}();
|
|
18927
19357
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18928
|
-
exports.quotaFromCodexRateLimits =
|
|
18929
|
-
|
|
19358
|
+
exports.quotaFromCodexRateLimits = quotaFromCodexRateLimits2;
|
|
19359
|
+
exports.readLatestCodexQuotaFromRollouts = readLatestCodexQuotaFromRollouts;
|
|
19360
|
+
exports.resolveCodexQuotaFromLocalSources = resolveCodexQuotaFromLocalSources;
|
|
19361
|
+
exports.resolveCodexQuota = resolveCodexQuota2;
|
|
19362
|
+
exports.fetchCodexQuotaFromApi = fetchCodexQuotaFromApi;
|
|
19363
|
+
var fs9 = __importStar(__require("fs"));
|
|
19364
|
+
var path8 = __importStar(__require("path"));
|
|
19365
|
+
var codexProfiles_1 = require_codexProfiles();
|
|
19366
|
+
var quotaSnapshots_1 = require_quotaSnapshots();
|
|
19367
|
+
var codex_1 = require_codex();
|
|
19368
|
+
var DEFAULT_TAIL_BYTES = 2 * 1024 * 1024;
|
|
19369
|
+
var DEFAULT_MAX_SESSION_FILES = 50;
|
|
19370
|
+
var CHATGPT_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
19371
|
+
function normalizePercent(value) {
|
|
19372
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
19373
|
+
}
|
|
19374
|
+
function timestampToIso(seconds) {
|
|
19375
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) {
|
|
19376
|
+
return "";
|
|
19377
|
+
}
|
|
19378
|
+
return new Date(seconds * 1e3).toISOString();
|
|
19379
|
+
}
|
|
19380
|
+
function accountEmail(account) {
|
|
19381
|
+
return account?.email ?? account?.metadata?.email;
|
|
19382
|
+
}
|
|
19383
|
+
function enrichCodexQuota(state, account) {
|
|
19384
|
+
return {
|
|
19385
|
+
...state,
|
|
19386
|
+
runtimeProvider: "codex",
|
|
19387
|
+
providerId: "codex",
|
|
19388
|
+
accountLabel: account?.label,
|
|
19389
|
+
accountDetail: accountEmail(account)
|
|
19390
|
+
};
|
|
19391
|
+
}
|
|
19392
|
+
function unavailableCodexQuota(error, account, meta = {}) {
|
|
19393
|
+
return enrichCodexQuota({
|
|
19394
|
+
fiveHour: { utilization: 0, resetsAt: "" },
|
|
19395
|
+
sevenDay: { utilization: 0, resetsAt: "" },
|
|
19396
|
+
available: false,
|
|
19397
|
+
error,
|
|
19398
|
+
providerId: "codex",
|
|
19399
|
+
fiveHourLabel: "Primary",
|
|
19400
|
+
sevenDayLabel: "Secondary",
|
|
19401
|
+
...meta
|
|
19402
|
+
}, account);
|
|
19403
|
+
}
|
|
19404
|
+
function parseRetryAfterMs(retryAfter) {
|
|
19405
|
+
if (!retryAfter)
|
|
19406
|
+
return void 0;
|
|
19407
|
+
const seconds = Number(retryAfter);
|
|
19408
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
19409
|
+
return Math.round(seconds * 1e3);
|
|
19410
|
+
}
|
|
19411
|
+
const retryAt = Date.parse(retryAfter);
|
|
19412
|
+
if (Number.isNaN(retryAt))
|
|
19413
|
+
return void 0;
|
|
19414
|
+
return Math.max(retryAt - Date.now(), 0);
|
|
19415
|
+
}
|
|
19416
|
+
function firstString(value) {
|
|
19417
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
19418
|
+
}
|
|
19419
|
+
function normalizeCredits(credits) {
|
|
19420
|
+
if (!credits)
|
|
19421
|
+
return void 0;
|
|
19422
|
+
return {
|
|
19423
|
+
hasCredits: credits.has_credits ?? credits.hasCredits,
|
|
19424
|
+
unlimited: credits.unlimited,
|
|
19425
|
+
balance: credits.balance ?? void 0
|
|
19426
|
+
};
|
|
19427
|
+
}
|
|
19428
|
+
function normalizeRateLimitReachedType(value) {
|
|
19429
|
+
if (typeof value === "string")
|
|
19430
|
+
return value;
|
|
19431
|
+
return firstString(value?.kind);
|
|
19432
|
+
}
|
|
19433
|
+
function normalizeApiWindow(window2) {
|
|
19434
|
+
if (!window2)
|
|
19435
|
+
return void 0;
|
|
19436
|
+
const windowMinutes = typeof window2.window_minutes === "number" || window2.window_minutes === null ? window2.window_minutes : typeof window2.limit_window_seconds === "number" ? Math.round(window2.limit_window_seconds / 60) : void 0;
|
|
19437
|
+
const resetsAt = typeof window2.resets_at === "number" || window2.resets_at === null ? window2.resets_at : window2.reset_at;
|
|
19438
|
+
return {
|
|
19439
|
+
used_percent: normalizePercent(window2.used_percent),
|
|
19440
|
+
window_minutes: windowMinutes,
|
|
19441
|
+
resets_at: resetsAt
|
|
19442
|
+
};
|
|
19443
|
+
}
|
|
19444
|
+
function rateLimitsFromUsagePayload(payload) {
|
|
19445
|
+
if (payload.primary || payload.secondary) {
|
|
19446
|
+
return {
|
|
19447
|
+
limit_id: payload.limit_id ?? "codex",
|
|
19448
|
+
limit_name: payload.limit_name ?? null,
|
|
19449
|
+
primary: payload.primary,
|
|
19450
|
+
secondary: payload.secondary,
|
|
19451
|
+
credits: normalizeCredits(payload.credits),
|
|
19452
|
+
plan_type: payload.plan_type ?? void 0,
|
|
19453
|
+
rate_limit_reached_type: normalizeRateLimitReachedType(payload.rate_limit_reached_type)
|
|
19454
|
+
};
|
|
19455
|
+
}
|
|
19456
|
+
const preferred = payload.rate_limit;
|
|
19457
|
+
return {
|
|
19458
|
+
limit_id: "codex",
|
|
19459
|
+
limit_name: null,
|
|
19460
|
+
primary: normalizeApiWindow(preferred?.primary_window),
|
|
19461
|
+
secondary: normalizeApiWindow(preferred?.secondary_window),
|
|
19462
|
+
credits: normalizeCredits(payload.credits),
|
|
19463
|
+
plan_type: payload.plan_type ?? void 0,
|
|
19464
|
+
rate_limit_reached_type: normalizeRateLimitReachedType(payload.rate_limit_reached_type)
|
|
19465
|
+
};
|
|
19466
|
+
}
|
|
19467
|
+
function quotaFromCodexRateLimits2(rateLimits, source = "session", capturedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
18930
19468
|
const primary = rateLimits?.primary;
|
|
18931
19469
|
const secondary = rateLimits?.secondary;
|
|
18932
19470
|
if (!primary && !secondary)
|
|
18933
19471
|
return null;
|
|
18934
19472
|
return {
|
|
18935
|
-
fiveHour: primary ? { utilization: primary.used_percent, resetsAt:
|
|
18936
|
-
sevenDay: secondary ? { utilization: secondary.used_percent, resetsAt:
|
|
19473
|
+
fiveHour: primary ? { utilization: normalizePercent(primary.used_percent), resetsAt: timestampToIso(primary.resets_at) } : { utilization: 0, resetsAt: "" },
|
|
19474
|
+
sevenDay: secondary ? { utilization: normalizePercent(secondary.used_percent), resetsAt: timestampToIso(secondary.resets_at) } : { utilization: 0, resetsAt: "" },
|
|
18937
19475
|
available: true,
|
|
18938
19476
|
providerId: "codex",
|
|
18939
19477
|
source,
|
|
18940
19478
|
capturedAt,
|
|
18941
19479
|
stale: source === "cache",
|
|
18942
19480
|
fiveHourLabel: "Primary",
|
|
18943
|
-
sevenDayLabel: "Secondary"
|
|
18944
|
-
|
|
19481
|
+
sevenDayLabel: "Secondary",
|
|
19482
|
+
limitId: rateLimits?.limit_id,
|
|
19483
|
+
limitName: rateLimits?.limit_name ?? void 0,
|
|
19484
|
+
credits: rateLimits?.credits,
|
|
19485
|
+
planType: rateLimits?.plan_type,
|
|
19486
|
+
rateLimitReachedType: rateLimits?.rate_limit_reached_type
|
|
19487
|
+
};
|
|
19488
|
+
}
|
|
19489
|
+
function readLatestCodexQuotaFromRollouts(sessionPaths, options = {}) {
|
|
19490
|
+
const maxSessionFiles = options.maxSessionFiles ?? DEFAULT_MAX_SESSION_FILES;
|
|
19491
|
+
const maxTailBytes = options.maxTailBytes ?? DEFAULT_TAIL_BYTES;
|
|
19492
|
+
for (const sessionPath of sessionPaths.slice(0, maxSessionFiles)) {
|
|
19493
|
+
const hit = readLatestQuotaFromRollout(sessionPath, maxTailBytes, options.source ?? "session");
|
|
19494
|
+
if (hit)
|
|
19495
|
+
return hit.quota;
|
|
19496
|
+
}
|
|
19497
|
+
return null;
|
|
19498
|
+
}
|
|
19499
|
+
function resolveCodexQuotaFromLocalSources(options = {}) {
|
|
19500
|
+
const account = options.activeAccount !== void 0 ? options.activeAccount : (0, codexProfiles_1.getActiveCodexAccount)();
|
|
19501
|
+
const readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
19502
|
+
const writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
19503
|
+
const maxTailBytes = options.maxTailBytes ?? DEFAULT_TAIL_BYTES;
|
|
19504
|
+
const maxSessionFiles = options.maxSessionFiles ?? DEFAULT_MAX_SESSION_FILES;
|
|
19505
|
+
let ownProvider = false;
|
|
19506
|
+
const provider = options.provider ?? (() => {
|
|
19507
|
+
ownProvider = true;
|
|
19508
|
+
return new codex_1.CodexProvider();
|
|
19509
|
+
})();
|
|
19510
|
+
try {
|
|
19511
|
+
const workspaceSessions = options.workspacePath ? provider.findAllSessions(options.workspacePath) : [];
|
|
19512
|
+
const workspaceQuota = readLatestCodexQuotaFromRollouts(workspaceSessions, { maxTailBytes, maxSessionFiles });
|
|
19513
|
+
if (workspaceQuota) {
|
|
19514
|
+
if (account)
|
|
19515
|
+
writeSnapshot("codex", account.id, workspaceQuota);
|
|
19516
|
+
return enrichCodexQuota(workspaceQuota, account);
|
|
19517
|
+
}
|
|
19518
|
+
const codexHome = options.codexHome ?? (0, codexProfiles_1.resolveSidekickCodexHome)();
|
|
19519
|
+
const accountSessions = findRolloutFiles(path8.join(codexHome, "sessions"));
|
|
19520
|
+
const accountQuota = readLatestCodexQuotaFromRollouts(accountSessions, { maxTailBytes, maxSessionFiles });
|
|
19521
|
+
if (accountQuota) {
|
|
19522
|
+
if (account)
|
|
19523
|
+
writeSnapshot("codex", account.id, accountQuota);
|
|
19524
|
+
return enrichCodexQuota(accountQuota, account);
|
|
19525
|
+
}
|
|
19526
|
+
const cached = account ? readSnapshot("codex", account.id) : null;
|
|
19527
|
+
if (cached) {
|
|
19528
|
+
return enrichCodexQuota({
|
|
19529
|
+
...cached,
|
|
19530
|
+
providerId: "codex",
|
|
19531
|
+
source: "cache",
|
|
19532
|
+
stale: true,
|
|
19533
|
+
fiveHourLabel: cached.fiveHourLabel ?? "Primary",
|
|
19534
|
+
sevenDayLabel: cached.sevenDayLabel ?? "Secondary"
|
|
19535
|
+
}, account);
|
|
19536
|
+
}
|
|
19537
|
+
} finally {
|
|
19538
|
+
if (ownProvider)
|
|
19539
|
+
provider.dispose();
|
|
19540
|
+
}
|
|
19541
|
+
return null;
|
|
19542
|
+
}
|
|
19543
|
+
async function resolveCodexQuota2(options = {}) {
|
|
19544
|
+
const source = options.source ?? "local";
|
|
19545
|
+
const account = options.activeAccount !== void 0 ? options.activeAccount : (0, codexProfiles_1.getActiveCodexAccount)();
|
|
19546
|
+
const writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
19547
|
+
if (source === "api") {
|
|
19548
|
+
const apiQuota = await fetchCodexQuotaFromApi(options);
|
|
19549
|
+
if (apiQuota.available) {
|
|
19550
|
+
if (account)
|
|
19551
|
+
writeSnapshot("codex", account.id, apiQuota);
|
|
19552
|
+
return enrichCodexQuota(apiQuota, account);
|
|
19553
|
+
}
|
|
19554
|
+
const fallback = resolveCodexQuotaFromLocalSources(options);
|
|
19555
|
+
return fallback ?? enrichCodexQuota(apiQuota, account);
|
|
19556
|
+
}
|
|
19557
|
+
const local = resolveCodexQuotaFromLocalSources(options);
|
|
19558
|
+
if (local)
|
|
19559
|
+
return local;
|
|
19560
|
+
if (source === "auto") {
|
|
19561
|
+
const apiQuota = await fetchCodexQuotaFromApi(options);
|
|
19562
|
+
if (apiQuota.available && account) {
|
|
19563
|
+
writeSnapshot("codex", account.id, apiQuota);
|
|
19564
|
+
}
|
|
19565
|
+
return enrichCodexQuota(apiQuota, account);
|
|
19566
|
+
}
|
|
19567
|
+
return unavailableCodexQuota(account ? `No Codex rate-limit data is available for "${account.label ?? account.id}".` : "No Codex rate-limit data is available.", account, { source: "session" });
|
|
19568
|
+
}
|
|
19569
|
+
async function fetchCodexQuotaFromApi(options = {}) {
|
|
19570
|
+
const capturedAt = options.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
19571
|
+
const accessToken = options.accessToken ?? readCodexAccessToken(options.codexHome ?? (0, codexProfiles_1.resolveSidekickCodexHome)());
|
|
19572
|
+
if (!accessToken) {
|
|
19573
|
+
return {
|
|
19574
|
+
fiveHour: { utilization: 0, resetsAt: "" },
|
|
19575
|
+
sevenDay: { utilization: 0, resetsAt: "" },
|
|
19576
|
+
available: false,
|
|
19577
|
+
error: "Codex API refresh requires a ChatGPT login.",
|
|
19578
|
+
failureKind: "auth",
|
|
19579
|
+
providerId: "codex",
|
|
19580
|
+
source: "api",
|
|
19581
|
+
capturedAt,
|
|
19582
|
+
fiveHourLabel: "Primary",
|
|
19583
|
+
sevenDayLabel: "Secondary"
|
|
19584
|
+
};
|
|
19585
|
+
}
|
|
19586
|
+
try {
|
|
19587
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
19588
|
+
const response = await fetchImpl(options.usageUrl ?? CHATGPT_USAGE_URL, {
|
|
19589
|
+
method: "GET",
|
|
19590
|
+
headers: {
|
|
19591
|
+
Authorization: `Bearer ${accessToken}`,
|
|
19592
|
+
Accept: "application/json"
|
|
19593
|
+
}
|
|
19594
|
+
});
|
|
19595
|
+
if (!response.ok) {
|
|
19596
|
+
return {
|
|
19597
|
+
fiveHour: { utilization: 0, resetsAt: "" },
|
|
19598
|
+
sevenDay: { utilization: 0, resetsAt: "" },
|
|
19599
|
+
available: false,
|
|
19600
|
+
error: `Codex usage API error: ${response.status}`,
|
|
19601
|
+
failureKind: response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : response.status >= 500 && response.status <= 599 ? "server" : "unknown",
|
|
19602
|
+
httpStatus: response.status,
|
|
19603
|
+
retryAfterMs: response.status === 429 ? parseRetryAfterMs(response.headers.get("retry-after")) : void 0,
|
|
19604
|
+
providerId: "codex",
|
|
19605
|
+
source: "api",
|
|
19606
|
+
capturedAt,
|
|
19607
|
+
fiveHourLabel: "Primary",
|
|
19608
|
+
sevenDayLabel: "Secondary"
|
|
19609
|
+
};
|
|
19610
|
+
}
|
|
19611
|
+
const payload = await response.json();
|
|
19612
|
+
const quota = quotaFromCodexRateLimits2(rateLimitsFromUsagePayload(payload), "api", capturedAt);
|
|
19613
|
+
if (!quota) {
|
|
19614
|
+
return {
|
|
19615
|
+
fiveHour: { utilization: 0, resetsAt: "" },
|
|
19616
|
+
sevenDay: { utilization: 0, resetsAt: "" },
|
|
19617
|
+
available: false,
|
|
19618
|
+
error: "Codex usage API returned no rate-limit windows.",
|
|
19619
|
+
failureKind: "unknown",
|
|
19620
|
+
providerId: "codex",
|
|
19621
|
+
source: "api",
|
|
19622
|
+
capturedAt,
|
|
19623
|
+
fiveHourLabel: "Primary",
|
|
19624
|
+
sevenDayLabel: "Secondary"
|
|
19625
|
+
};
|
|
19626
|
+
}
|
|
19627
|
+
return quota;
|
|
19628
|
+
} catch {
|
|
19629
|
+
return {
|
|
19630
|
+
fiveHour: { utilization: 0, resetsAt: "" },
|
|
19631
|
+
sevenDay: { utilization: 0, resetsAt: "" },
|
|
19632
|
+
available: false,
|
|
19633
|
+
error: "Codex usage API network error",
|
|
19634
|
+
failureKind: "network",
|
|
19635
|
+
providerId: "codex",
|
|
19636
|
+
source: "api",
|
|
19637
|
+
capturedAt,
|
|
19638
|
+
fiveHourLabel: "Primary",
|
|
19639
|
+
sevenDayLabel: "Secondary"
|
|
19640
|
+
};
|
|
19641
|
+
}
|
|
19642
|
+
}
|
|
19643
|
+
function readCodexAccessToken(codexHome) {
|
|
19644
|
+
try {
|
|
19645
|
+
const parsed = JSON.parse(fs9.readFileSync(path8.join(codexHome, "auth.json"), "utf8"));
|
|
19646
|
+
if (parsed.OPENAI_API_KEY || parsed.auth_mode === "api_key")
|
|
19647
|
+
return null;
|
|
19648
|
+
return parsed.tokens?.access_token || null;
|
|
19649
|
+
} catch {
|
|
19650
|
+
return null;
|
|
19651
|
+
}
|
|
19652
|
+
}
|
|
19653
|
+
function readLatestQuotaFromRollout(sessionPath, maxTailBytes, source) {
|
|
19654
|
+
let fd = null;
|
|
19655
|
+
try {
|
|
19656
|
+
const stat = fs9.statSync(sessionPath);
|
|
19657
|
+
if (!stat.isFile() || stat.size <= 0)
|
|
19658
|
+
return null;
|
|
19659
|
+
const start = Math.max(0, stat.size - maxTailBytes);
|
|
19660
|
+
const bytesToRead = stat.size - start;
|
|
19661
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
19662
|
+
fd = fs9.openSync(sessionPath, "r");
|
|
19663
|
+
const bytesRead = fs9.readSync(fd, buffer, 0, bytesToRead, start);
|
|
19664
|
+
fs9.closeSync(fd);
|
|
19665
|
+
fd = null;
|
|
19666
|
+
let text = buffer.toString("utf8", 0, bytesRead);
|
|
19667
|
+
if (start > 0) {
|
|
19668
|
+
const firstNewline = text.indexOf("\n");
|
|
19669
|
+
text = firstNewline >= 0 ? text.slice(firstNewline + 1) : "";
|
|
19670
|
+
}
|
|
19671
|
+
const lines = text.split("\n");
|
|
19672
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
19673
|
+
const line = lines[i].trim();
|
|
19674
|
+
if (!line || !line.includes("rate_limits"))
|
|
19675
|
+
continue;
|
|
19676
|
+
try {
|
|
19677
|
+
const parsed = JSON.parse(line);
|
|
19678
|
+
if (parsed.type !== "event_msg" || parsed.payload?.type !== "token_count")
|
|
19679
|
+
continue;
|
|
19680
|
+
const quota = quotaFromCodexRateLimits2(parsed.payload.rate_limits, source, parsed.timestamp ?? new Date(stat.mtime).toISOString());
|
|
19681
|
+
if (quota)
|
|
19682
|
+
return { quota, filePath: sessionPath };
|
|
19683
|
+
} catch {
|
|
19684
|
+
}
|
|
19685
|
+
}
|
|
19686
|
+
} catch {
|
|
19687
|
+
return null;
|
|
19688
|
+
} finally {
|
|
19689
|
+
if (fd !== null) {
|
|
19690
|
+
try {
|
|
19691
|
+
fs9.closeSync(fd);
|
|
19692
|
+
} catch {
|
|
19693
|
+
}
|
|
19694
|
+
}
|
|
19695
|
+
}
|
|
19696
|
+
return null;
|
|
19697
|
+
}
|
|
19698
|
+
function findRolloutFiles(sessionsDir) {
|
|
19699
|
+
const results = [];
|
|
19700
|
+
function visit(dir) {
|
|
19701
|
+
let entries;
|
|
19702
|
+
try {
|
|
19703
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
19704
|
+
} catch {
|
|
19705
|
+
return;
|
|
19706
|
+
}
|
|
19707
|
+
for (const entry of entries) {
|
|
19708
|
+
const fullPath = path8.join(dir, entry.name);
|
|
19709
|
+
if (entry.isDirectory()) {
|
|
19710
|
+
visit(fullPath);
|
|
19711
|
+
continue;
|
|
19712
|
+
}
|
|
19713
|
+
if (!entry.isFile() || !entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl"))
|
|
19714
|
+
continue;
|
|
19715
|
+
try {
|
|
19716
|
+
const stat = fs9.statSync(fullPath);
|
|
19717
|
+
if (stat.size > 0) {
|
|
19718
|
+
results.push({ path: fullPath, mtime: stat.mtime.getTime() });
|
|
19719
|
+
}
|
|
19720
|
+
} catch {
|
|
19721
|
+
}
|
|
19722
|
+
}
|
|
19723
|
+
}
|
|
19724
|
+
visit(sessionsDir);
|
|
19725
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
19726
|
+
return results.map((item) => item.path);
|
|
18945
19727
|
}
|
|
18946
19728
|
}
|
|
18947
19729
|
});
|
|
@@ -18993,6 +19775,7 @@ var require_codexQuotaWatcher = __commonJS({
|
|
|
18993
19775
|
var codexProfiles_1 = require_codexProfiles();
|
|
18994
19776
|
var codexQuota_1 = require_codexQuota();
|
|
18995
19777
|
var quotaSnapshots_1 = require_quotaSnapshots();
|
|
19778
|
+
var quotaHistory_1 = require_quotaHistory();
|
|
18996
19779
|
var codex_1 = require_codex();
|
|
18997
19780
|
var DEFAULT_DISCOVERY_POLL_INTERVAL_MS = 3e4;
|
|
18998
19781
|
function accountEmail(account) {
|
|
@@ -19029,6 +19812,10 @@ var require_codexQuotaWatcher = __commonJS({
|
|
|
19029
19812
|
readSnapshot;
|
|
19030
19813
|
writeSnapshot;
|
|
19031
19814
|
watchFile;
|
|
19815
|
+
maxTailBytes;
|
|
19816
|
+
maxSessionFiles;
|
|
19817
|
+
workspaceId;
|
|
19818
|
+
appendHistorySample;
|
|
19032
19819
|
listeners = [];
|
|
19033
19820
|
discoveryTimer;
|
|
19034
19821
|
provider = null;
|
|
@@ -19045,6 +19832,10 @@ var require_codexQuotaWatcher = __commonJS({
|
|
|
19045
19832
|
this.readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
19046
19833
|
this.writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
19047
19834
|
this.watchFile = options.watchFile ?? fs9.watch;
|
|
19835
|
+
this.maxTailBytes = options.maxTailBytes;
|
|
19836
|
+
this.maxSessionFiles = options.maxSessionFiles;
|
|
19837
|
+
this.workspaceId = options.workspaceId;
|
|
19838
|
+
this.appendHistorySample = options.appendHistorySample ?? quotaHistory_1.appendQuotaHistorySample;
|
|
19048
19839
|
}
|
|
19049
19840
|
start() {
|
|
19050
19841
|
if (this.running)
|
|
@@ -19141,6 +19932,28 @@ var require_codexQuotaWatcher = __commonJS({
|
|
|
19141
19932
|
const account = this.getActiveAccount();
|
|
19142
19933
|
if (account) {
|
|
19143
19934
|
this.writeSnapshot("codex", account.id, liveQuota);
|
|
19935
|
+
if (this.workspaceId) {
|
|
19936
|
+
const sample = {
|
|
19937
|
+
timestamp: liveQuota.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
19938
|
+
runtimeProvider: "codex",
|
|
19939
|
+
providerId: account.id,
|
|
19940
|
+
workspaceId: this.workspaceId,
|
|
19941
|
+
fiveHour: { utilization: liveQuota.fiveHour.utilization, resetsAt: liveQuota.fiveHour.resetsAt },
|
|
19942
|
+
sevenDay: { utilization: liveQuota.sevenDay.utilization, resetsAt: liveQuota.sevenDay.resetsAt },
|
|
19943
|
+
available: liveQuota.available,
|
|
19944
|
+
error: liveQuota.error,
|
|
19945
|
+
source: liveQuota.source,
|
|
19946
|
+
stale: liveQuota.stale
|
|
19947
|
+
};
|
|
19948
|
+
try {
|
|
19949
|
+
const result = this.appendHistorySample(sample);
|
|
19950
|
+
if (result && typeof result.catch === "function") {
|
|
19951
|
+
result.catch(() => {
|
|
19952
|
+
});
|
|
19953
|
+
}
|
|
19954
|
+
} catch {
|
|
19955
|
+
}
|
|
19956
|
+
}
|
|
19144
19957
|
}
|
|
19145
19958
|
this.emitState(enrichQuotaState({
|
|
19146
19959
|
...liveQuota,
|
|
@@ -19150,6 +19963,26 @@ var require_codexQuotaWatcher = __commonJS({
|
|
|
19150
19963
|
}
|
|
19151
19964
|
emitCachedOrUnavailable() {
|
|
19152
19965
|
const account = this.getActiveAccount();
|
|
19966
|
+
let localProvider = null;
|
|
19967
|
+
try {
|
|
19968
|
+
localProvider = this.providerFactory();
|
|
19969
|
+
const local = (0, codexQuota_1.resolveCodexQuotaFromLocalSources)({
|
|
19970
|
+
workspacePath: this.workspacePath,
|
|
19971
|
+
activeAccount: account,
|
|
19972
|
+
readSnapshot: this.readSnapshot,
|
|
19973
|
+
writeSnapshot: this.writeSnapshot,
|
|
19974
|
+
provider: localProvider,
|
|
19975
|
+
maxTailBytes: this.maxTailBytes,
|
|
19976
|
+
maxSessionFiles: this.maxSessionFiles
|
|
19977
|
+
});
|
|
19978
|
+
if (local) {
|
|
19979
|
+
this.emitState(local);
|
|
19980
|
+
return;
|
|
19981
|
+
}
|
|
19982
|
+
} catch {
|
|
19983
|
+
} finally {
|
|
19984
|
+
localProvider?.dispose();
|
|
19985
|
+
}
|
|
19153
19986
|
const cached = account ? this.readSnapshot("codex", account.id) : null;
|
|
19154
19987
|
if (cached) {
|
|
19155
19988
|
this.emitState(enrichQuotaState({
|
|
@@ -35961,8 +36794,8 @@ var require_dist = __commonJS({
|
|
|
35961
36794
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35962
36795
|
exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = void 0;
|
|
35963
36796
|
exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.formatTokenCount = exports.formatDurationMs = exports.createJsonlTail = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = void 0;
|
|
35964
|
-
exports.
|
|
35965
|
-
exports.fetchPeakHoursStatus = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = void 0;
|
|
36797
|
+
exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.isMultiAccountEnabled = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = exports.readAccountRegistry = exports.ensureDefaultAccounts = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscript = exports.generateHtmlReport = exports.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = void 0;
|
|
36798
|
+
exports.fetchPeakHoursStatus = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = void 0;
|
|
35966
36799
|
var taskPersistence_1 = require_taskPersistence();
|
|
35967
36800
|
Object.defineProperty(exports, "TASK_PERSISTENCE_SCHEMA_VERSION", { enumerable: true, get: function() {
|
|
35968
36801
|
return taskPersistence_1.TASK_PERSISTENCE_SCHEMA_VERSION;
|
|
@@ -36467,10 +37300,38 @@ var require_dist = __commonJS({
|
|
|
36467
37300
|
Object.defineProperty(exports, "writeQuotaSnapshot", { enumerable: true, get: function() {
|
|
36468
37301
|
return quotaSnapshots_1.writeQuotaSnapshot;
|
|
36469
37302
|
} });
|
|
37303
|
+
var quotaHistory_1 = require_quotaHistory();
|
|
37304
|
+
Object.defineProperty(exports, "appendQuotaHistorySample", { enumerable: true, get: function() {
|
|
37305
|
+
return quotaHistory_1.appendQuotaHistorySample;
|
|
37306
|
+
} });
|
|
37307
|
+
Object.defineProperty(exports, "readQuotaHistoryRange", { enumerable: true, get: function() {
|
|
37308
|
+
return quotaHistory_1.readQuotaHistoryRange;
|
|
37309
|
+
} });
|
|
37310
|
+
Object.defineProperty(exports, "readQuotaHistoryDailyBuckets", { enumerable: true, get: function() {
|
|
37311
|
+
return quotaHistory_1.readQuotaHistoryDailyBuckets;
|
|
37312
|
+
} });
|
|
37313
|
+
Object.defineProperty(exports, "pruneQuotaHistory", { enumerable: true, get: function() {
|
|
37314
|
+
return quotaHistory_1.pruneQuotaHistory;
|
|
37315
|
+
} });
|
|
37316
|
+
Object.defineProperty(exports, "getWorkspaceIdFromPath", { enumerable: true, get: function() {
|
|
37317
|
+
return quotaHistory_1.getWorkspaceIdFromPath;
|
|
37318
|
+
} });
|
|
36470
37319
|
var codexQuota_1 = require_codexQuota();
|
|
37320
|
+
Object.defineProperty(exports, "fetchCodexQuotaFromApi", { enumerable: true, get: function() {
|
|
37321
|
+
return codexQuota_1.fetchCodexQuotaFromApi;
|
|
37322
|
+
} });
|
|
36471
37323
|
Object.defineProperty(exports, "quotaFromCodexRateLimits", { enumerable: true, get: function() {
|
|
36472
37324
|
return codexQuota_1.quotaFromCodexRateLimits;
|
|
36473
37325
|
} });
|
|
37326
|
+
Object.defineProperty(exports, "readLatestCodexQuotaFromRollouts", { enumerable: true, get: function() {
|
|
37327
|
+
return codexQuota_1.readLatestCodexQuotaFromRollouts;
|
|
37328
|
+
} });
|
|
37329
|
+
Object.defineProperty(exports, "resolveCodexQuota", { enumerable: true, get: function() {
|
|
37330
|
+
return codexQuota_1.resolveCodexQuota;
|
|
37331
|
+
} });
|
|
37332
|
+
Object.defineProperty(exports, "resolveCodexQuotaFromLocalSources", { enumerable: true, get: function() {
|
|
37333
|
+
return codexQuota_1.resolveCodexQuotaFromLocalSources;
|
|
37334
|
+
} });
|
|
36474
37335
|
var codexQuotaWatcher_1 = require_codexQuotaWatcher();
|
|
36475
37336
|
Object.defineProperty(exports, "CodexQuotaWatcher", { enumerable: true, get: function() {
|
|
36476
37337
|
return codexQuotaWatcher_1.CodexQuotaWatcher;
|
|
@@ -38803,7 +39664,7 @@ var init_UpdateCheckService = __esm({
|
|
|
38803
39664
|
/** Run the update check (one-shot). */
|
|
38804
39665
|
async check() {
|
|
38805
39666
|
try {
|
|
38806
|
-
const current = "0.18.
|
|
39667
|
+
const current = "0.18.3";
|
|
38807
39668
|
const cached = this.readCache();
|
|
38808
39669
|
let latest;
|
|
38809
39670
|
if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
|
|
@@ -79420,7 +80281,7 @@ function StatusBar({
|
|
|
79420
80281
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: parseBlessedTags(BRAND_INLINE) }),
|
|
79421
80282
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { dimColor: true, children: [
|
|
79422
80283
|
" v",
|
|
79423
|
-
"0.18.
|
|
80284
|
+
"0.18.3"
|
|
79424
80285
|
] }),
|
|
79425
80286
|
updateInfo && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: "yellow", children: [
|
|
79426
80287
|
" (v",
|
|
@@ -79810,7 +80671,7 @@ function ChangelogOverlay({ entries, scrollOffset }) {
|
|
|
79810
80671
|
" ",
|
|
79811
80672
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { bold: true, color: "cyan", children: [
|
|
79812
80673
|
"Terminal Dashboard v",
|
|
79813
|
-
"0.18.
|
|
80674
|
+
"0.18.3"
|
|
79814
80675
|
] }),
|
|
79815
80676
|
latestDate ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { color: "gray", children: [
|
|
79816
80677
|
" \u2014 ",
|
|
@@ -80132,7 +80993,7 @@ var init_mouse = __esm({
|
|
|
80132
80993
|
var CHANGELOG_default;
|
|
80133
80994
|
var init_CHANGELOG = __esm({
|
|
80134
80995
|
"CHANGELOG.md"() {
|
|
80135
|
-
CHANGELOG_default = '# Changelog\n\nAll notable changes to the Sidekick Agent Hub CLI will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.18.1] - 2026-05-08\n\n### Changed\n\n- **Shared dashboard formatting**: terminal dashboard `fmtNum()` and `formatDuration()` now delegate to `formatTokenCount()` and `formatDurationMs()` from `sidekick-shared`, keeping the existing CLI surface (uppercase `K`/`M` suffix, compact `1m5s` style) while removing forked rounding logic\n\n## [0.18.0] - 2026-05-08\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.0**: Picks up the new provider-aware quota orchestration surface \u2014 `MultiProviderQuotaService`, `CodexQuotaWatcher`, `getActiveAccountStatus()`, `extractToolCall()`, cost-provenance helpers (`calculateCostWithProvenance`, `mergeCostSources`), and model display helpers (`shortModelName`, `getModelDisplayInfo`, `compareModelIds`, `sortModelIds`). `parseModelId()` also now recognizes legacy Claude IDs such as `claude-3-opus-20240229` and `claude-3-5-sonnet-20241022`\n- **No CLI runtime changes**: This release ships the shared library upgrade for downstream tooling alignment; `sidekick quota`, `sidekick status`, and the live dashboard keep using the existing polling path. Wiring the new orchestrator into the CLI will land in a follow-up release\n\n## [0.17.7] - 2026-04-28\n\n### Fixed\n\n- **Quota snapshot write race**: Updated the bundled `sidekick-shared` snapshot writer so concurrent `sidekick quota` / Codex session updates no longer collide on `quota-snapshots.json.tmp` or throw `ENOENT`. Failed writes now also clean up their partial temp files instead of leaving orphans in `~/.config/sidekick/`\n\n## [0.17.6] - 2026-04-19\n\n### Added\n\n- **`sidekick peak` command**: One-shot check for Claude\'s current peak-hours state \u2014 weekdays 13:00\u201319:00 UTC, when session limits drain faster on Free/Pro/Max/Team subscriptions. Prints a color-coded status block with a countdown to the next transition. Data comes from the public `promoclock.co/api/status` endpoint (third-party, unaffiliated with Anthropic) with a graceful fallback when unreachable. `--json` emits the full raw state\n- **Peak-hours block in `sidekick status`**: When the active provider is `claude-code`, the Claude + OpenAI health blocks are now followed by a **Claude Peak Hours** block (off-peak or in-peak, with countdown). Gated on the provider so OpenCode / Codex users don\'t trigger an unnecessary third-party fetch. `--json` output includes the new `peak` field\n- **Peak-hours summary in `sidekick quota`**: Claude subscription quota output now shows a **Peak** line under the 5-hour / 7-day bars \u2014 green dot off-peak, orange dot during an active peak, with a countdown to the next transition. `--json` output includes the new `peak` field\n\n## [0.17.5] - 2026-04-18\n\n### Added\n\n- **Default account bootstrap at CLI startup**: The CLI now calls `ensureDefaultAccounts()` from `sidekick-shared` at module load and awaits the result inside a Commander `preAction` hook, so the first real subcommand blocks briefly on the bootstrap while `--version` and `--help` stay instant. When a system Claude Code or Codex credential exists and no saved account is active for that provider yet, the CLI registers it as "Default" \u2014 `sidekick quota`, `sidekick account`, and `sidekick stats` now reflect the active account on first run without requiring an explicit `sidekick account --add` first. Idempotent, never overwrites manually saved accounts, and all errors are swallowed so startup is never blocked\n\nThanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#16](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/16).\n\n## [0.17.4] - 2026-04-17\n\n### Changed\n\n- **Pricing hydration import migrated to `sidekick-shared/node`**: `cli.ts` now imports `hydratePricingCatalog` from the new Node-only subpath and keeps `detectProvider` on the package root. Runtime behavior is unchanged; the split makes the CLI\'s import surface self-documenting (hydration is explicitly a Node API) and aligns the CLI with the shared library\'s new versioned public API contract\n\n## [0.17.3] - 2026-04-17\n\n### Changed\n\n- **Version sync with the VS Code extension**: Republished to keep CLI, extension, and shared-library versions aligned after a cosmetic changelog fix in 0.17.3. No CLI code changes \u2014 functionally identical to 0.17.2\n\n## [0.17.2] - 2026-04-17\n\n### Added\n\n- **LiteLLM pricing hydration on startup**: The CLI now fetches the LiteLLM pricing catalog on startup and caches to `~/.config/sidekick/pricing-catalog.json` with a 24-hour TTL, 3s timeout, and stale-cache fallback \u2014 new model prices are picked up without a CLI upgrade\n- **Expanded pricing coverage**: GPT-4o, GPT-4.1, GPT-5.x, o1, o3, and o3-mini families are now priced alongside the existing Claude entries\n- **Real-dollar Codex / Claude Code costs**: `EventAggregator` computes cost from the pricing table when the session provider doesn\'t report one, so `sidekick` live dashboards now show actual dollars for Codex and Claude Code sessions\n- **`stats` footer lists unpriced models**: `sidekick stats` prints any models encountered with no pricing entry so missing coverage is visible\n\n### Fixed\n\n- **Context-gauge % wrong for Opus 4.7 (1M) and other new models**: The dashboard\'s context gauge was dividing by 200K for Claude Opus 4.7 (native 1M), inflating the displayed %. The shared model \u2192 context-window map now includes Opus/Sonnet 4.7 (1M), GPT-5.4 (1.05M), GPT-5.3-Codex (400K), and GPT-5.3-Codex-Spark (128K). Claude Code\'s `[1m]` suffix is now also honored as an explicit 1M marker\n- **Silent Sonnet-priced fallback for unknown models**: Codex, GPT-5.x, and o-series rows were being rendered at Sonnet rates. Unknown-model rows now render as `\u2014` in yellow instead of inventing a dollar figure\n\n### Changed\n\n- **`historical-data.json` schema v2**: reads `priced` flag and `unpricedModelIds` from records written by the latest VS Code extension; v1 records still read correctly\n\n## [0.17.1] - 2026-04-13\n\n### Fixed\n\n- **Codex multi-home session discovery**: Provider detection now scans all candidate Codex home directories, fixing missed sessions when the managed profile home is empty but the system `~/.codex/` has activity\n\n## [0.17.0] - 2026-04-13\n\n### Added\n\n- **Multi-provider account management**: `sidekick account` now supports `--provider codex` for Codex profile management alongside Claude Code accounts\n- **Codex account lifecycle**: `--add` prepares a profile and spawns `codex login`; `--switch-to` and `--remove` accept email, label, or profile ID\n- **Quota snapshot fallback**: `sidekick quota` for Codex shows cached rate-limit snapshots when no active session exists, with "cached from" timestamp\n\n### Fixed\n\n- **Email normalization**: Claude account lookup normalizes email case for reliable matching\n\n## [0.16.1] - 2026-03-27\n\n### Fixed\n\n- **Dashboard provider status scoping**: The TUI now shows degraded-service notices only for the monitored provider \u2014 Claude for Claude Code sessions, OpenAI for Codex sessions, and no status banner for OpenCode\n\n## [0.16.0] - 2026-03-23\n\n### Changed\n\n- **Consistent cost formatting**: All cost displays (`stats`, `context`, Sessions panel, narrative prompt) now use shared `formatCost()` with intelligent decimal precision (4 places for < $0.01, 2 otherwise)\n- **QuotaService**: Rewritten to wrap shared `QuotaPoller` with exponential backoff instead of manual polling loop\n- **modelContext**: Now re-exports `getModelInfo` from shared library alongside `getContextWindowSize`\n\n## [0.15.2] - 2026-03-18\n\n### Fixed\n\n- **CLI help descriptions**: Updated `quota` and `status` command descriptions to reflect provider-aware behavior\n- **`sidekick quota --provider`**: Added local `--provider` option so `sidekick quota --provider codex` works naturally\n\n## [0.15.0] - 2026-03-18\n\n### Added\n\n- **OpenAI status page monitoring**: CLI dashboard now shows OpenAI API status alongside Claude API status\n- **Codex rate limits in dashboard**: Sessions panel displays Codex rate-limit data with "Rate Limits" header instead of "Quota"\n- **Provider-aware `sidekick quota` command**: Detects active provider and shows Codex rate limits, Claude subscription quota, or an informational message for OpenCode\n\n### Fixed\n\n- **QuotaService polling for Codex**: Dashboard no longer starts Claude OAuth quota polling when the active provider is Codex\n\n## [0.14.2] - 2026-03-16\n\n### Fixed\n\n- **Quota polling interval**: Reduced quota refresh from every 30 seconds to every 5 minutes to avoid unnecessary API calls\n- **SessionsPanel `detailWidth()` call**: Removed unused parameter from `detailWidth()` in the Sessions panel quota rendering\n\n## [0.14.1] - 2026-03-14\n\n### Fixed\n\n- **Per-model context window sizes**: Dashboard context gauge now shows correct utilization for Claude Opus 4.6 (1M context) and other models with non-200K windows\n\n### Changed\n\n- **Shared model context lookup**: CLI dashboard now uses the centralized `getModelContextWindowSize()` from `sidekick-shared` instead of a local duplicate map\n\n## [0.14.0] - 2026-03-12\n\n### Added\n\n- **`sidekick account` Command**: Manage Claude Code accounts from the terminal \u2014 list saved accounts, add the current account with an optional label, switch to the next or a specific account, and remove accounts. Supports `--json` output for scripting\n- **Quota Account Label**: `sidekick quota` now shows the active account email and label above the quota bars when multi-account is enabled\n- **macOS Keychain Support**: `sidekick account` and `sidekick quota` now read and write credentials via the system Keychain on macOS, fixing account switching and quota checks on Mac\n\n## [0.13.8] - 2026-03-12\n\n### Changed\n\n- **Structured quota failure output**: `sidekick quota` now renders consistent auth, rate-limit, server, network, and unexpected-failure copy from shared quota failure descriptors while preserving `--json` machine-readable output\n- **Dashboard unavailable quota rendering**: The Sessions panel now shows Claude Code quota failures inline instead of hiding the quota section whenever subscription data is unavailable\n- **Quota transition toasts**: The Ink dashboard now fires low-noise toast notifications only when Claude Code quota failure state changes, avoiding repeated alerts every polling interval\n\n## [0.13.7] - 2026-03-11\n\n### Changed\n\n- **npm README sync**: Updated the published CLI package README to reflect current OpenCode monitoring behavior, platform-specific data directories, and the `sqlite3` runtime requirement\n- **README badge cleanup**: Removed the Ask DeepWiki badge from the published CLI package README; the repo root README still keeps it\n\n## [0.13.6] - 2026-03-11\n\n### Changed\n\n- **Refreshed CLI Dashboard Wordmark**: Updated the dashboard wordmark/header styling for a cleaner splash and dashboard identity\n\n### Fixed\n\n- **OpenCode dashboard startup**: OpenCode DB-backed session discovery now resolves projects by worktree, sandboxes, and session directory instead of quietly behaving like no session exists\n- **OpenCode runtime notices**: The CLI now prints an OpenCode-only actionable notice when `opencode.db` exists but `sqlite3` is missing, blocked, or otherwise unusable in the current shell environment\n\n## [0.13.5] - 2026-03-10\n\n### Added\n\n- **`sidekick status` Command**: One-shot Claude API status check with color-coded text output and `--json` mode\n- **Dashboard Status Banner**: Status bar shows a colored `\u25CF API minor/major/critical` indicator when Claude is degraded; Sessions panel Summary tab shows an "API Status" section with affected components and active incident details. Polls every 60s\n\n## [0.13.4] - 2026-03-08\n\n### Fixed\n\n- **Onboarding Phrase Spam**: Splash screen and detail pane motivational phrases memoized \u2014 no longer flicker every render tick (fixes [#13](https://github.com/cesarandreslopez/sidekick-agent-hub/issues/13))\n\n### Changed\n\n- **Simplified Logo**: Replaced 6-line ASCII robot art with compact text header in splash, help, and changelog overlays\n- **Removed Dead Code**: Removed unused `getSplashContent()` and `HELP_HEADER` exports from branding module\n\n## [0.13.3] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.2] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.1] - 2026-03-04\n\n### Added\n\n- **`sidekick quota` Command**: One-shot subscription quota check showing 5-hour and 7-day utilization with color-coded progress bars and reset countdowns \u2014 supports `--json` for machine-readable output\n- **Quota Projections**: Elapsed-time projections shown in `sidekick quota` output and TUI dashboard quota section \u2014 displays projected end-of-window utilization next to current value (e.g., `40% \u2192 100%`), included in `--json` output as `projectedFiveHour` / `projectedSevenDay`\n\n## [0.13.0] - 2026-03-03\n\n_No CLI-specific changes in this release._\n\n## [0.12.10] - 2026-03-01\n\n### Added\n\n- **Events Panel** (key 7): Scrollable live event stream with colored type badges (`[USR]`, `[AST]`, `[TOOL]`, `[RES]`), timestamps, and keyword-highlighted summaries; detail tabs for full event JSON and surrounding context\n- **Charts Panel** (key 8): Tool frequency horizontal bars, event type distribution, 60-minute activity heatmap using `\u2591\u2592\u2593\u2588` intensity characters, and pattern analysis with frequency bars and template text\n- **Multi-Mode Filter**: `/` filter overlay now supports four modes \u2014 substring, fuzzy, regex, and date range \u2014 Tab cycles modes, regex mode shows red validation errors\n- **Search Term Highlighting**: Active filter terms highlighted in blue within side list items\n- **Timeline Keyword Coloring**: Event summaries in the Sessions panel Timeline tab now use semantic keyword coloring \u2014 errors red, success green, tool names cyan, file paths magenta\n\n### Removed\n\n- **Search Panel**: Removed redundant Search panel (previously key 7) \u2014 the `/` filter with multi-mode support serves the same purpose\n\n## [0.12.9] - 2026-02-28\n\n### Added\n\n- **Standalone Data Commands**: `sidekick tasks`, `sidekick decisions`, `sidekick notes`, `sidekick stats`, `sidekick handoff` for accessing project data without launching the TUI\n- **`sidekick search <query>`**: Cross-session full-text search from the terminal\n- **`sidekick context`**: Composite output of tasks, decisions, notes, and handoff for piping into other tools\n- **`--list` flag on `sidekick dump`**: Discover available session IDs before requiring `--session <id>`\n- **Search Panel**: Search panel (panel 7) wired into the TUI dashboard\n\n### Changed\n\n- **`taskMerger` utility**: Duplicate `mergeTasks` logic extracted into shared `taskMerger` utility\n- **Model constants**: Hardcoded model IDs extracted to named constants\n\n### Fixed\n\n- **`convention` icon**: Notes panel icon replaced with valid `tip` type\n- **Linux clipboard**: Now supports Wayland (`wl-copy`) and `xsel` fallbacks, with error messages instead of silent failure\n- **`provider.dispose()`**: Added to `dump` and `report` commands (prevents SQLite connection leaks)\n\n## [0.12.8] - 2026-02-28\n\n### Changed\n\n- **Dashboard UI/UX Polish**: Visual overhaul for better hierarchy, consistency, and readability\n - Splash screen and help overlay now display the robot ASCII logo\n - Toast notifications show severity icons (\u2718 error, \u26A0 warning, \u25CF info) with inner padding\n - Focused pane uses double-border for clear focus indication\n - Section dividers (`\u2500\u2500 Title \u2500\u2500\u2500\u2500`) replace bare bold headers in summary, agents, and context attribution\n - Tab bar: active tab underlined in magenta, inactive tabs dimmed, bracket syntax removed\n - Status bar: segmented layout with `\u2502` separators; keys bold, labels dim\n - Summary metrics condensed: elapsed/events/compactions on one line, tokens on one line with cache rate and cost\n - Sparklines display peak metadata annotations\n - Progress bars use blessed color tags for consistent coloring\n - Help overlay uses dot-leader alignment for all keybinding rows\n - Empty state hints per panel (e.g. "Tasks appear as your agent works.")\n - Session picker groups sessions by provider with section headers when multiple providers are present\n\n## [0.12.7] - 2026-02-27\n\n### Added\n\n- **HTML Session Report**: `sidekick report` command generates a self-contained HTML report and opens it in the default browser\n - Options: `--session`, `--output`, `--theme` (dark/light), `--no-open`, `--no-thinking`\n - TUI Dashboard: press `r` to generate and open an HTML report for the current session\n\n## [0.12.6] - 2026-02-26\n\n### Added\n\n- **Session Dump Command**: `sidekick dump` exports session data in text, markdown, or JSON format with `--format`, `--width`, and `--expand` options\n- **Plans Panel Re-enabled**: Plans panel restored in CLI dashboard with plan file discovery from `~/.claude/plans/`\n- **Enhanced Status Bar**: Session info display improved with richer metadata\n\n### Fixed\n\n- **Old snapshot format migration**: Restoring pre-0.12.3 session snapshots no longer shows empty timeline entries\n\n### Changed\n\n- **Phrase library moved to shared**: CLI-specific phrase formatting kept local, all phrase content now from `sidekick-shared`\n\n## [0.12.5] - 2026-02-24\n\n### Fixed\n\n- **Update check too slow to notice new versions**: Reduced npm registry cache TTL from 24 hours to 4 hours so upgrade notices appear sooner after a new release\n\n## [0.12.4] - 2026-02-24\n\n### Fixed\n\n- **Session crash on upgrade**: Fixed `d.timestamp.getTime is not a function` error when restoring tool call data from session snapshots \u2014 `Date` objects were serialized to strings by JSON but not rehydrated on restore, causing the session monitor to crash on first run after upgrading from 0.12.2 to 0.12.3\n\n## [0.12.3] - 2026-02-24\n\n### Added\n\n- **Latest-node indicator**: The most recently added node in tree and boxed mind map views is now marked with a yellow indicator\n- **Plan analytics in mind map**: Tree and boxed views now display plan progress and per-step metrics\n - Tree view: plan header shows completion stats; steps show complexity, duration, tokens, tool calls, and errors in metadata brackets\n - Box view: progress bar with completion percentage; steps show right-aligned metrics; subtitle shows step count and total duration\n- **Cross-provider plan extraction**: Shared `PlanExtractor` now handles Claude Code (EnterPlanMode/ExitPlanMode) and OpenCode (`<proposed_plan>` XML) plans \u2014 previously only Codex plans were shown\n- **Enriched plan data model**: Plan steps include duration, token count, tool call count, and error messages\n- **Phase-grouped plan display**: When a plan has phase structure, tree and boxed views group steps under phase headers with context lines from the original plan markdown\n- **Node type filter**: Press `f` on the Mind Map tab to cycle through node type filters (file, tool, task, subagent, command, plan, knowledge-note) \u2014 non-matching sections render dimmed in grey\n\n### Fixed\n\n- **Kanban board regression**: Subagent and plan-step tasks now correctly appear in the kanban board\n\n### Changed\n\n- **Plans panel temporarily disabled**: The Plans panel in the CLI dashboard is disabled until plan-mode event capture is reliably working end-to-end. Plan nodes in the mind map remain active.\n- `DashboardState` now delegates to shared `EventAggregator` instead of maintaining its own aggregation logic\n\n## [0.12.2] - 2026-02-23\n\n### Added\n\n- **Update notifications**: The dashboard now checks the npm registry for newer versions on startup and shows a yellow banner in the status bar when an update is available (e.g., `v0.13.0 available \u2014 npm i -g sidekick-agent-hub`). Results are cached for 24 hours to avoid repeated network requests.\n\n## [0.12.1] - 2026-02-23\n\n### Fixed\n\n- **VS Code integration**: Fixed exit code 127 when the extension launches the CLI dashboard on systems using nvm or volta (node binary not found when shell init is bypassed)\n\n## [0.12.0] - 2026-02-22\n\n### Added\n\n- **"Open CLI Dashboard" VS Code Integration**: New VS Code command `Sidekick: Open CLI Dashboard` launches the TUI dashboard in an integrated terminal\n - Install the CLI with `npm install -g sidekick-agent-hub`\n\n## [0.11.0] - 2026-02-19\n\n### Added\n\n- **Initial Release**: Full-screen TUI dashboard for monitoring agent sessions from the terminal\n - Ink-based terminal UI with panels for sessions, tasks, kanban, mind map, notes, decisions, search, files, and git diff\n - Multi-provider support: auto-detects Claude Code, OpenCode, and Codex sessions\n - Reads from `~/.config/sidekick/` \u2014 the same data files the VS Code extension writes\n - Usage: `sidekick dashboard [--project <path>] [--provider <id>]`\n';
|
|
80996
|
+
CHANGELOG_default = '# Changelog\n\nAll notable changes to the Sidekick Agent Hub CLI will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.18.3] - 2026-05-19\n\n### Added\n\n- **`sidekick quota history`**: New subcommand that renders a 13-week GitHub-contributions-style heatmap of quota utilization for the current workspace. Flags: `--weeks <n>` (1-26, default 13), `--provider claude|codex` (default both), `--workspace <path>` (default cwd). Bucketed glyphs (`\xB7 \u2591 \u2592 \u2593 \u2588`) are color-coded by utilization band (\u22640 / <25 / <50 / <75 / \u226575), with per-provider rows and a peak / avg / unavailable-days / samples footer. Days that hit `available: false` render as a red `\xD7`. With `--json`, emits a `{ workspaceId, weeks, providers: { claude?, codex? }, generatedAt }` payload \u2014 the same shape consumed by the VS Code dashboard\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.3**: Picks up the new per-workspace quota history surface (`appendQuotaHistorySample`, `readQuotaHistoryRange`, `readQuotaHistoryDailyBuckets`, `pruneQuotaHistory`, `getWorkspaceIdFromPath`) and the optional `workspaceId` / `appendHistorySample` hooks on `CodexQuotaWatcher`\n\n## [0.18.2] - 2026-05-19\n\n### Added\n\n- **`sidekick quota --refresh`**: New flag on the `quota` command that, for Codex, explicitly refreshes from the ChatGPT usage API before falling back to local rollout data and cached snapshots. Without the flag, the Codex quota path stays fully local and makes no upstream network call\n\n### Changed\n\n- **Codex quota is local-only by default**: `sidekick quota --provider codex` now delegates to the new `resolveCodexQuota` orchestrator in `sidekick-shared`. It checks the current workspace\'s most recent rollout, then recent account-level rollouts under `CODEX_HOME/sessions`, then the active account\'s cached snapshot \u2014 no upstream network call unless `--refresh` is passed. Failure output continues to include structured `failureKind` / `httpStatus` / `retryAfterMs` fields under `--json`\n- **Bundled `sidekick-shared` 0.18.2**: Picks up the new Codex quota orchestrator (`resolveCodexQuota`, `resolveCodexQuotaFromLocalSources`, `readLatestCodexQuotaFromRollouts`, `fetchCodexQuotaFromApi`), the relaxed `CodexRateLimits` shape (nullable `resets_at` / `window_minutes`), the rate-limit-only `token_count` event emission in `JsonlSessionWatcher`, and `state_N.sqlite` discovery in `CodexDatabase` + provider auto-detect\n\n## [0.18.1] - 2026-05-08\n\n### Changed\n\n- **Shared dashboard formatting**: terminal dashboard `fmtNum()` and `formatDuration()` now delegate to `formatTokenCount()` and `formatDurationMs()` from `sidekick-shared`, keeping the existing CLI surface (uppercase `K`/`M` suffix, compact `1m5s` style) while removing forked rounding logic\n\n## [0.18.0] - 2026-05-08\n\n### Changed\n\n- **Bundled `sidekick-shared` 0.18.0**: Picks up the new provider-aware quota orchestration surface \u2014 `MultiProviderQuotaService`, `CodexQuotaWatcher`, `getActiveAccountStatus()`, `extractToolCall()`, cost-provenance helpers (`calculateCostWithProvenance`, `mergeCostSources`), and model display helpers (`shortModelName`, `getModelDisplayInfo`, `compareModelIds`, `sortModelIds`). `parseModelId()` also now recognizes legacy Claude IDs such as `claude-3-opus-20240229` and `claude-3-5-sonnet-20241022`\n- **No CLI runtime changes**: This release ships the shared library upgrade for downstream tooling alignment; `sidekick quota`, `sidekick status`, and the live dashboard keep using the existing polling path. Wiring the new orchestrator into the CLI will land in a follow-up release\n\n## [0.17.7] - 2026-04-28\n\n### Fixed\n\n- **Quota snapshot write race**: Updated the bundled `sidekick-shared` snapshot writer so concurrent `sidekick quota` / Codex session updates no longer collide on `quota-snapshots.json.tmp` or throw `ENOENT`. Failed writes now also clean up their partial temp files instead of leaving orphans in `~/.config/sidekick/`\n\n## [0.17.6] - 2026-04-19\n\n### Added\n\n- **`sidekick peak` command**: One-shot check for Claude\'s current peak-hours state \u2014 weekdays 13:00\u201319:00 UTC, when session limits drain faster on Free/Pro/Max/Team subscriptions. Prints a color-coded status block with a countdown to the next transition. Data comes from the public `promoclock.co/api/status` endpoint (third-party, unaffiliated with Anthropic) with a graceful fallback when unreachable. `--json` emits the full raw state\n- **Peak-hours block in `sidekick status`**: When the active provider is `claude-code`, the Claude + OpenAI health blocks are now followed by a **Claude Peak Hours** block (off-peak or in-peak, with countdown). Gated on the provider so OpenCode / Codex users don\'t trigger an unnecessary third-party fetch. `--json` output includes the new `peak` field\n- **Peak-hours summary in `sidekick quota`**: Claude subscription quota output now shows a **Peak** line under the 5-hour / 7-day bars \u2014 green dot off-peak, orange dot during an active peak, with a countdown to the next transition. `--json` output includes the new `peak` field\n\n## [0.17.5] - 2026-04-18\n\n### Added\n\n- **Default account bootstrap at CLI startup**: The CLI now calls `ensureDefaultAccounts()` from `sidekick-shared` at module load and awaits the result inside a Commander `preAction` hook, so the first real subcommand blocks briefly on the bootstrap while `--version` and `--help` stay instant. When a system Claude Code or Codex credential exists and no saved account is active for that provider yet, the CLI registers it as "Default" \u2014 `sidekick quota`, `sidekick account`, and `sidekick stats` now reflect the active account on first run without requiring an explicit `sidekick account --add` first. Idempotent, never overwrites manually saved accounts, and all errors are swallowed so startup is never blocked\n\nThanks to [@B33pBeeps](https://github.com/B33pBeeps) (Juan Fourie) for contributing this feature in [#16](https://github.com/cesarandreslopez/sidekick-agent-hub/pull/16).\n\n## [0.17.4] - 2026-04-17\n\n### Changed\n\n- **Pricing hydration import migrated to `sidekick-shared/node`**: `cli.ts` now imports `hydratePricingCatalog` from the new Node-only subpath and keeps `detectProvider` on the package root. Runtime behavior is unchanged; the split makes the CLI\'s import surface self-documenting (hydration is explicitly a Node API) and aligns the CLI with the shared library\'s new versioned public API contract\n\n## [0.17.3] - 2026-04-17\n\n### Changed\n\n- **Version sync with the VS Code extension**: Republished to keep CLI, extension, and shared-library versions aligned after a cosmetic changelog fix in 0.17.3. No CLI code changes \u2014 functionally identical to 0.17.2\n\n## [0.17.2] - 2026-04-17\n\n### Added\n\n- **LiteLLM pricing hydration on startup**: The CLI now fetches the LiteLLM pricing catalog on startup and caches to `~/.config/sidekick/pricing-catalog.json` with a 24-hour TTL, 3s timeout, and stale-cache fallback \u2014 new model prices are picked up without a CLI upgrade\n- **Expanded pricing coverage**: GPT-4o, GPT-4.1, GPT-5.x, o1, o3, and o3-mini families are now priced alongside the existing Claude entries\n- **Real-dollar Codex / Claude Code costs**: `EventAggregator` computes cost from the pricing table when the session provider doesn\'t report one, so `sidekick` live dashboards now show actual dollars for Codex and Claude Code sessions\n- **`stats` footer lists unpriced models**: `sidekick stats` prints any models encountered with no pricing entry so missing coverage is visible\n\n### Fixed\n\n- **Context-gauge % wrong for Opus 4.7 (1M) and other new models**: The dashboard\'s context gauge was dividing by 200K for Claude Opus 4.7 (native 1M), inflating the displayed %. The shared model \u2192 context-window map now includes Opus/Sonnet 4.7 (1M), GPT-5.4 (1.05M), GPT-5.3-Codex (400K), and GPT-5.3-Codex-Spark (128K). Claude Code\'s `[1m]` suffix is now also honored as an explicit 1M marker\n- **Silent Sonnet-priced fallback for unknown models**: Codex, GPT-5.x, and o-series rows were being rendered at Sonnet rates. Unknown-model rows now render as `\u2014` in yellow instead of inventing a dollar figure\n\n### Changed\n\n- **`historical-data.json` schema v2**: reads `priced` flag and `unpricedModelIds` from records written by the latest VS Code extension; v1 records still read correctly\n\n## [0.17.1] - 2026-04-13\n\n### Fixed\n\n- **Codex multi-home session discovery**: Provider detection now scans all candidate Codex home directories, fixing missed sessions when the managed profile home is empty but the system `~/.codex/` has activity\n\n## [0.17.0] - 2026-04-13\n\n### Added\n\n- **Multi-provider account management**: `sidekick account` now supports `--provider codex` for Codex profile management alongside Claude Code accounts\n- **Codex account lifecycle**: `--add` prepares a profile and spawns `codex login`; `--switch-to` and `--remove` accept email, label, or profile ID\n- **Quota snapshot fallback**: `sidekick quota` for Codex shows cached rate-limit snapshots when no active session exists, with "cached from" timestamp\n\n### Fixed\n\n- **Email normalization**: Claude account lookup normalizes email case for reliable matching\n\n## [0.16.1] - 2026-03-27\n\n### Fixed\n\n- **Dashboard provider status scoping**: The TUI now shows degraded-service notices only for the monitored provider \u2014 Claude for Claude Code sessions, OpenAI for Codex sessions, and no status banner for OpenCode\n\n## [0.16.0] - 2026-03-23\n\n### Changed\n\n- **Consistent cost formatting**: All cost displays (`stats`, `context`, Sessions panel, narrative prompt) now use shared `formatCost()` with intelligent decimal precision (4 places for < $0.01, 2 otherwise)\n- **QuotaService**: Rewritten to wrap shared `QuotaPoller` with exponential backoff instead of manual polling loop\n- **modelContext**: Now re-exports `getModelInfo` from shared library alongside `getContextWindowSize`\n\n## [0.15.2] - 2026-03-18\n\n### Fixed\n\n- **CLI help descriptions**: Updated `quota` and `status` command descriptions to reflect provider-aware behavior\n- **`sidekick quota --provider`**: Added local `--provider` option so `sidekick quota --provider codex` works naturally\n\n## [0.15.0] - 2026-03-18\n\n### Added\n\n- **OpenAI status page monitoring**: CLI dashboard now shows OpenAI API status alongside Claude API status\n- **Codex rate limits in dashboard**: Sessions panel displays Codex rate-limit data with "Rate Limits" header instead of "Quota"\n- **Provider-aware `sidekick quota` command**: Detects active provider and shows Codex rate limits, Claude subscription quota, or an informational message for OpenCode\n\n### Fixed\n\n- **QuotaService polling for Codex**: Dashboard no longer starts Claude OAuth quota polling when the active provider is Codex\n\n## [0.14.2] - 2026-03-16\n\n### Fixed\n\n- **Quota polling interval**: Reduced quota refresh from every 30 seconds to every 5 minutes to avoid unnecessary API calls\n- **SessionsPanel `detailWidth()` call**: Removed unused parameter from `detailWidth()` in the Sessions panel quota rendering\n\n## [0.14.1] - 2026-03-14\n\n### Fixed\n\n- **Per-model context window sizes**: Dashboard context gauge now shows correct utilization for Claude Opus 4.6 (1M context) and other models with non-200K windows\n\n### Changed\n\n- **Shared model context lookup**: CLI dashboard now uses the centralized `getModelContextWindowSize()` from `sidekick-shared` instead of a local duplicate map\n\n## [0.14.0] - 2026-03-12\n\n### Added\n\n- **`sidekick account` Command**: Manage Claude Code accounts from the terminal \u2014 list saved accounts, add the current account with an optional label, switch to the next or a specific account, and remove accounts. Supports `--json` output for scripting\n- **Quota Account Label**: `sidekick quota` now shows the active account email and label above the quota bars when multi-account is enabled\n- **macOS Keychain Support**: `sidekick account` and `sidekick quota` now read and write credentials via the system Keychain on macOS, fixing account switching and quota checks on Mac\n\n## [0.13.8] - 2026-03-12\n\n### Changed\n\n- **Structured quota failure output**: `sidekick quota` now renders consistent auth, rate-limit, server, network, and unexpected-failure copy from shared quota failure descriptors while preserving `--json` machine-readable output\n- **Dashboard unavailable quota rendering**: The Sessions panel now shows Claude Code quota failures inline instead of hiding the quota section whenever subscription data is unavailable\n- **Quota transition toasts**: The Ink dashboard now fires low-noise toast notifications only when Claude Code quota failure state changes, avoiding repeated alerts every polling interval\n\n## [0.13.7] - 2026-03-11\n\n### Changed\n\n- **npm README sync**: Updated the published CLI package README to reflect current OpenCode monitoring behavior, platform-specific data directories, and the `sqlite3` runtime requirement\n- **README badge cleanup**: Removed the Ask DeepWiki badge from the published CLI package README; the repo root README still keeps it\n\n## [0.13.6] - 2026-03-11\n\n### Changed\n\n- **Refreshed CLI Dashboard Wordmark**: Updated the dashboard wordmark/header styling for a cleaner splash and dashboard identity\n\n### Fixed\n\n- **OpenCode dashboard startup**: OpenCode DB-backed session discovery now resolves projects by worktree, sandboxes, and session directory instead of quietly behaving like no session exists\n- **OpenCode runtime notices**: The CLI now prints an OpenCode-only actionable notice when `opencode.db` exists but `sqlite3` is missing, blocked, or otherwise unusable in the current shell environment\n\n## [0.13.5] - 2026-03-10\n\n### Added\n\n- **`sidekick status` Command**: One-shot Claude API status check with color-coded text output and `--json` mode\n- **Dashboard Status Banner**: Status bar shows a colored `\u25CF API minor/major/critical` indicator when Claude is degraded; Sessions panel Summary tab shows an "API Status" section with affected components and active incident details. Polls every 60s\n\n## [0.13.4] - 2026-03-08\n\n### Fixed\n\n- **Onboarding Phrase Spam**: Splash screen and detail pane motivational phrases memoized \u2014 no longer flicker every render tick (fixes [#13](https://github.com/cesarandreslopez/sidekick-agent-hub/issues/13))\n\n### Changed\n\n- **Simplified Logo**: Replaced 6-line ASCII robot art with compact text header in splash, help, and changelog overlays\n- **Removed Dead Code**: Removed unused `getSplashContent()` and `HELP_HEADER` exports from branding module\n\n## [0.13.3] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.2] - 2026-03-04\n\n_No CLI-specific changes in this release._\n\n## [0.13.1] - 2026-03-04\n\n### Added\n\n- **`sidekick quota` Command**: One-shot subscription quota check showing 5-hour and 7-day utilization with color-coded progress bars and reset countdowns \u2014 supports `--json` for machine-readable output\n- **Quota Projections**: Elapsed-time projections shown in `sidekick quota` output and TUI dashboard quota section \u2014 displays projected end-of-window utilization next to current value (e.g., `40% \u2192 100%`), included in `--json` output as `projectedFiveHour` / `projectedSevenDay`\n\n## [0.13.0] - 2026-03-03\n\n_No CLI-specific changes in this release._\n\n## [0.12.10] - 2026-03-01\n\n### Added\n\n- **Events Panel** (key 7): Scrollable live event stream with colored type badges (`[USR]`, `[AST]`, `[TOOL]`, `[RES]`), timestamps, and keyword-highlighted summaries; detail tabs for full event JSON and surrounding context\n- **Charts Panel** (key 8): Tool frequency horizontal bars, event type distribution, 60-minute activity heatmap using `\u2591\u2592\u2593\u2588` intensity characters, and pattern analysis with frequency bars and template text\n- **Multi-Mode Filter**: `/` filter overlay now supports four modes \u2014 substring, fuzzy, regex, and date range \u2014 Tab cycles modes, regex mode shows red validation errors\n- **Search Term Highlighting**: Active filter terms highlighted in blue within side list items\n- **Timeline Keyword Coloring**: Event summaries in the Sessions panel Timeline tab now use semantic keyword coloring \u2014 errors red, success green, tool names cyan, file paths magenta\n\n### Removed\n\n- **Search Panel**: Removed redundant Search panel (previously key 7) \u2014 the `/` filter with multi-mode support serves the same purpose\n\n## [0.12.9] - 2026-02-28\n\n### Added\n\n- **Standalone Data Commands**: `sidekick tasks`, `sidekick decisions`, `sidekick notes`, `sidekick stats`, `sidekick handoff` for accessing project data without launching the TUI\n- **`sidekick search <query>`**: Cross-session full-text search from the terminal\n- **`sidekick context`**: Composite output of tasks, decisions, notes, and handoff for piping into other tools\n- **`--list` flag on `sidekick dump`**: Discover available session IDs before requiring `--session <id>`\n- **Search Panel**: Search panel (panel 7) wired into the TUI dashboard\n\n### Changed\n\n- **`taskMerger` utility**: Duplicate `mergeTasks` logic extracted into shared `taskMerger` utility\n- **Model constants**: Hardcoded model IDs extracted to named constants\n\n### Fixed\n\n- **`convention` icon**: Notes panel icon replaced with valid `tip` type\n- **Linux clipboard**: Now supports Wayland (`wl-copy`) and `xsel` fallbacks, with error messages instead of silent failure\n- **`provider.dispose()`**: Added to `dump` and `report` commands (prevents SQLite connection leaks)\n\n## [0.12.8] - 2026-02-28\n\n### Changed\n\n- **Dashboard UI/UX Polish**: Visual overhaul for better hierarchy, consistency, and readability\n - Splash screen and help overlay now display the robot ASCII logo\n - Toast notifications show severity icons (\u2718 error, \u26A0 warning, \u25CF info) with inner padding\n - Focused pane uses double-border for clear focus indication\n - Section dividers (`\u2500\u2500 Title \u2500\u2500\u2500\u2500`) replace bare bold headers in summary, agents, and context attribution\n - Tab bar: active tab underlined in magenta, inactive tabs dimmed, bracket syntax removed\n - Status bar: segmented layout with `\u2502` separators; keys bold, labels dim\n - Summary metrics condensed: elapsed/events/compactions on one line, tokens on one line with cache rate and cost\n - Sparklines display peak metadata annotations\n - Progress bars use blessed color tags for consistent coloring\n - Help overlay uses dot-leader alignment for all keybinding rows\n - Empty state hints per panel (e.g. "Tasks appear as your agent works.")\n - Session picker groups sessions by provider with section headers when multiple providers are present\n\n## [0.12.7] - 2026-02-27\n\n### Added\n\n- **HTML Session Report**: `sidekick report` command generates a self-contained HTML report and opens it in the default browser\n - Options: `--session`, `--output`, `--theme` (dark/light), `--no-open`, `--no-thinking`\n - TUI Dashboard: press `r` to generate and open an HTML report for the current session\n\n## [0.12.6] - 2026-02-26\n\n### Added\n\n- **Session Dump Command**: `sidekick dump` exports session data in text, markdown, or JSON format with `--format`, `--width`, and `--expand` options\n- **Plans Panel Re-enabled**: Plans panel restored in CLI dashboard with plan file discovery from `~/.claude/plans/`\n- **Enhanced Status Bar**: Session info display improved with richer metadata\n\n### Fixed\n\n- **Old snapshot format migration**: Restoring pre-0.12.3 session snapshots no longer shows empty timeline entries\n\n### Changed\n\n- **Phrase library moved to shared**: CLI-specific phrase formatting kept local, all phrase content now from `sidekick-shared`\n\n## [0.12.5] - 2026-02-24\n\n### Fixed\n\n- **Update check too slow to notice new versions**: Reduced npm registry cache TTL from 24 hours to 4 hours so upgrade notices appear sooner after a new release\n\n## [0.12.4] - 2026-02-24\n\n### Fixed\n\n- **Session crash on upgrade**: Fixed `d.timestamp.getTime is not a function` error when restoring tool call data from session snapshots \u2014 `Date` objects were serialized to strings by JSON but not rehydrated on restore, causing the session monitor to crash on first run after upgrading from 0.12.2 to 0.12.3\n\n## [0.12.3] - 2026-02-24\n\n### Added\n\n- **Latest-node indicator**: The most recently added node in tree and boxed mind map views is now marked with a yellow indicator\n- **Plan analytics in mind map**: Tree and boxed views now display plan progress and per-step metrics\n - Tree view: plan header shows completion stats; steps show complexity, duration, tokens, tool calls, and errors in metadata brackets\n - Box view: progress bar with completion percentage; steps show right-aligned metrics; subtitle shows step count and total duration\n- **Cross-provider plan extraction**: Shared `PlanExtractor` now handles Claude Code (EnterPlanMode/ExitPlanMode) and OpenCode (`<proposed_plan>` XML) plans \u2014 previously only Codex plans were shown\n- **Enriched plan data model**: Plan steps include duration, token count, tool call count, and error messages\n- **Phase-grouped plan display**: When a plan has phase structure, tree and boxed views group steps under phase headers with context lines from the original plan markdown\n- **Node type filter**: Press `f` on the Mind Map tab to cycle through node type filters (file, tool, task, subagent, command, plan, knowledge-note) \u2014 non-matching sections render dimmed in grey\n\n### Fixed\n\n- **Kanban board regression**: Subagent and plan-step tasks now correctly appear in the kanban board\n\n### Changed\n\n- **Plans panel temporarily disabled**: The Plans panel in the CLI dashboard is disabled until plan-mode event capture is reliably working end-to-end. Plan nodes in the mind map remain active.\n- `DashboardState` now delegates to shared `EventAggregator` instead of maintaining its own aggregation logic\n\n## [0.12.2] - 2026-02-23\n\n### Added\n\n- **Update notifications**: The dashboard now checks the npm registry for newer versions on startup and shows a yellow banner in the status bar when an update is available (e.g., `v0.13.0 available \u2014 npm i -g sidekick-agent-hub`). Results are cached for 24 hours to avoid repeated network requests.\n\n## [0.12.1] - 2026-02-23\n\n### Fixed\n\n- **VS Code integration**: Fixed exit code 127 when the extension launches the CLI dashboard on systems using nvm or volta (node binary not found when shell init is bypassed)\n\n## [0.12.0] - 2026-02-22\n\n### Added\n\n- **"Open CLI Dashboard" VS Code Integration**: New VS Code command `Sidekick: Open CLI Dashboard` launches the TUI dashboard in an integrated terminal\n - Install the CLI with `npm install -g sidekick-agent-hub`\n\n## [0.11.0] - 2026-02-19\n\n### Added\n\n- **Initial Release**: Full-screen TUI dashboard for monitoring agent sessions from the terminal\n - Ink-based terminal UI with panels for sessions, tasks, kanban, mind map, notes, decisions, search, files, and git diff\n - Multi-provider support: auto-detects Claude Code, OpenCode, and Codex sessions\n - Reads from `~/.config/sidekick/` \u2014 the same data files the VS Code extension writes\n - Usage: `sidekick dashboard [--project <path>] [--provider <id>]`\n';
|
|
80136
80997
|
}
|
|
80137
80998
|
});
|
|
80138
80999
|
|
|
@@ -82316,7 +83177,7 @@ async function quotaAction(_opts, cmd) {
|
|
|
82316
83177
|
return;
|
|
82317
83178
|
}
|
|
82318
83179
|
if (provider.id === "codex") {
|
|
82319
|
-
await codexQuotaAction(provider, globalOpts, jsonOutput);
|
|
83180
|
+
await codexQuotaAction(provider, globalOpts, localOpts, jsonOutput);
|
|
82320
83181
|
return;
|
|
82321
83182
|
}
|
|
82322
83183
|
provider.dispose();
|
|
@@ -82387,57 +83248,25 @@ function printPeakHoursSummary(peak) {
|
|
|
82387
83248
|
process.stdout.write(` ${source_default.dim("Peak")} ${line}
|
|
82388
83249
|
`);
|
|
82389
83250
|
}
|
|
82390
|
-
async function codexQuotaAction(provider, globalOpts, jsonOutput) {
|
|
83251
|
+
async function codexQuotaAction(provider, globalOpts, localOpts, jsonOutput) {
|
|
82391
83252
|
const workspacePath = globalOpts.project || process.cwd();
|
|
82392
83253
|
const activeAccount = (0, import_sidekick_shared29.getActiveCodexAccount)();
|
|
82393
|
-
let quota
|
|
82394
|
-
|
|
82395
|
-
|
|
82396
|
-
|
|
82397
|
-
|
|
82398
|
-
|
|
82399
|
-
|
|
82400
|
-
|
|
82401
|
-
|
|
82402
|
-
|
|
82403
|
-
if (event.rateLimits) {
|
|
82404
|
-
captured.rl = event.rateLimits;
|
|
82405
|
-
}
|
|
82406
|
-
},
|
|
82407
|
-
onError: () => {
|
|
82408
|
-
}
|
|
82409
|
-
}
|
|
82410
|
-
});
|
|
82411
|
-
result.watcher.start(true);
|
|
82412
|
-
result.watcher.stop();
|
|
82413
|
-
} catch {
|
|
82414
|
-
}
|
|
82415
|
-
const liveQuota = captured.rl ? (0, import_sidekick_shared29.quotaFromCodexRateLimits)({
|
|
82416
|
-
primary: captured.rl.primary ? {
|
|
82417
|
-
used_percent: captured.rl.primary.usedPercent,
|
|
82418
|
-
window_minutes: captured.rl.primary.windowMinutes,
|
|
82419
|
-
resets_at: captured.rl.primary.resetsAt
|
|
82420
|
-
} : void 0,
|
|
82421
|
-
secondary: captured.rl.secondary ? {
|
|
82422
|
-
used_percent: captured.rl.secondary.usedPercent,
|
|
82423
|
-
window_minutes: captured.rl.secondary.windowMinutes,
|
|
82424
|
-
resets_at: captured.rl.secondary.resetsAt
|
|
82425
|
-
} : void 0
|
|
82426
|
-
}, "session") : null;
|
|
82427
|
-
if (liveQuota) {
|
|
82428
|
-
quota = liveQuota;
|
|
82429
|
-
if (activeAccount) {
|
|
82430
|
-
(0, import_sidekick_shared29.writeQuotaSnapshot)("codex", activeAccount.id, liveQuota);
|
|
82431
|
-
}
|
|
82432
|
-
}
|
|
83254
|
+
let quota;
|
|
83255
|
+
try {
|
|
83256
|
+
quota = await (0, import_sidekick_shared29.resolveCodexQuota)({
|
|
83257
|
+
workspacePath,
|
|
83258
|
+
provider,
|
|
83259
|
+
activeAccount,
|
|
83260
|
+
source: localOpts.refresh ? "api" : "local"
|
|
83261
|
+
});
|
|
83262
|
+
} finally {
|
|
83263
|
+
provider.dispose();
|
|
82433
83264
|
}
|
|
82434
|
-
|
|
82435
|
-
if (!quota) {
|
|
82436
|
-
const msg = activeAccount ? `No active Codex session and no cached rate-limit snapshot is available for "${activeAccount.label ?? activeAccount.id}".` : "No active Codex session and no saved Codex account snapshot is available.";
|
|
83265
|
+
if (!quota.available) {
|
|
82437
83266
|
if (jsonOutput) {
|
|
82438
|
-
process.stdout.write(JSON.stringify(
|
|
83267
|
+
process.stdout.write(JSON.stringify(quota, null, 2) + "\n");
|
|
82439
83268
|
} else {
|
|
82440
|
-
process.stderr.write(source_default.yellow(
|
|
83269
|
+
process.stderr.write(source_default.yellow(quota.error ?? "Codex rate-limit data is unavailable.") + "\n");
|
|
82441
83270
|
}
|
|
82442
83271
|
return;
|
|
82443
83272
|
}
|
|
@@ -82483,6 +83312,183 @@ var init_quota = __esm({
|
|
|
82483
83312
|
}
|
|
82484
83313
|
});
|
|
82485
83314
|
|
|
83315
|
+
// src/commands/quotaHistory.ts
|
|
83316
|
+
var quotaHistory_exports = {};
|
|
83317
|
+
__export(quotaHistory_exports, {
|
|
83318
|
+
bucketForUtilization: () => bucketForUtilization,
|
|
83319
|
+
colorForBucket: () => colorForBucket,
|
|
83320
|
+
quotaHistoryAction: () => quotaHistoryAction,
|
|
83321
|
+
renderProviderHeatmap: () => renderProviderHeatmap
|
|
83322
|
+
});
|
|
83323
|
+
function bucketForUtilization(util) {
|
|
83324
|
+
if (util <= 0) return 0;
|
|
83325
|
+
if (util < 25) return 1;
|
|
83326
|
+
if (util < 50) return 2;
|
|
83327
|
+
if (util < 75) return 3;
|
|
83328
|
+
return 4;
|
|
83329
|
+
}
|
|
83330
|
+
function colorForBucket(bucket) {
|
|
83331
|
+
switch (bucket) {
|
|
83332
|
+
case 0:
|
|
83333
|
+
return source_default.dim;
|
|
83334
|
+
case 1:
|
|
83335
|
+
return source_default.green;
|
|
83336
|
+
case 2:
|
|
83337
|
+
return source_default.yellow;
|
|
83338
|
+
case 3:
|
|
83339
|
+
return source_default.hex("#ff8800");
|
|
83340
|
+
case 4:
|
|
83341
|
+
return source_default.red.bold;
|
|
83342
|
+
default:
|
|
83343
|
+
return source_default.white;
|
|
83344
|
+
}
|
|
83345
|
+
}
|
|
83346
|
+
function bucketsToCells(buckets) {
|
|
83347
|
+
return buckets.map((b) => ({
|
|
83348
|
+
date: b.date,
|
|
83349
|
+
utilization: Math.max(b.maxUtilizationFiveHour, b.maxUtilizationSevenDay),
|
|
83350
|
+
unavailable: b.anyUnavailable,
|
|
83351
|
+
samples: b.samples
|
|
83352
|
+
}));
|
|
83353
|
+
}
|
|
83354
|
+
function renderProviderHeatmap(label, cells, weeks) {
|
|
83355
|
+
const cols = weeks;
|
|
83356
|
+
const rows = 7;
|
|
83357
|
+
const totalCells = cols * rows;
|
|
83358
|
+
let firstDayOfWeek = 0;
|
|
83359
|
+
if (cells.length > 0) {
|
|
83360
|
+
firstDayOfWeek = (/* @__PURE__ */ new Date(`${cells[0].date}T00:00:00Z`)).getUTCDay();
|
|
83361
|
+
}
|
|
83362
|
+
const padded = [];
|
|
83363
|
+
for (let i = 0; i < firstDayOfWeek; i += 1) padded.push(null);
|
|
83364
|
+
for (const cell of cells) padded.push(cell);
|
|
83365
|
+
while (padded.length < totalCells) padded.push(null);
|
|
83366
|
+
while (padded.length > totalCells) padded.shift();
|
|
83367
|
+
const lines = [];
|
|
83368
|
+
for (let row = 0; row < rows; row += 1) {
|
|
83369
|
+
const dayLabel = source_default.dim(DAY_LABELS[row].padEnd(4));
|
|
83370
|
+
const glyphs = [];
|
|
83371
|
+
for (let col = 0; col < cols; col += 1) {
|
|
83372
|
+
const cell = padded[col * rows + row];
|
|
83373
|
+
if (!cell) {
|
|
83374
|
+
glyphs.push(source_default.dim(" "));
|
|
83375
|
+
continue;
|
|
83376
|
+
}
|
|
83377
|
+
if (cell.unavailable && cell.samples > 0) {
|
|
83378
|
+
glyphs.push(source_default.red("\xD7"));
|
|
83379
|
+
continue;
|
|
83380
|
+
}
|
|
83381
|
+
const bucket = bucketForUtilization(cell.utilization);
|
|
83382
|
+
glyphs.push(colorForBucket(bucket)(BUCKET_GLYPHS[bucket]));
|
|
83383
|
+
}
|
|
83384
|
+
lines.push(`${dayLabel}${glyphs.join("")}`);
|
|
83385
|
+
}
|
|
83386
|
+
let peak = 0;
|
|
83387
|
+
let sum = 0;
|
|
83388
|
+
let sampledDays = 0;
|
|
83389
|
+
let unavailableDays = 0;
|
|
83390
|
+
let totalSamples = 0;
|
|
83391
|
+
for (const cell of cells) {
|
|
83392
|
+
if (cell.samples === 0) continue;
|
|
83393
|
+
sampledDays += 1;
|
|
83394
|
+
totalSamples += cell.samples;
|
|
83395
|
+
if (cell.utilization > peak) peak = cell.utilization;
|
|
83396
|
+
sum += cell.utilization;
|
|
83397
|
+
if (cell.unavailable) unavailableDays += 1;
|
|
83398
|
+
}
|
|
83399
|
+
const avg = sampledDays > 0 ? Math.round(sum / sampledDays) : 0;
|
|
83400
|
+
const peakColor = colorForBucket(bucketForUtilization(peak));
|
|
83401
|
+
const avgColor = colorForBucket(bucketForUtilization(avg));
|
|
83402
|
+
const header = source_default.bold(label) + source_default.dim(` \xB7 ${weeks} weeks \xB7 ${sampledDays} day(s) with samples`);
|
|
83403
|
+
const footer = [
|
|
83404
|
+
`Peak ${peakColor(`${peak}%`)}`,
|
|
83405
|
+
`Avg ${avgColor(`${avg}%`)}`,
|
|
83406
|
+
unavailableDays > 0 ? source_default.red(`Unavailable ${unavailableDays} day(s)`) : null,
|
|
83407
|
+
source_default.dim(`Samples ${totalSamples.toLocaleString()}`)
|
|
83408
|
+
].filter(Boolean).join(" \xB7 ");
|
|
83409
|
+
return [header, ...lines, footer].join("\n");
|
|
83410
|
+
}
|
|
83411
|
+
function legend() {
|
|
83412
|
+
const swatches = BUCKET_GLYPHS.map((g, i) => colorForBucket(i)(g)).join("");
|
|
83413
|
+
return source_default.dim("Less ") + swatches + source_default.dim(" More");
|
|
83414
|
+
}
|
|
83415
|
+
function parseProviderFilter(value) {
|
|
83416
|
+
if (typeof value !== "string") return "auto";
|
|
83417
|
+
const lower = value.toLowerCase();
|
|
83418
|
+
if (lower === "claude" || lower === "claude-code") return "claude";
|
|
83419
|
+
if (lower === "codex") return "codex";
|
|
83420
|
+
return "auto";
|
|
83421
|
+
}
|
|
83422
|
+
function clampWeeks(value) {
|
|
83423
|
+
const parsed = typeof value === "string" ? Number.parseInt(value, 10) : typeof value === "number" ? value : 13;
|
|
83424
|
+
if (!Number.isFinite(parsed)) return 13;
|
|
83425
|
+
return Math.max(1, Math.min(26, parsed));
|
|
83426
|
+
}
|
|
83427
|
+
async function quotaHistoryAction(_localOpts, cmd) {
|
|
83428
|
+
const globalOpts = cmd.parent?.parent?.opts() ?? cmd.parent?.opts() ?? {};
|
|
83429
|
+
const localOpts = cmd.opts();
|
|
83430
|
+
const jsonOutput = !!globalOpts.json;
|
|
83431
|
+
const weeks = clampWeeks(localOpts.weeks);
|
|
83432
|
+
const providerFilter = parseProviderFilter(localOpts.provider ?? globalOpts.provider);
|
|
83433
|
+
const workspacePath = typeof localOpts.workspace === "string" && localOpts.workspace ? localOpts.workspace : process.cwd();
|
|
83434
|
+
const workspaceId = (0, import_sidekick_shared30.getWorkspaceIdFromPath)(workspacePath);
|
|
83435
|
+
const toMs = Date.now();
|
|
83436
|
+
const fromMs = toMs - (weeks * 7 - 1) * MS_PER_DAY;
|
|
83437
|
+
const from = new Date(fromMs).toISOString();
|
|
83438
|
+
const to = new Date(toMs).toISOString();
|
|
83439
|
+
const wanted = providerFilter === "auto" ? ["claude", "codex"] : [providerFilter];
|
|
83440
|
+
const providerCells = {};
|
|
83441
|
+
for (const provider of wanted) {
|
|
83442
|
+
const buckets = await (0, import_sidekick_shared30.readQuotaHistoryDailyBuckets)({ workspaceId, provider, from, to });
|
|
83443
|
+
providerCells[provider] = bucketsToCells(buckets);
|
|
83444
|
+
}
|
|
83445
|
+
const payload = {
|
|
83446
|
+
workspaceId,
|
|
83447
|
+
weeks,
|
|
83448
|
+
providers: {
|
|
83449
|
+
...providerCells.claude ? { claude: { cells: providerCells.claude } } : {},
|
|
83450
|
+
...providerCells.codex ? { codex: { cells: providerCells.codex } } : {}
|
|
83451
|
+
},
|
|
83452
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
83453
|
+
};
|
|
83454
|
+
if (jsonOutput) {
|
|
83455
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
83456
|
+
return;
|
|
83457
|
+
}
|
|
83458
|
+
const sections = [];
|
|
83459
|
+
const claudeHasData = (payload.providers.claude?.cells ?? []).some((c) => c.samples > 0);
|
|
83460
|
+
const codexHasData = (payload.providers.codex?.cells ?? []).some((c) => c.samples > 0);
|
|
83461
|
+
if (providerFilter === "auto" && !claudeHasData && !codexHasData) {
|
|
83462
|
+
process.stdout.write(
|
|
83463
|
+
source_default.yellow(`No quota history yet for workspace ${source_default.bold(workspaceId)}.`) + "\n" + source_default.dim(`Run a Claude Max or Codex session in this workspace, or override with --workspace <path>.`) + "\n"
|
|
83464
|
+
);
|
|
83465
|
+
return;
|
|
83466
|
+
}
|
|
83467
|
+
if (payload.providers.claude && (providerFilter !== "auto" || claudeHasData)) {
|
|
83468
|
+
sections.push(renderProviderHeatmap("Claude", payload.providers.claude.cells, weeks));
|
|
83469
|
+
}
|
|
83470
|
+
if (payload.providers.codex && (providerFilter !== "auto" || codexHasData)) {
|
|
83471
|
+
sections.push(renderProviderHeatmap("Codex", payload.providers.codex.cells, weeks));
|
|
83472
|
+
}
|
|
83473
|
+
const header = source_default.dim(`workspace ${workspaceId} \xB7 ${from.slice(0, 10)} \u2192 ${to.slice(0, 10)}`);
|
|
83474
|
+
process.stdout.write(`${header}
|
|
83475
|
+
${legend()}
|
|
83476
|
+
|
|
83477
|
+
${sections.join("\n\n")}
|
|
83478
|
+
`);
|
|
83479
|
+
}
|
|
83480
|
+
var import_sidekick_shared30, MS_PER_DAY, BUCKET_GLYPHS, DAY_LABELS;
|
|
83481
|
+
var init_quotaHistory = __esm({
|
|
83482
|
+
"src/commands/quotaHistory.ts"() {
|
|
83483
|
+
"use strict";
|
|
83484
|
+
init_source();
|
|
83485
|
+
import_sidekick_shared30 = __toESM(require_dist(), 1);
|
|
83486
|
+
MS_PER_DAY = 864e5;
|
|
83487
|
+
BUCKET_GLYPHS = ["\xB7", "\u2591", "\u2592", "\u2593", "\u2588"];
|
|
83488
|
+
DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
83489
|
+
}
|
|
83490
|
+
});
|
|
83491
|
+
|
|
82486
83492
|
// src/commands/status.ts
|
|
82487
83493
|
var status_exports = {};
|
|
82488
83494
|
__export(status_exports, {
|
|
@@ -82529,9 +83535,9 @@ async function statusAction(_opts, cmd) {
|
|
|
82529
83535
|
const providerId = resolveProviderId(globalOpts);
|
|
82530
83536
|
const wantsPeak = providerId === "claude-code";
|
|
82531
83537
|
const [claude, openai, peak] = await Promise.all([
|
|
82532
|
-
(0,
|
|
82533
|
-
(0,
|
|
82534
|
-
wantsPeak ? (0,
|
|
83538
|
+
(0, import_sidekick_shared31.fetchProviderStatus)(),
|
|
83539
|
+
(0, import_sidekick_shared31.fetchOpenAIStatus)(),
|
|
83540
|
+
wantsPeak ? (0, import_sidekick_shared31.fetchPeakHoursStatus)() : Promise.resolve(null)
|
|
82535
83541
|
]);
|
|
82536
83542
|
if (jsonOutput) {
|
|
82537
83543
|
process.stdout.write(JSON.stringify({ claude, openai, peak }, null, 2) + "\n");
|
|
@@ -82545,12 +83551,12 @@ async function statusAction(_opts, cmd) {
|
|
|
82545
83551
|
printPeakHoursBlock(peak);
|
|
82546
83552
|
}
|
|
82547
83553
|
}
|
|
82548
|
-
var
|
|
83554
|
+
var import_sidekick_shared31;
|
|
82549
83555
|
var init_status = __esm({
|
|
82550
83556
|
"src/commands/status.ts"() {
|
|
82551
83557
|
"use strict";
|
|
82552
83558
|
init_source();
|
|
82553
|
-
|
|
83559
|
+
import_sidekick_shared31 = __toESM(require_dist(), 1);
|
|
82554
83560
|
init_peakHoursRender();
|
|
82555
83561
|
init_cli();
|
|
82556
83562
|
}
|
|
@@ -82564,18 +83570,18 @@ __export(peak_exports, {
|
|
|
82564
83570
|
async function peakAction(_opts, cmd) {
|
|
82565
83571
|
const globalOpts = cmd.parent.opts();
|
|
82566
83572
|
const jsonOutput = !!globalOpts.json;
|
|
82567
|
-
const state = await (0,
|
|
83573
|
+
const state = await (0, import_sidekick_shared32.fetchPeakHoursStatus)();
|
|
82568
83574
|
if (jsonOutput) {
|
|
82569
83575
|
process.stdout.write(JSON.stringify(state, null, 2) + "\n");
|
|
82570
83576
|
return;
|
|
82571
83577
|
}
|
|
82572
83578
|
printPeakHoursBlock(state);
|
|
82573
83579
|
}
|
|
82574
|
-
var
|
|
83580
|
+
var import_sidekick_shared32;
|
|
82575
83581
|
var init_peak = __esm({
|
|
82576
83582
|
"src/commands/peak.ts"() {
|
|
82577
83583
|
"use strict";
|
|
82578
|
-
|
|
83584
|
+
import_sidekick_shared32 = __toESM(require_dist(), 1);
|
|
82579
83585
|
init_peakHoursRender();
|
|
82580
83586
|
}
|
|
82581
83587
|
});
|
|
@@ -82607,13 +83613,13 @@ async function accountAction(_opts, cmd) {
|
|
|
82607
83613
|
}
|
|
82608
83614
|
function claudeAccountAction(opts, jsonOutput) {
|
|
82609
83615
|
if (opts.add) {
|
|
82610
|
-
const result = (0,
|
|
83616
|
+
const result = (0, import_sidekick_shared33.addCurrentAccount)(opts.label);
|
|
82611
83617
|
if (!result.success) {
|
|
82612
83618
|
process.stderr.write(source_default.red(result.error ?? "Failed to save account.") + "\n");
|
|
82613
83619
|
process.exit(1);
|
|
82614
83620
|
return;
|
|
82615
83621
|
}
|
|
82616
|
-
const active2 = (0,
|
|
83622
|
+
const active2 = (0, import_sidekick_shared33.readActiveClaudeAccount)();
|
|
82617
83623
|
if (jsonOutput) {
|
|
82618
83624
|
process.stdout.write(JSON.stringify({ action: "added", provider: "claude-code", email: active2?.email, label: opts.label }) + "\n");
|
|
82619
83625
|
} else {
|
|
@@ -82622,14 +83628,14 @@ function claudeAccountAction(opts, jsonOutput) {
|
|
|
82622
83628
|
return;
|
|
82623
83629
|
}
|
|
82624
83630
|
if (opts.remove) {
|
|
82625
|
-
const accounts2 = (0,
|
|
83631
|
+
const accounts2 = (0, import_sidekick_shared33.listAccounts)();
|
|
82626
83632
|
const target = findClaudeAccount(opts.remove, accounts2);
|
|
82627
83633
|
if (!target) {
|
|
82628
83634
|
process.stderr.write(source_default.red(`Account "${opts.remove}" not found.`) + "\n");
|
|
82629
83635
|
process.exit(1);
|
|
82630
83636
|
return;
|
|
82631
83637
|
}
|
|
82632
|
-
const result = (0,
|
|
83638
|
+
const result = (0, import_sidekick_shared33.removeAccount)(target.uuid);
|
|
82633
83639
|
if (!result.success) {
|
|
82634
83640
|
process.stderr.write(source_default.red(result.error ?? "Failed to remove account.") + "\n");
|
|
82635
83641
|
process.exit(1);
|
|
@@ -82643,14 +83649,14 @@ function claudeAccountAction(opts, jsonOutput) {
|
|
|
82643
83649
|
return;
|
|
82644
83650
|
}
|
|
82645
83651
|
if (opts.switchTo) {
|
|
82646
|
-
const accounts2 = (0,
|
|
83652
|
+
const accounts2 = (0, import_sidekick_shared33.listAccounts)();
|
|
82647
83653
|
const target = findClaudeAccount(opts.switchTo, accounts2);
|
|
82648
83654
|
if (!target) {
|
|
82649
83655
|
process.stderr.write(source_default.red(`Account "${opts.switchTo}" not found.`) + "\n");
|
|
82650
83656
|
process.exit(1);
|
|
82651
83657
|
return;
|
|
82652
83658
|
}
|
|
82653
|
-
const result = (0,
|
|
83659
|
+
const result = (0, import_sidekick_shared33.switchToAccount)(target.uuid);
|
|
82654
83660
|
if (!result.success) {
|
|
82655
83661
|
process.stderr.write(source_default.red(result.error ?? "Failed to switch.") + "\n");
|
|
82656
83662
|
process.exit(1);
|
|
@@ -82664,17 +83670,17 @@ function claudeAccountAction(opts, jsonOutput) {
|
|
|
82664
83670
|
return;
|
|
82665
83671
|
}
|
|
82666
83672
|
if (opts.switch) {
|
|
82667
|
-
const accounts2 = (0,
|
|
83673
|
+
const accounts2 = (0, import_sidekick_shared33.listAccounts)();
|
|
82668
83674
|
if (accounts2.length < 2) {
|
|
82669
83675
|
process.stderr.write(source_default.yellow("Need at least 2 saved accounts to switch.") + "\n");
|
|
82670
83676
|
process.exit(1);
|
|
82671
83677
|
return;
|
|
82672
83678
|
}
|
|
82673
|
-
const active2 = (0,
|
|
83679
|
+
const active2 = (0, import_sidekick_shared33.getActiveAccount)();
|
|
82674
83680
|
const currentIdx = active2 ? accounts2.findIndex((a) => a.uuid === active2.uuid) : -1;
|
|
82675
83681
|
const nextIdx = (currentIdx + 1) % accounts2.length;
|
|
82676
83682
|
const target = accounts2[nextIdx];
|
|
82677
|
-
const result = (0,
|
|
83683
|
+
const result = (0, import_sidekick_shared33.switchToAccount)(target.uuid);
|
|
82678
83684
|
if (!result.success) {
|
|
82679
83685
|
process.stderr.write(source_default.red(result.error ?? "Failed to switch.") + "\n");
|
|
82680
83686
|
process.exit(1);
|
|
@@ -82687,9 +83693,9 @@ function claudeAccountAction(opts, jsonOutput) {
|
|
|
82687
83693
|
}
|
|
82688
83694
|
return;
|
|
82689
83695
|
}
|
|
82690
|
-
const accounts = (0,
|
|
83696
|
+
const accounts = (0, import_sidekick_shared33.listAccounts)();
|
|
82691
83697
|
if (accounts.length === 0) {
|
|
82692
|
-
const current = (0,
|
|
83698
|
+
const current = (0, import_sidekick_shared33.readActiveClaudeAccount)();
|
|
82693
83699
|
if (jsonOutput) {
|
|
82694
83700
|
process.stdout.write(JSON.stringify({ provider: "claude-code", accounts: [], current: current ?? null }) + "\n");
|
|
82695
83701
|
} else if (current) {
|
|
@@ -82701,14 +83707,14 @@ function claudeAccountAction(opts, jsonOutput) {
|
|
|
82701
83707
|
return;
|
|
82702
83708
|
}
|
|
82703
83709
|
if (jsonOutput) {
|
|
82704
|
-
const active2 = (0,
|
|
83710
|
+
const active2 = (0, import_sidekick_shared33.getActiveAccount)();
|
|
82705
83711
|
process.stdout.write(JSON.stringify({
|
|
82706
83712
|
provider: "claude-code",
|
|
82707
83713
|
accounts: accounts.map((a) => ({ ...a, active: a.uuid === active2?.uuid }))
|
|
82708
83714
|
}, null, 2) + "\n");
|
|
82709
83715
|
return;
|
|
82710
83716
|
}
|
|
82711
|
-
const active = (0,
|
|
83717
|
+
const active = (0, import_sidekick_shared33.getActiveAccount)();
|
|
82712
83718
|
process.stdout.write(source_default.bold("Claude Accounts\n"));
|
|
82713
83719
|
process.stdout.write(source_default.dim("\u2500".repeat(50) + "\n"));
|
|
82714
83720
|
for (const account of accounts) {
|
|
@@ -82755,7 +83761,7 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82755
83761
|
process.exit(1);
|
|
82756
83762
|
return;
|
|
82757
83763
|
}
|
|
82758
|
-
const prepared = (0,
|
|
83764
|
+
const prepared = (0, import_sidekick_shared33.prepareCodexAccount)(opts.label);
|
|
82759
83765
|
if (!prepared.success) {
|
|
82760
83766
|
process.stderr.write(source_default.red(prepared.error ?? "Failed to prepare Codex account.") + "\n");
|
|
82761
83767
|
process.exit(1);
|
|
@@ -82773,14 +83779,14 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82773
83779
|
process.exit(1);
|
|
82774
83780
|
return;
|
|
82775
83781
|
}
|
|
82776
|
-
const finalized = (0,
|
|
83782
|
+
const finalized = (0, import_sidekick_shared33.finalizeCodexAccount)(prepared.profileId);
|
|
82777
83783
|
if (!finalized.success) {
|
|
82778
83784
|
process.stderr.write(source_default.red(finalized.error ?? "Failed to finalize Codex account.") + "\n");
|
|
82779
83785
|
process.exit(1);
|
|
82780
83786
|
return;
|
|
82781
83787
|
}
|
|
82782
83788
|
}
|
|
82783
|
-
const active2 = (0,
|
|
83789
|
+
const active2 = (0, import_sidekick_shared33.getActiveCodexAccount)();
|
|
82784
83790
|
if (jsonOutput) {
|
|
82785
83791
|
process.stdout.write(JSON.stringify({
|
|
82786
83792
|
action: "added",
|
|
@@ -82796,14 +83802,14 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82796
83802
|
return;
|
|
82797
83803
|
}
|
|
82798
83804
|
if (opts.remove) {
|
|
82799
|
-
const accounts2 = (0,
|
|
83805
|
+
const accounts2 = (0, import_sidekick_shared33.listCodexAccounts)();
|
|
82800
83806
|
const target = findCodexAccount(opts.remove, accounts2);
|
|
82801
83807
|
if (!target) {
|
|
82802
83808
|
process.stderr.write(source_default.red(`Codex account "${opts.remove}" not found.`) + "\n");
|
|
82803
83809
|
process.exit(1);
|
|
82804
83810
|
return;
|
|
82805
83811
|
}
|
|
82806
|
-
const result = (0,
|
|
83812
|
+
const result = (0, import_sidekick_shared33.removeCodexAccount)(target.id);
|
|
82807
83813
|
if (!result.success) {
|
|
82808
83814
|
process.stderr.write(source_default.red(result.error ?? "Failed to remove Codex account.") + "\n");
|
|
82809
83815
|
process.exit(1);
|
|
@@ -82817,14 +83823,14 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82817
83823
|
return;
|
|
82818
83824
|
}
|
|
82819
83825
|
if (opts.switchTo) {
|
|
82820
|
-
const accounts2 = (0,
|
|
83826
|
+
const accounts2 = (0, import_sidekick_shared33.listCodexAccounts)();
|
|
82821
83827
|
const target = findCodexAccount(opts.switchTo, accounts2);
|
|
82822
83828
|
if (!target) {
|
|
82823
83829
|
process.stderr.write(source_default.red(`Codex account "${opts.switchTo}" not found.`) + "\n");
|
|
82824
83830
|
process.exit(1);
|
|
82825
83831
|
return;
|
|
82826
83832
|
}
|
|
82827
|
-
const result = (0,
|
|
83833
|
+
const result = (0, import_sidekick_shared33.switchToCodexAccount)(target.id);
|
|
82828
83834
|
if (!result.success) {
|
|
82829
83835
|
process.stderr.write(source_default.red(result.error ?? "Failed to switch Codex account.") + "\n");
|
|
82830
83836
|
process.exit(1);
|
|
@@ -82838,17 +83844,17 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82838
83844
|
return;
|
|
82839
83845
|
}
|
|
82840
83846
|
if (opts.switch) {
|
|
82841
|
-
const accounts2 = (0,
|
|
83847
|
+
const accounts2 = (0, import_sidekick_shared33.listCodexAccounts)();
|
|
82842
83848
|
if (accounts2.length < 2) {
|
|
82843
83849
|
process.stderr.write(source_default.yellow("Need at least 2 saved Codex accounts to switch.") + "\n");
|
|
82844
83850
|
process.exit(1);
|
|
82845
83851
|
return;
|
|
82846
83852
|
}
|
|
82847
|
-
const active2 = (0,
|
|
83853
|
+
const active2 = (0, import_sidekick_shared33.getActiveCodexAccount)();
|
|
82848
83854
|
const currentIdx = active2 ? accounts2.findIndex((account) => account.id === active2.id) : -1;
|
|
82849
83855
|
const nextIdx = (currentIdx + 1) % accounts2.length;
|
|
82850
83856
|
const target = accounts2[nextIdx];
|
|
82851
|
-
const result = (0,
|
|
83857
|
+
const result = (0, import_sidekick_shared33.switchToCodexAccount)(target.id);
|
|
82852
83858
|
if (!result.success) {
|
|
82853
83859
|
process.stderr.write(source_default.red(result.error ?? "Failed to switch Codex account.") + "\n");
|
|
82854
83860
|
process.exit(1);
|
|
@@ -82861,7 +83867,7 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82861
83867
|
}
|
|
82862
83868
|
return;
|
|
82863
83869
|
}
|
|
82864
|
-
const accounts = (0,
|
|
83870
|
+
const accounts = (0, import_sidekick_shared33.listCodexAccounts)();
|
|
82865
83871
|
if (accounts.length === 0) {
|
|
82866
83872
|
if (jsonOutput) {
|
|
82867
83873
|
process.stdout.write(JSON.stringify({ provider: "codex", accounts: [], current: null }) + "\n");
|
|
@@ -82871,7 +83877,7 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82871
83877
|
return;
|
|
82872
83878
|
}
|
|
82873
83879
|
if (jsonOutput) {
|
|
82874
|
-
const active2 = (0,
|
|
83880
|
+
const active2 = (0, import_sidekick_shared33.getActiveCodexAccount)();
|
|
82875
83881
|
process.stdout.write(JSON.stringify({
|
|
82876
83882
|
provider: "codex",
|
|
82877
83883
|
accounts: accounts.map((account) => ({
|
|
@@ -82881,7 +83887,7 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82881
83887
|
}, null, 2) + "\n");
|
|
82882
83888
|
return;
|
|
82883
83889
|
}
|
|
82884
|
-
const active = (0,
|
|
83890
|
+
const active = (0, import_sidekick_shared33.getActiveCodexAccount)();
|
|
82885
83891
|
process.stdout.write(source_default.bold("Codex Accounts\n"));
|
|
82886
83892
|
process.stdout.write(source_default.dim("\u2500".repeat(50) + "\n"));
|
|
82887
83893
|
for (const account of accounts) {
|
|
@@ -82892,12 +83898,12 @@ function codexAccountAction(opts, jsonOutput) {
|
|
|
82892
83898
|
`);
|
|
82893
83899
|
}
|
|
82894
83900
|
}
|
|
82895
|
-
var
|
|
83901
|
+
var import_sidekick_shared33;
|
|
82896
83902
|
var init_account = __esm({
|
|
82897
83903
|
"src/commands/account.ts"() {
|
|
82898
83904
|
"use strict";
|
|
82899
83905
|
init_source();
|
|
82900
|
-
|
|
83906
|
+
import_sidekick_shared33 = __toESM(require_dist(), 1);
|
|
82901
83907
|
init_cli();
|
|
82902
83908
|
}
|
|
82903
83909
|
});
|
|
@@ -82912,12 +83918,12 @@ async function handoffAction(_opts, cmd) {
|
|
|
82912
83918
|
const workspacePath = globalOpts.project || process.cwd();
|
|
82913
83919
|
const jsonOutput = !!globalOpts.json;
|
|
82914
83920
|
try {
|
|
82915
|
-
const rawSlug = (0,
|
|
82916
|
-
const resolvedSlug = (0,
|
|
83921
|
+
const rawSlug = (0, import_sidekick_shared34.getProjectSlugRaw)(workspacePath);
|
|
83922
|
+
const resolvedSlug = (0, import_sidekick_shared34.getProjectSlug)(workspacePath);
|
|
82917
83923
|
const slugs = rawSlug !== resolvedSlug ? [rawSlug, resolvedSlug] : [rawSlug];
|
|
82918
83924
|
let content = null;
|
|
82919
83925
|
for (const slug of slugs) {
|
|
82920
|
-
content = await (0,
|
|
83926
|
+
content = await (0, import_sidekick_shared34.readLatestHandoff)(slug);
|
|
82921
83927
|
if (content) break;
|
|
82922
83928
|
}
|
|
82923
83929
|
if (!content) {
|
|
@@ -82942,12 +83948,12 @@ async function handoffAction(_opts, cmd) {
|
|
|
82942
83948
|
process.exit(1);
|
|
82943
83949
|
}
|
|
82944
83950
|
}
|
|
82945
|
-
var
|
|
83951
|
+
var import_sidekick_shared34;
|
|
82946
83952
|
var init_handoff = __esm({
|
|
82947
83953
|
"src/commands/handoff.ts"() {
|
|
82948
83954
|
"use strict";
|
|
82949
83955
|
init_source();
|
|
82950
|
-
|
|
83956
|
+
import_sidekick_shared34 = __toESM(require_dist(), 1);
|
|
82951
83957
|
}
|
|
82952
83958
|
});
|
|
82953
83959
|
|
|
@@ -82961,35 +83967,35 @@ function resolveProviderId(opts, defaultProvider = "auto") {
|
|
|
82961
83967
|
if (defaultProvider !== "auto") {
|
|
82962
83968
|
return defaultProvider;
|
|
82963
83969
|
}
|
|
82964
|
-
return (0,
|
|
83970
|
+
return (0, import_sidekick_shared35.detectProvider)();
|
|
82965
83971
|
}
|
|
82966
83972
|
function resolveProvider(opts) {
|
|
82967
83973
|
const id = resolveProviderId(opts);
|
|
82968
83974
|
switch (id) {
|
|
82969
83975
|
case "opencode":
|
|
82970
|
-
return new
|
|
83976
|
+
return new import_sidekick_shared36.OpenCodeProvider();
|
|
82971
83977
|
case "codex":
|
|
82972
|
-
return new
|
|
83978
|
+
return new import_sidekick_shared36.CodexProvider();
|
|
82973
83979
|
case "claude-code":
|
|
82974
83980
|
default:
|
|
82975
|
-
return new
|
|
83981
|
+
return new import_sidekick_shared36.ClaudeCodeProvider();
|
|
82976
83982
|
}
|
|
82977
83983
|
}
|
|
82978
|
-
var
|
|
83984
|
+
var import_sidekick_shared35, import_node, import_sidekick_shared36, defaultAccountsReady, program2, dashCmd, dumpCmd, ctxCmd, reportCmd, searchCmd, tasksCmd, decisionsCmd, notesCmd, statsCmd, quotaCmd, statusCmd, peakCmd, accountCmd, handoffCmd;
|
|
82979
83985
|
var init_cli = __esm({
|
|
82980
83986
|
"src/cli.ts"() {
|
|
82981
83987
|
init_esm();
|
|
82982
|
-
import_sidekick_shared34 = __toESM(require_dist(), 1);
|
|
82983
|
-
import_node = __toESM(require_node(), 1);
|
|
82984
83988
|
import_sidekick_shared35 = __toESM(require_dist(), 1);
|
|
83989
|
+
import_node = __toESM(require_node(), 1);
|
|
83990
|
+
import_sidekick_shared36 = __toESM(require_dist(), 1);
|
|
82985
83991
|
(0, import_node.hydratePricingCatalog)({
|
|
82986
83992
|
cacheDir: path7.join(os5.homedir(), ".config", "sidekick")
|
|
82987
83993
|
}).catch(() => {
|
|
82988
83994
|
});
|
|
82989
|
-
defaultAccountsReady = (0,
|
|
83995
|
+
defaultAccountsReady = (0, import_sidekick_shared35.ensureDefaultAccounts)().catch(() => {
|
|
82990
83996
|
});
|
|
82991
83997
|
program2 = new Command();
|
|
82992
|
-
program2.name("sidekick").description("Query Sidekick project intelligence from the command line").version("0.18.
|
|
83998
|
+
program2.name("sidekick").description("Query Sidekick project intelligence from the command line").version("0.18.3").option("--json", "Output as JSON").option("--project <path>", "Override project path (default: cwd)").option("--provider <id>", "Provider: claude-code, opencode, codex, auto (default: auto)");
|
|
82993
83999
|
program2.hook("preAction", async () => {
|
|
82994
84000
|
await defaultAccountsReady;
|
|
82995
84001
|
});
|
|
@@ -83039,10 +84045,14 @@ var init_cli = __esm({
|
|
|
83039
84045
|
return statsAction2(_opts, cmd);
|
|
83040
84046
|
});
|
|
83041
84047
|
program2.addCommand(statsCmd);
|
|
83042
|
-
quotaCmd = new Command("quota").description("Show quota or rate-limit utilization (auto-detects provider)").option("--provider <id>", "Provider: claude-code, codex, auto (default: auto)").action(async (_opts, cmd) => {
|
|
84048
|
+
quotaCmd = new Command("quota").description("Show quota or rate-limit utilization (auto-detects provider)").option("--provider <id>", "Provider: claude-code, codex, auto (default: auto)").option("--refresh", "For Codex, explicitly refresh from the Codex usage API before falling back to local data").action(async (_opts, cmd) => {
|
|
83043
84049
|
const { quotaAction: quotaAction2 } = await Promise.resolve().then(() => (init_quota(), quota_exports));
|
|
83044
84050
|
return quotaAction2(_opts, cmd);
|
|
83045
84051
|
});
|
|
84052
|
+
quotaCmd.command("history").description("Render a 13-week heatmap of quota utilization for the current workspace").option("--weeks <n>", "Weeks of history to render (default: 13, clamped 1-26)", "13").option("--provider <id>", "Limit to a single runtime provider: claude or codex (default: both)").option("--workspace <path>", "Workspace path used to derive the history scope (default: cwd)").action(async (_opts, cmd) => {
|
|
84053
|
+
const { quotaHistoryAction: quotaHistoryAction2 } = await Promise.resolve().then(() => (init_quotaHistory(), quotaHistory_exports));
|
|
84054
|
+
return quotaHistoryAction2(_opts, cmd);
|
|
84055
|
+
});
|
|
83046
84056
|
program2.addCommand(quotaCmd);
|
|
83047
84057
|
statusCmd = new Command("status").description("Show API status (Claude and OpenAI)").action(async (_opts, cmd) => {
|
|
83048
84058
|
const { statusAction: statusAction2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|