@cmmd-center/forge 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs
CHANGED
|
@@ -65224,7 +65224,7 @@ function normalizeNumberish(value) {
|
|
|
65224
65224
|
}
|
|
65225
65225
|
//#endregion
|
|
65226
65226
|
//#region package.json
|
|
65227
|
-
var version$1 = "0.10.
|
|
65227
|
+
var version$1 = "0.10.1";
|
|
65228
65228
|
//#endregion
|
|
65229
65229
|
//#region src/sentry.ts
|
|
65230
65230
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -68388,6 +68388,13 @@ const resolveBaseDir = require_Schema$1.fn(function* (raw) {
|
|
|
68388
68388
|
if (!raw || raw.trim().length === 0) return join(node_os.homedir(), ".forge");
|
|
68389
68389
|
return resolve(yield* expandHomePath$1(raw.trim()));
|
|
68390
68390
|
});
|
|
68391
|
+
function bunHttpServerOptions(config) {
|
|
68392
|
+
return {
|
|
68393
|
+
port: config.port,
|
|
68394
|
+
...config.host ? { hostname: config.host } : {},
|
|
68395
|
+
idleTimeout: 120
|
|
68396
|
+
};
|
|
68397
|
+
}
|
|
68391
68398
|
//#endregion
|
|
68392
68399
|
//#region ../../packages/shared/src/loopback.ts
|
|
68393
68400
|
const LOOPBACK_HOSTNAMES$1 = new Set([
|
|
@@ -97911,6 +97918,31 @@ function mapClaudeRateLimitInfo(info) {
|
|
|
97911
97918
|
}
|
|
97912
97919
|
};
|
|
97913
97920
|
}
|
|
97921
|
+
const SEVEN_DAY_MODEL_PREFIX = "seven_day_";
|
|
97922
|
+
const SEVEN_DAY_OVERAGE_SUFFIX = "overage_included";
|
|
97923
|
+
function capitalizeModelSuffix(suffix) {
|
|
97924
|
+
return suffix.length === 0 ? suffix : suffix.charAt(0).toUpperCase() + suffix.slice(1);
|
|
97925
|
+
}
|
|
97926
|
+
/**
|
|
97927
|
+
* Maps a per-model weekly SDKRateLimitInfo (`seven_day_<model>`, e.g.
|
|
97928
|
+
* `seven_day_opus`) into a `ProviderUsageModelWindow`. `seven_day_overage_included`
|
|
97929
|
+
* is not a per-model window (it reports overage-inclusive quota, not a
|
|
97930
|
+
* specific model) and stays ignored here, same as plain `overage`.
|
|
97931
|
+
*/
|
|
97932
|
+
function mapClaudeModelRateLimitInfo(info) {
|
|
97933
|
+
if (typeof info.utilization !== "number" || typeof info.resetsAt !== "number" || !info.status) return null;
|
|
97934
|
+
if (!info.rateLimitType?.startsWith(SEVEN_DAY_MODEL_PREFIX)) return null;
|
|
97935
|
+
const suffix = info.rateLimitType.slice(10);
|
|
97936
|
+
if (suffix.length === 0 || suffix === SEVEN_DAY_OVERAGE_SUFFIX) return null;
|
|
97937
|
+
return {
|
|
97938
|
+
model: capitalizeModelSuffix(suffix),
|
|
97939
|
+
window: {
|
|
97940
|
+
usedPercent: Math.round(info.utilization * 100),
|
|
97941
|
+
resetsAt: (/* @__PURE__ */ new Date(info.resetsAt * 1e3)).toISOString(),
|
|
97942
|
+
status: info.status
|
|
97943
|
+
}
|
|
97944
|
+
};
|
|
97945
|
+
}
|
|
97914
97946
|
const cachedSnapshots = /* @__PURE__ */ new Map();
|
|
97915
97947
|
let localClaudeGenerationHash = null;
|
|
97916
97948
|
function setLocalClaudeUsageGeneration(generationId) {
|
|
@@ -97920,22 +97952,71 @@ function currentClaudeUsageGenerationHash() {
|
|
|
97920
97952
|
const value = process.env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
97921
97953
|
return /^[a-f0-9]{64}$/.test(value) ? value : localClaudeGenerationHash;
|
|
97922
97954
|
}
|
|
97923
|
-
|
|
97924
|
-
|
|
97925
|
-
|
|
97926
|
-
|
|
97927
|
-
|
|
97928
|
-
|
|
97929
|
-
|
|
97930
|
-
|
|
97931
|
-
|
|
97932
|
-
|
|
97933
|
-
|
|
97934
|
-
|
|
97955
|
+
function cacheKeyFor(generationHash, providerAccountId) {
|
|
97956
|
+
return providerAccountId ? `${providerAccountId}:${generationHash}` : generationHash;
|
|
97957
|
+
}
|
|
97958
|
+
/**
|
|
97959
|
+
* `observedAt` here is a placeholder, not an observation — it becomes
|
|
97960
|
+
* meaningful only once a standard (five_hour/seven_day) window actually
|
|
97961
|
+
* lands via `withStandardWindow`, which recomputes it from the retained
|
|
97962
|
+
* windows' own observation times. A per-model-only cache (no standard window
|
|
97963
|
+
* yet) still carries this placeholder, so callers should treat `observedAt`
|
|
97964
|
+
* as trustworthy only once `fiveHour` or `sevenDay` is non-null.
|
|
97965
|
+
*/
|
|
97966
|
+
function emptyCachedSnapshot(observedAt) {
|
|
97967
|
+
return {
|
|
97968
|
+
fiveHour: null,
|
|
97969
|
+
sevenDay: null,
|
|
97970
|
+
modelWindowsByModel: {},
|
|
97971
|
+
observedAt,
|
|
97972
|
+
fiveHourObservedAt: null,
|
|
97973
|
+
sevenDayObservedAt: null
|
|
97974
|
+
};
|
|
97975
|
+
}
|
|
97976
|
+
/** Oldest observation among the retained standard windows, so one fresh event cannot mask stale data. */
|
|
97977
|
+
function oldestObservedAt(fiveHourObservedAt, sevenDayObservedAt) {
|
|
97978
|
+
return [fiveHourObservedAt, sevenDayObservedAt].filter((value) => value !== null).toSorted()[0];
|
|
97979
|
+
}
|
|
97980
|
+
function withStandardWindow(previous, mapped, observedAt) {
|
|
97981
|
+
const isFiveHour = mapped.kind === "fiveHour";
|
|
97982
|
+
const fiveHourObservedAt = isFiveHour ? observedAt : previous.fiveHourObservedAt;
|
|
97983
|
+
const sevenDayObservedAt = isFiveHour ? previous.sevenDayObservedAt : observedAt;
|
|
97984
|
+
return {
|
|
97985
|
+
...previous,
|
|
97986
|
+
fiveHour: isFiveHour ? mapped.window : previous.fiveHour,
|
|
97987
|
+
sevenDay: isFiveHour ? previous.sevenDay : mapped.window,
|
|
97935
97988
|
fiveHourObservedAt,
|
|
97936
97989
|
sevenDayObservedAt,
|
|
97937
|
-
observedAt:
|
|
97938
|
-
}
|
|
97990
|
+
observedAt: oldestObservedAt(fiveHourObservedAt, sevenDayObservedAt)
|
|
97991
|
+
};
|
|
97992
|
+
}
|
|
97993
|
+
function withModelWindow(previous, mapped) {
|
|
97994
|
+
return {
|
|
97995
|
+
...previous,
|
|
97996
|
+
modelWindowsByModel: {
|
|
97997
|
+
...previous.modelWindowsByModel,
|
|
97998
|
+
[mapped.model]: mapped.window
|
|
97999
|
+
}
|
|
98000
|
+
};
|
|
98001
|
+
}
|
|
98002
|
+
/**
|
|
98003
|
+
* Merges a single-window update into the cached snapshot without clobbering
|
|
98004
|
+
* the other windows. A per-model weekly report (`seven_day_<model>`) merges
|
|
98005
|
+
* into `modelWindowsByModel` keyed by model, leaving every other model and
|
|
98006
|
+
* the plain five_hour/seven_day windows untouched.
|
|
98007
|
+
*/
|
|
98008
|
+
function recordClaudeRateLimitTelemetry(info, generationHash = currentClaudeUsageGenerationHash(), providerAccountId) {
|
|
98009
|
+
if (generationHash === null) return;
|
|
98010
|
+
const cacheKey = cacheKeyFor(generationHash, providerAccountId);
|
|
98011
|
+
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
98012
|
+
const previous = cachedSnapshots.get(cacheKey) ?? emptyCachedSnapshot(observedAt);
|
|
98013
|
+
const standard = mapClaudeRateLimitInfo(info);
|
|
98014
|
+
if (standard) {
|
|
98015
|
+
cachedSnapshots.set(cacheKey, withStandardWindow(previous, standard, observedAt));
|
|
98016
|
+
return;
|
|
98017
|
+
}
|
|
98018
|
+
const model = mapClaudeModelRateLimitInfo(info);
|
|
98019
|
+
if (model) cachedSnapshots.set(cacheKey, withModelWindow(previous, model));
|
|
97939
98020
|
}
|
|
97940
98021
|
//#endregion
|
|
97941
98022
|
//#region src/providerAuth/ClaudeDeviceAuth.ts
|
|
@@ -98139,6 +98220,44 @@ function parseUsageLine(output, label, now) {
|
|
|
98139
98220
|
status: usageStatus(usedPercent)
|
|
98140
98221
|
};
|
|
98141
98222
|
}
|
|
98223
|
+
const MODEL_WEEK_LINE = /^Current week \(([^)]+)\): (\d+(?:\.\d+)?)% used(?: · resets (.+))?$/gm;
|
|
98224
|
+
const ALL_MODELS_LABEL = "all models";
|
|
98225
|
+
/** Matches CMMD's own bound in `personal-claude-usage.ts` — a model name past
|
|
98226
|
+
* this is treated as noise, not a real per-model window. */
|
|
98227
|
+
const MAX_MODEL_NAME_LENGTH = 64;
|
|
98228
|
+
/** Matches CMMD's own cap in `personal-claude-usage.ts`. */
|
|
98229
|
+
const MAX_MODEL_WINDOWS = 32;
|
|
98230
|
+
/**
|
|
98231
|
+
* Parses every "Current week (<Model>)" line except the "(all models)" row
|
|
98232
|
+
* already handled by `parseUsageLine`. The exclusion is case-insensitive to
|
|
98233
|
+
* match CMMD's own parser. A per-model line usually carries no reset suffix
|
|
98234
|
+
* of its own — Claude Code shows the same weekly reset as the all-models row
|
|
98235
|
+
* — so a missing suffix falls back to `sevenDayResetsAt`. A malformed line
|
|
98236
|
+
* (bad percent, unparsable reset, an implausibly long model name) is
|
|
98237
|
+
* skipped, not fatal: the two required lines are what fail closed, not these
|
|
98238
|
+
* optional extras. The result is capped at CMMD's own `modelWindows` bound so
|
|
98239
|
+
* an unexpectedly large or noisy `/usage` output can't grow the publish body
|
|
98240
|
+
* without limit.
|
|
98241
|
+
*/
|
|
98242
|
+
function parseModelUsageLines(output, sevenDayResetsAt, now) {
|
|
98243
|
+
const windows = [];
|
|
98244
|
+
for (const match of output.matchAll(MODEL_WEEK_LINE)) {
|
|
98245
|
+
const model = match[1];
|
|
98246
|
+
if (model.toLowerCase() === ALL_MODELS_LABEL) continue;
|
|
98247
|
+
if (model.length > MAX_MODEL_NAME_LENGTH) continue;
|
|
98248
|
+
const usedPercent = Number(match[2]);
|
|
98249
|
+
if (!Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100) continue;
|
|
98250
|
+
const resetsAt = match[3] ? parseResetAt(match[3], now) : sevenDayResetsAt;
|
|
98251
|
+
if (resetsAt === null) continue;
|
|
98252
|
+
windows.push({
|
|
98253
|
+
model,
|
|
98254
|
+
usedPercent,
|
|
98255
|
+
resetsAt,
|
|
98256
|
+
status: usageStatus(usedPercent)
|
|
98257
|
+
});
|
|
98258
|
+
}
|
|
98259
|
+
return windows.slice(0, MAX_MODEL_WINDOWS);
|
|
98260
|
+
}
|
|
98142
98261
|
async function fetchClaudeUsageViaOfficialClient(input) {
|
|
98143
98262
|
const raw = await (input.run ?? runClaudeUsageCommand)(input.binaryPath, USAGE_ARGS, input.claudeConfigDir);
|
|
98144
98263
|
const parsed = JSON.parse(raw);
|
|
@@ -98150,7 +98269,8 @@ async function fetchClaudeUsageViaOfficialClient(input) {
|
|
|
98150
98269
|
if (fiveHour === null || sevenDay === null) throw new Error("Claude client usage output shape changed.");
|
|
98151
98270
|
return {
|
|
98152
98271
|
fiveHour,
|
|
98153
|
-
sevenDay
|
|
98272
|
+
sevenDay,
|
|
98273
|
+
modelWindows: parseModelUsageLines(parsed.result, sevenDay.resetsAt, now)
|
|
98154
98274
|
};
|
|
98155
98275
|
}
|
|
98156
98276
|
//#endregion
|
|
@@ -98348,27 +98468,61 @@ const logUsagePublishFailure = (failure, path) => failure === null ? require_Sch
|
|
|
98348
98468
|
stack: failure.cause instanceof Error ? failure.cause.stack : void 0,
|
|
98349
98469
|
path
|
|
98350
98470
|
});
|
|
98471
|
+
/**
|
|
98472
|
+
* True only when CMMD's own telemetry schema is the reason the publish
|
|
98473
|
+
* failed, and only for a body that actually carried the field it can't
|
|
98474
|
+
* parse — never for a 400 caused by something else in the same body.
|
|
98475
|
+
*/
|
|
98476
|
+
function isModelWindowsRejection(cause, body) {
|
|
98477
|
+
return cause instanceof CmmdProxyRequestError && cause.status === 400 && "modelWindows" in body;
|
|
98478
|
+
}
|
|
98479
|
+
/**
|
|
98480
|
+
* Sends the usage-publish body, tolerant of a CMMD deployment that has not
|
|
98481
|
+
* yet rolled out per-model windows. CMMD's telemetry schema is `.strict()`,
|
|
98482
|
+
* so an unrecognized `modelWindows` key fails the WHOLE body with a 400 —
|
|
98483
|
+
* losing fiveHour/sevenDay too, not just the field it can't parse yet. Retry
|
|
98484
|
+
* exactly once with that key omitted rather than losing the publish
|
|
98485
|
+
* outright; deploy CMMD first so this retry becomes unnecessary. A second
|
|
98486
|
+
* failure (still 400, or anything else) propagates like any other publish
|
|
98487
|
+
* failure — this is a rollout accommodation, not a general retry policy.
|
|
98488
|
+
*/
|
|
98489
|
+
async function publishUsageBody(jwt, path, body) {
|
|
98490
|
+
try {
|
|
98491
|
+
await cmmdGateway.request({
|
|
98492
|
+
method: "POST",
|
|
98493
|
+
jwt,
|
|
98494
|
+
path,
|
|
98495
|
+
body
|
|
98496
|
+
});
|
|
98497
|
+
} catch (cause) {
|
|
98498
|
+
if (!isModelWindowsRejection(cause, body)) throw cause;
|
|
98499
|
+
const { modelWindows: _modelWindows, ...bodyWithoutModelWindows } = body;
|
|
98500
|
+
console.info("[cmmd-claude-cred-sync] usage publish retried without modelWindows — CMMD predates per-model rate-limit windows", { path });
|
|
98501
|
+
await cmmdGateway.request({
|
|
98502
|
+
method: "POST",
|
|
98503
|
+
jwt,
|
|
98504
|
+
path,
|
|
98505
|
+
body: bodyWithoutModelWindows
|
|
98506
|
+
});
|
|
98507
|
+
}
|
|
98508
|
+
}
|
|
98351
98509
|
async function publishClaudeUsageAfterSync(input) {
|
|
98352
98510
|
try {
|
|
98353
98511
|
const usage = await input.loadUsage({
|
|
98354
98512
|
binaryPath: resolveClaudeBinaryPath("claude"),
|
|
98355
98513
|
...input.homePath ? { claudeConfigDir: input.homePath } : {}
|
|
98356
98514
|
});
|
|
98357
|
-
await
|
|
98358
|
-
|
|
98359
|
-
|
|
98360
|
-
|
|
98361
|
-
|
|
98362
|
-
|
|
98363
|
-
|
|
98364
|
-
|
|
98365
|
-
|
|
98366
|
-
|
|
98367
|
-
|
|
98368
|
-
fiveHour: usage.fiveHour,
|
|
98369
|
-
sevenDay: usage.sevenDay,
|
|
98370
|
-
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
98371
|
-
}
|
|
98515
|
+
await publishUsageBody(input.auth.jwt, input.auth.path === AUTH_RUNTIME_CLAUDE_CREDENTIAL_PATH ? AUTH_RUNTIME_CLAUDE_USAGE_PATH : USER_CLAUDE_USAGE_PATH, {
|
|
98516
|
+
providerAccountId: input.providerAccountId,
|
|
98517
|
+
credentialGenerationId: input.credentialGenerationId,
|
|
98518
|
+
accountEmail: input.accountEmail,
|
|
98519
|
+
accountDisplayName: input.accountDisplayName,
|
|
98520
|
+
planType: input.planType,
|
|
98521
|
+
source: "active_poll",
|
|
98522
|
+
fiveHour: usage.fiveHour,
|
|
98523
|
+
sevenDay: usage.sevenDay,
|
|
98524
|
+
modelWindows: usage.modelWindows,
|
|
98525
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
98372
98526
|
});
|
|
98373
98527
|
return null;
|
|
98374
98528
|
} catch (cause) {
|
|
@@ -288888,7 +289042,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
288888
289042
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
288889
289043
|
*/
|
|
288890
289044
|
function readBakedBuildCommit() {
|
|
288891
|
-
return "
|
|
289045
|
+
return "abd184dd1d3a4d5156ad021326b71afb06430b1b";
|
|
288892
289046
|
}
|
|
288893
289047
|
async function resolveServerBuildCommit(input) {
|
|
288894
289048
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -310629,6 +310783,7 @@ const claudeUsageHandler = (dependencies) => require_Schema$1.gen(function* () {
|
|
|
310629
310783
|
response = require_HttpServer.jsonUnsafe({
|
|
310630
310784
|
fiveHour: usage.fiveHour,
|
|
310631
310785
|
sevenDay: usage.sevenDay,
|
|
310786
|
+
modelWindows: usage.modelWindows,
|
|
310632
310787
|
observedAt: observedAt.toISOString(),
|
|
310633
310788
|
receipt: {
|
|
310634
310789
|
orgId: claims.orgId,
|
|
@@ -310920,10 +311075,7 @@ const PtyAdapterLive = require_Schema$1.unwrap(require_Schema$1.gen(function* ()
|
|
|
310920
311075
|
}));
|
|
310921
311076
|
const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
310922
311077
|
const config = yield* ServerConfig;
|
|
310923
|
-
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-Cp9k4ybO.cjs")))).layer(
|
|
310924
|
-
port: config.port,
|
|
310925
|
-
...config.host ? { hostname: config.host } : {}
|
|
310926
|
-
});
|
|
311078
|
+
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-Cp9k4ybO.cjs")))).layer(bunHttpServerOptions(config));
|
|
310927
311079
|
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-ClnvzpdU.cjs"))), require_Schema$1.promise(() => import("node:http"))]);
|
|
310928
311080
|
return NodeHttpServer.layer(NodeHttp.createServer, {
|
|
310929
311081
|
host: config.host,
|