mobbdev 1.2.54 → 1.2.56
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/.env +3 -2
- package/dist/args/commands/upload_ai_blame.d.mts +1 -1
- package/dist/args/commands/upload_ai_blame.mjs +8 -9
- package/dist/index.mjs +857 -410
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -9918,7 +9918,7 @@ var _contextLogger = null;
|
|
|
9918
9918
|
var createContextLogger = async () => {
|
|
9919
9919
|
if (_contextLogger) return _contextLogger;
|
|
9920
9920
|
try {
|
|
9921
|
-
let
|
|
9921
|
+
let logger3;
|
|
9922
9922
|
try {
|
|
9923
9923
|
let module;
|
|
9924
9924
|
try {
|
|
@@ -9928,15 +9928,15 @@ var createContextLogger = async () => {
|
|
|
9928
9928
|
const sourcePath = "../../../../../tscommon/backend/src/utils/logger";
|
|
9929
9929
|
module = await import(sourcePath);
|
|
9930
9930
|
}
|
|
9931
|
-
|
|
9931
|
+
logger3 = module.logger;
|
|
9932
9932
|
} catch {
|
|
9933
9933
|
}
|
|
9934
|
-
if (
|
|
9934
|
+
if (logger3) {
|
|
9935
9935
|
_contextLogger = {
|
|
9936
|
-
info: (message, data) => data ?
|
|
9937
|
-
warn: (message, data) => data ?
|
|
9938
|
-
debug: (message, data) => data ?
|
|
9939
|
-
error: (message, data) => data ?
|
|
9936
|
+
info: (message, data) => data ? logger3.info(data, message) : logger3.info(message),
|
|
9937
|
+
warn: (message, data) => data ? logger3.warn(data, message) : logger3.warn(message),
|
|
9938
|
+
debug: (message, data) => data ? logger3.debug(data, message) : logger3.debug(message),
|
|
9939
|
+
error: (message, data) => data ? logger3.error(data, message) : logger3.error(message)
|
|
9940
9940
|
};
|
|
9941
9941
|
return _contextLogger;
|
|
9942
9942
|
}
|
|
@@ -9952,20 +9952,20 @@ var createContextLogger = async () => {
|
|
|
9952
9952
|
};
|
|
9953
9953
|
var contextLogger = {
|
|
9954
9954
|
info: async (message, data) => {
|
|
9955
|
-
const
|
|
9956
|
-
return
|
|
9955
|
+
const logger3 = await createContextLogger();
|
|
9956
|
+
return logger3.info(message, data);
|
|
9957
9957
|
},
|
|
9958
9958
|
debug: async (message, data) => {
|
|
9959
|
-
const
|
|
9960
|
-
return
|
|
9959
|
+
const logger3 = await createContextLogger();
|
|
9960
|
+
return logger3.debug(message, data);
|
|
9961
9961
|
},
|
|
9962
9962
|
warn: async (message, data) => {
|
|
9963
|
-
const
|
|
9964
|
-
return
|
|
9963
|
+
const logger3 = await createContextLogger();
|
|
9964
|
+
return logger3.warn(message, data);
|
|
9965
9965
|
},
|
|
9966
9966
|
error: async (message, data) => {
|
|
9967
|
-
const
|
|
9968
|
-
return
|
|
9967
|
+
const logger3 = await createContextLogger();
|
|
9968
|
+
return logger3.error(message, data);
|
|
9969
9969
|
}
|
|
9970
9970
|
};
|
|
9971
9971
|
|
|
@@ -11313,12 +11313,12 @@ var defaultLogger = {
|
|
|
11313
11313
|
}
|
|
11314
11314
|
}
|
|
11315
11315
|
};
|
|
11316
|
-
function createGitWithLogging(dirName,
|
|
11316
|
+
function createGitWithLogging(dirName, logger3 = defaultLogger) {
|
|
11317
11317
|
return simpleGit2(dirName, {
|
|
11318
11318
|
maxConcurrentProcesses: 6
|
|
11319
11319
|
}).outputHandler((bin, stdout2, stderr2) => {
|
|
11320
11320
|
const callID = Math.random();
|
|
11321
|
-
|
|
11321
|
+
logger3.info({ callID, bin }, "Start git CLI call");
|
|
11322
11322
|
let errChunks = [];
|
|
11323
11323
|
let outChunks = [];
|
|
11324
11324
|
let isStdoutDone = false;
|
|
@@ -11339,25 +11339,25 @@ function createGitWithLogging(dirName, logger2 = defaultLogger) {
|
|
|
11339
11339
|
err: `${errChunks.join("").slice(0, 200)}...`,
|
|
11340
11340
|
out: `${outChunks.join("").slice(0, 200)}...`
|
|
11341
11341
|
};
|
|
11342
|
-
|
|
11342
|
+
logger3.info(logObj, "git log output");
|
|
11343
11343
|
stderr2.removeListener("data", onStderrData);
|
|
11344
11344
|
stdout2.removeListener("data", onStdoutData);
|
|
11345
11345
|
errChunks = [];
|
|
11346
11346
|
outChunks = [];
|
|
11347
11347
|
}
|
|
11348
|
-
function markDone(
|
|
11349
|
-
if (
|
|
11348
|
+
function markDone(stream3) {
|
|
11349
|
+
if (stream3 === "stderr") isStderrDone = true;
|
|
11350
11350
|
else isStdoutDone = true;
|
|
11351
11351
|
finalizeAndCleanup();
|
|
11352
11352
|
}
|
|
11353
11353
|
stderr2.on("close", () => markDone("stderr"));
|
|
11354
11354
|
stdout2.on("close", () => markDone("stdout"));
|
|
11355
11355
|
stderr2.on("error", (error) => {
|
|
11356
|
-
|
|
11356
|
+
logger3.info({ callID, error: String(error) }, "git stderr stream error");
|
|
11357
11357
|
markDone("stderr");
|
|
11358
11358
|
});
|
|
11359
11359
|
stdout2.on("error", (error) => {
|
|
11360
|
-
|
|
11360
|
+
logger3.info({ callID, error: String(error) }, "git stdout stream error");
|
|
11361
11361
|
markDone("stdout");
|
|
11362
11362
|
});
|
|
11363
11363
|
});
|
|
@@ -13014,9 +13014,9 @@ async function uploadFile({
|
|
|
13014
13014
|
url,
|
|
13015
13015
|
uploadKey,
|
|
13016
13016
|
uploadFields,
|
|
13017
|
-
logger:
|
|
13017
|
+
logger: logger3
|
|
13018
13018
|
}) {
|
|
13019
|
-
const logInfo2 =
|
|
13019
|
+
const logInfo2 = logger3 || ((_message, _data) => {
|
|
13020
13020
|
});
|
|
13021
13021
|
logInfo2(`FileUpload: upload file start ${url}`);
|
|
13022
13022
|
logInfo2(`FileUpload: upload fields`, uploadFields);
|
|
@@ -13059,22 +13059,27 @@ import os from "os";
|
|
|
13059
13059
|
|
|
13060
13060
|
// src/utils/ConfigStoreService.ts
|
|
13061
13061
|
import Configstore from "configstore";
|
|
13062
|
-
function
|
|
13062
|
+
function getSanitizedDomain() {
|
|
13063
13063
|
const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL;
|
|
13064
13064
|
let domain = "";
|
|
13065
13065
|
try {
|
|
13066
13066
|
const url = new URL(API_URL2);
|
|
13067
13067
|
domain = url.hostname;
|
|
13068
|
-
} catch
|
|
13068
|
+
} catch {
|
|
13069
13069
|
domain = API_URL2.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/:\d+$/, "");
|
|
13070
13070
|
}
|
|
13071
|
-
|
|
13072
|
-
|
|
13071
|
+
return domain.replace(/\./g, "_");
|
|
13072
|
+
}
|
|
13073
|
+
function createConfigStore(defaultValues = { apiToken: "" }) {
|
|
13074
|
+
return new Configstore(`mobbdev-${getSanitizedDomain()}`, defaultValues);
|
|
13073
13075
|
}
|
|
13074
|
-
function
|
|
13075
|
-
return
|
|
13076
|
+
function createSessionConfigStore(sessionId) {
|
|
13077
|
+
return new Configstore(`mobbdev-${getSanitizedDomain()}-session-${sessionId}`);
|
|
13076
13078
|
}
|
|
13077
|
-
|
|
13079
|
+
function getSessionFilePrefix() {
|
|
13080
|
+
return `mobbdev-${getSanitizedDomain()}-session-`;
|
|
13081
|
+
}
|
|
13082
|
+
var configStore = createConfigStore();
|
|
13078
13083
|
|
|
13079
13084
|
// src/utils/computerName.ts
|
|
13080
13085
|
var STABLE_COMPUTER_NAME_CONFIG_KEY = "stableComputerName";
|
|
@@ -13305,6 +13310,10 @@ async function sanitizeDataWithCounts(obj) {
|
|
|
13305
13310
|
const sanitizedData = await sanitizeRecursive(obj);
|
|
13306
13311
|
return { sanitizedData, counts };
|
|
13307
13312
|
}
|
|
13313
|
+
async function sanitizeData(obj) {
|
|
13314
|
+
const result = await sanitizeDataWithCounts(obj);
|
|
13315
|
+
return result.sanitizedData;
|
|
13316
|
+
}
|
|
13308
13317
|
|
|
13309
13318
|
// src/args/commands/upload_ai_blame.ts
|
|
13310
13319
|
var defaultLogger2 = {
|
|
@@ -13358,9 +13367,9 @@ var PromptItemZ = z27.object({
|
|
|
13358
13367
|
}).optional()
|
|
13359
13368
|
});
|
|
13360
13369
|
var PromptItemArrayZ = z27.array(PromptItemZ);
|
|
13361
|
-
async function getRepositoryUrl() {
|
|
13370
|
+
async function getRepositoryUrl(workingDir) {
|
|
13362
13371
|
try {
|
|
13363
|
-
const gitService = new GitService(process.cwd());
|
|
13372
|
+
const gitService = new GitService(workingDir ?? process.cwd());
|
|
13364
13373
|
const isRepo = await gitService.isGitRepository();
|
|
13365
13374
|
if (!isRepo) {
|
|
13366
13375
|
return null;
|
|
@@ -13510,7 +13519,7 @@ async function uploadAiBlameHandler(options) {
|
|
|
13510
13519
|
exitOnError = true,
|
|
13511
13520
|
apiUrl,
|
|
13512
13521
|
webAppUrl,
|
|
13513
|
-
logger:
|
|
13522
|
+
logger: logger3 = defaultLogger2
|
|
13514
13523
|
} = options;
|
|
13515
13524
|
const prompts = args.prompt || [];
|
|
13516
13525
|
const inferences = args.inference || [];
|
|
@@ -13521,7 +13530,7 @@ async function uploadAiBlameHandler(options) {
|
|
|
13521
13530
|
const sessionIds = args.sessionId || args["session-id"] || [];
|
|
13522
13531
|
if (prompts.length !== inferences.length) {
|
|
13523
13532
|
const errorMsg = "prompt and inference must have the same number of entries";
|
|
13524
|
-
|
|
13533
|
+
logger3.error(chalk4.red(errorMsg));
|
|
13525
13534
|
if (exitOnError) {
|
|
13526
13535
|
process.exit(1);
|
|
13527
13536
|
}
|
|
@@ -13541,7 +13550,7 @@ async function uploadAiBlameHandler(options) {
|
|
|
13541
13550
|
]);
|
|
13542
13551
|
} catch {
|
|
13543
13552
|
const errorMsg = `File not found for session ${i + 1}`;
|
|
13544
|
-
|
|
13553
|
+
logger3.error(chalk4.red(errorMsg));
|
|
13545
13554
|
if (exitOnError) {
|
|
13546
13555
|
process.exit(1);
|
|
13547
13556
|
}
|
|
@@ -13574,7 +13583,7 @@ async function uploadAiBlameHandler(options) {
|
|
|
13574
13583
|
const uploadSessions = initRes.uploadAIBlameInferencesInit?.uploadSessions ?? [];
|
|
13575
13584
|
if (uploadSessions.length !== sessions.length) {
|
|
13576
13585
|
const errorMsg = "Init failed to return expected number of sessions";
|
|
13577
|
-
|
|
13586
|
+
logger3.error(chalk4.red(errorMsg));
|
|
13578
13587
|
if (exitOnError) {
|
|
13579
13588
|
process.exit(1);
|
|
13580
13589
|
}
|
|
@@ -13618,7 +13627,7 @@ async function uploadAiBlameHandler(options) {
|
|
|
13618
13627
|
};
|
|
13619
13628
|
});
|
|
13620
13629
|
try {
|
|
13621
|
-
|
|
13630
|
+
logger3.info(
|
|
13622
13631
|
`[UPLOAD] Calling finalizeAIBlameInferencesUploadRaw with ${finalizeSessions.length} sessions`
|
|
13623
13632
|
);
|
|
13624
13633
|
const finRes = await authenticatedClient.finalizeAIBlameInferencesUploadRaw(
|
|
@@ -13626,11 +13635,11 @@ async function uploadAiBlameHandler(options) {
|
|
|
13626
13635
|
sessions: finalizeSessions
|
|
13627
13636
|
}
|
|
13628
13637
|
);
|
|
13629
|
-
|
|
13638
|
+
logger3.info("[UPLOAD] Finalize response:", JSON.stringify(finRes, null, 2));
|
|
13630
13639
|
const status = finRes?.finalizeAIBlameInferencesUpload?.status;
|
|
13631
13640
|
if (status !== "OK") {
|
|
13632
13641
|
const errorMsg = finRes?.finalizeAIBlameInferencesUpload?.error || "unknown error";
|
|
13633
|
-
|
|
13642
|
+
logger3.error(
|
|
13634
13643
|
chalk4.red(
|
|
13635
13644
|
`[UPLOAD] Finalize failed with status: ${status}, error: ${errorMsg}`
|
|
13636
13645
|
)
|
|
@@ -13640,9 +13649,9 @@ async function uploadAiBlameHandler(options) {
|
|
|
13640
13649
|
}
|
|
13641
13650
|
throw new Error(errorMsg);
|
|
13642
13651
|
}
|
|
13643
|
-
|
|
13652
|
+
logger3.info(chalk4.green("[UPLOAD] AI Blame uploads finalized successfully"));
|
|
13644
13653
|
} catch (error) {
|
|
13645
|
-
|
|
13654
|
+
logger3.error("[UPLOAD] Finalize threw error:", error);
|
|
13646
13655
|
throw error;
|
|
13647
13656
|
}
|
|
13648
13657
|
}
|
|
@@ -13652,6 +13661,110 @@ async function uploadAiBlameCommandHandler(args) {
|
|
|
13652
13661
|
|
|
13653
13662
|
// src/features/analysis/graphql/tracy-batch-upload.ts
|
|
13654
13663
|
var debug10 = Debug9("mobbdev:tracy-batch-upload");
|
|
13664
|
+
async function sanitizeRawData(rawData) {
|
|
13665
|
+
try {
|
|
13666
|
+
const sanitized = await sanitizeData(rawData);
|
|
13667
|
+
return JSON.stringify(sanitized);
|
|
13668
|
+
} catch (err) {
|
|
13669
|
+
console.warn(
|
|
13670
|
+
"[tracy] sanitizeRawData failed, falling back to unsanitized:",
|
|
13671
|
+
err.message
|
|
13672
|
+
);
|
|
13673
|
+
return JSON.stringify(rawData);
|
|
13674
|
+
}
|
|
13675
|
+
}
|
|
13676
|
+
async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
|
|
13677
|
+
const repositoryUrl = await getRepositoryUrl(workingDir);
|
|
13678
|
+
const { computerName, userName } = getSystemInfo();
|
|
13679
|
+
const clientVersion = packageJson.version;
|
|
13680
|
+
const serializedRawDataByIndex = /* @__PURE__ */ new Map();
|
|
13681
|
+
const records = await Promise.all(
|
|
13682
|
+
rawRecords.map(async (record, index) => {
|
|
13683
|
+
if (record.rawData != null) {
|
|
13684
|
+
const serialized = await sanitizeRawData(record.rawData);
|
|
13685
|
+
serializedRawDataByIndex.set(index, serialized);
|
|
13686
|
+
}
|
|
13687
|
+
const { rawData: _rawData, ...rest } = record;
|
|
13688
|
+
return {
|
|
13689
|
+
...rest,
|
|
13690
|
+
repositoryUrl: repositoryUrl ?? void 0,
|
|
13691
|
+
computerName,
|
|
13692
|
+
userName,
|
|
13693
|
+
clientVersion
|
|
13694
|
+
};
|
|
13695
|
+
})
|
|
13696
|
+
);
|
|
13697
|
+
const recordsWithRawData = rawRecords.map((r, i) => ({ recordId: r.recordId, index: i })).filter((entry) => serializedRawDataByIndex.has(entry.index));
|
|
13698
|
+
if (recordsWithRawData.length > 0) {
|
|
13699
|
+
debug10("Uploading %d rawData files to S3...", recordsWithRawData.length);
|
|
13700
|
+
const uploadUrlResult = await client.getTracyRawDataUploadUrl();
|
|
13701
|
+
const { url, uploadFieldsJSON, keyPrefix } = uploadUrlResult.getTracyRawDataUploadUrl;
|
|
13702
|
+
if (!url || !uploadFieldsJSON || !keyPrefix) {
|
|
13703
|
+
return {
|
|
13704
|
+
ok: false,
|
|
13705
|
+
errors: ["Failed to get S3 upload URL for rawData"]
|
|
13706
|
+
};
|
|
13707
|
+
}
|
|
13708
|
+
let uploadFields;
|
|
13709
|
+
try {
|
|
13710
|
+
uploadFields = JSON.parse(uploadFieldsJSON);
|
|
13711
|
+
} catch {
|
|
13712
|
+
return { ok: false, errors: ["Malformed uploadFieldsJSON from server"] };
|
|
13713
|
+
}
|
|
13714
|
+
const uploadResults = await Promise.allSettled(
|
|
13715
|
+
recordsWithRawData.map(async (entry) => {
|
|
13716
|
+
const rawDataJson = serializedRawDataByIndex.get(entry.index);
|
|
13717
|
+
if (!rawDataJson) {
|
|
13718
|
+
debug10("No serialized rawData for recordId=%s", entry.recordId);
|
|
13719
|
+
return;
|
|
13720
|
+
}
|
|
13721
|
+
const uploadKey = `${keyPrefix}${entry.recordId}.json`;
|
|
13722
|
+
await uploadFile({
|
|
13723
|
+
file: Buffer.from(rawDataJson, "utf-8"),
|
|
13724
|
+
url,
|
|
13725
|
+
uploadKey,
|
|
13726
|
+
uploadFields
|
|
13727
|
+
});
|
|
13728
|
+
records[entry.index].rawDataS3Key = uploadKey;
|
|
13729
|
+
})
|
|
13730
|
+
);
|
|
13731
|
+
const uploadErrors = uploadResults.filter((r) => r.status === "rejected").map((r) => r.reason.message);
|
|
13732
|
+
if (uploadErrors.length > 0) {
|
|
13733
|
+
debug10("S3 upload errors: %O", uploadErrors);
|
|
13734
|
+
}
|
|
13735
|
+
const missingS3Keys = recordsWithRawData.filter(
|
|
13736
|
+
(entry) => !records[entry.index].rawDataS3Key
|
|
13737
|
+
);
|
|
13738
|
+
if (missingS3Keys.length > 0) {
|
|
13739
|
+
const missingIds = missingS3Keys.map((e) => e.recordId);
|
|
13740
|
+
debug10("Records missing S3 keys after upload: %O", missingIds);
|
|
13741
|
+
return {
|
|
13742
|
+
ok: false,
|
|
13743
|
+
errors: [
|
|
13744
|
+
`Failed to upload rawData to S3 for ${missingS3Keys.length} record(s): ${missingIds.join(", ")}`,
|
|
13745
|
+
...uploadErrors
|
|
13746
|
+
]
|
|
13747
|
+
};
|
|
13748
|
+
}
|
|
13749
|
+
debug10("S3 uploads complete");
|
|
13750
|
+
}
|
|
13751
|
+
try {
|
|
13752
|
+
const result = await client.uploadTracyRecords({ records });
|
|
13753
|
+
if (result.uploadTracyRecords.status !== "OK") {
|
|
13754
|
+
return {
|
|
13755
|
+
ok: false,
|
|
13756
|
+
errors: [result.uploadTracyRecords.error ?? "Unknown server error"]
|
|
13757
|
+
};
|
|
13758
|
+
}
|
|
13759
|
+
} catch (err) {
|
|
13760
|
+
debug10("Upload failed: %s", err.message);
|
|
13761
|
+
return {
|
|
13762
|
+
ok: false,
|
|
13763
|
+
errors: [err.message]
|
|
13764
|
+
};
|
|
13765
|
+
}
|
|
13766
|
+
return { ok: true, errors: null };
|
|
13767
|
+
}
|
|
13655
13768
|
|
|
13656
13769
|
// src/mcp/services/types.ts
|
|
13657
13770
|
function detectIDE() {
|
|
@@ -14869,8 +14982,8 @@ if (typeof __filename !== "undefined") {
|
|
|
14869
14982
|
}
|
|
14870
14983
|
var costumeRequire = createRequire(moduleUrl);
|
|
14871
14984
|
var getCheckmarxPath = () => {
|
|
14872
|
-
const
|
|
14873
|
-
const cxFileName =
|
|
14985
|
+
const os14 = type();
|
|
14986
|
+
const cxFileName = os14 === "Windows_NT" ? "cx.exe" : "cx";
|
|
14874
14987
|
try {
|
|
14875
14988
|
return costumeRequire.resolve(`.bin/${cxFileName}`);
|
|
14876
14989
|
} catch (e) {
|
|
@@ -16183,229 +16296,383 @@ async function analyzeHandler(args) {
|
|
|
16183
16296
|
}
|
|
16184
16297
|
|
|
16185
16298
|
// src/features/claude_code/data_collector.ts
|
|
16299
|
+
import { createHash as createHash2 } from "crypto";
|
|
16300
|
+
import { open as open4, readdir, readFile, unlink } from "fs/promises";
|
|
16301
|
+
import path13 from "path";
|
|
16186
16302
|
import { z as z33 } from "zod";
|
|
16187
16303
|
init_client_generates();
|
|
16188
|
-
init_GitService();
|
|
16189
|
-
init_urlParser2();
|
|
16190
16304
|
|
|
16191
|
-
// src/utils/
|
|
16192
|
-
import
|
|
16193
|
-
import
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16305
|
+
// src/utils/shared-logger/create-logger.ts
|
|
16306
|
+
import Configstore2 from "configstore";
|
|
16307
|
+
import pino from "pino";
|
|
16308
|
+
|
|
16309
|
+
// src/utils/shared-logger/configstore-stream.ts
|
|
16310
|
+
import { writeFileSync } from "fs";
|
|
16311
|
+
import * as stream from "stream";
|
|
16312
|
+
var DEFAULT_MAX_LOGS = 1e3;
|
|
16313
|
+
var DEFAULT_MAX_HEARTBEAT = 100;
|
|
16314
|
+
var LOGS_KEY = "logs";
|
|
16315
|
+
var HEARTBEAT_KEY = "heartbeat";
|
|
16316
|
+
function createConfigstoreStream(store, opts) {
|
|
16317
|
+
const maxLogs = opts.maxLogs ?? DEFAULT_MAX_LOGS;
|
|
16318
|
+
const maxHeartbeat = opts.maxHeartbeat ?? DEFAULT_MAX_HEARTBEAT;
|
|
16319
|
+
const buffer = [];
|
|
16320
|
+
const heartbeatBuffer = [];
|
|
16321
|
+
let scopePath = opts.scopePath;
|
|
16322
|
+
function storeKey(base) {
|
|
16323
|
+
return scopePath ? `${base}:${scopePath}` : base;
|
|
16324
|
+
}
|
|
16325
|
+
function writeToDisk(entries, key, max) {
|
|
16205
16326
|
try {
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
|
|
16216
|
-
}
|
|
16217
|
-
|
|
16327
|
+
const existing = store.get(key) ?? [];
|
|
16328
|
+
existing.push(...entries);
|
|
16329
|
+
const trimmed = existing.length > max ? existing.slice(-max) : existing;
|
|
16330
|
+
store.set(key, trimmed);
|
|
16331
|
+
} catch {
|
|
16332
|
+
try {
|
|
16333
|
+
const lines = `${entries.map((e) => JSON.stringify(e)).join("\n")}
|
|
16334
|
+
`;
|
|
16335
|
+
writeFileSync(`${store.path}.fallback`, lines, { flag: "a" });
|
|
16336
|
+
} catch {
|
|
16337
|
+
}
|
|
16338
|
+
}
|
|
16339
|
+
}
|
|
16340
|
+
const writable = new stream.Writable({
|
|
16341
|
+
write(chunk, _encoding, callback) {
|
|
16342
|
+
callback();
|
|
16343
|
+
try {
|
|
16344
|
+
const parsed = JSON.parse(chunk.toString());
|
|
16345
|
+
const entry = {
|
|
16346
|
+
timestamp: parsed.time ? new Date(parsed.time).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
16347
|
+
level: parsed.level ?? "info",
|
|
16348
|
+
message: parsed.msg ?? "",
|
|
16349
|
+
...parsed.durationMs !== void 0 && {
|
|
16350
|
+
durationMs: parsed.durationMs
|
|
16351
|
+
},
|
|
16352
|
+
...parsed.data !== void 0 && { data: parsed.data }
|
|
16353
|
+
};
|
|
16354
|
+
const isHeartbeat = parsed.heartbeat === true;
|
|
16355
|
+
if (opts.buffered) {
|
|
16356
|
+
if (isHeartbeat) {
|
|
16357
|
+
heartbeatBuffer.push(entry);
|
|
16358
|
+
} else {
|
|
16359
|
+
buffer.push(entry);
|
|
16360
|
+
}
|
|
16361
|
+
} else {
|
|
16362
|
+
if (isHeartbeat) {
|
|
16363
|
+
writeToDisk([entry], storeKey(HEARTBEAT_KEY), maxHeartbeat);
|
|
16364
|
+
} else {
|
|
16365
|
+
writeToDisk([entry], storeKey(LOGS_KEY), maxLogs);
|
|
16366
|
+
}
|
|
16367
|
+
}
|
|
16368
|
+
} catch {
|
|
16218
16369
|
}
|
|
16219
16370
|
}
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16371
|
+
});
|
|
16372
|
+
function flush() {
|
|
16373
|
+
if (buffer.length > 0) {
|
|
16374
|
+
writeToDisk(buffer, storeKey(LOGS_KEY), maxLogs);
|
|
16375
|
+
buffer.length = 0;
|
|
16376
|
+
}
|
|
16377
|
+
if (heartbeatBuffer.length > 0) {
|
|
16378
|
+
writeToDisk(heartbeatBuffer, storeKey(HEARTBEAT_KEY), maxHeartbeat);
|
|
16379
|
+
heartbeatBuffer.length = 0;
|
|
16380
|
+
}
|
|
16381
|
+
}
|
|
16382
|
+
function setScopePath(path27) {
|
|
16383
|
+
scopePath = path27;
|
|
16384
|
+
}
|
|
16385
|
+
return { writable, flush, setScopePath };
|
|
16386
|
+
}
|
|
16387
|
+
|
|
16388
|
+
// src/utils/shared-logger/dd-batch.ts
|
|
16389
|
+
import * as stream2 from "stream";
|
|
16390
|
+
import * as util from "util";
|
|
16391
|
+
import fetch5 from "cross-fetch";
|
|
16392
|
+
var DD_BATCH_INTERVAL_MS = 5e3;
|
|
16393
|
+
var DD_BATCH_MAX_SIZE = 50;
|
|
16394
|
+
function createDdBatch(config2) {
|
|
16395
|
+
let batch = [];
|
|
16396
|
+
let flushTimer = null;
|
|
16397
|
+
let pendingFlush = null;
|
|
16398
|
+
let errorLogged = false;
|
|
16399
|
+
function flush() {
|
|
16400
|
+
if (batch.length === 0) return;
|
|
16401
|
+
const toSend = batch;
|
|
16402
|
+
batch = [];
|
|
16403
|
+
pendingFlush = fetch5("https://http-intake.logs.datadoghq.com/api/v2/logs", {
|
|
16404
|
+
method: "POST",
|
|
16405
|
+
headers: {
|
|
16406
|
+
"Content-Type": "application/json",
|
|
16407
|
+
"DD-API-KEY": config2.apiKey
|
|
16408
|
+
},
|
|
16409
|
+
body: JSON.stringify(toSend)
|
|
16410
|
+
}).then(() => void 0).catch((error) => {
|
|
16411
|
+
if (!errorLogged) {
|
|
16412
|
+
errorLogged = true;
|
|
16413
|
+
config2.onError?.(
|
|
16414
|
+
`Error sending log to Datadog (further errors suppressed): ${util.inspect(error)}`
|
|
16415
|
+
);
|
|
16416
|
+
}
|
|
16417
|
+
});
|
|
16418
|
+
}
|
|
16419
|
+
function enqueue(message) {
|
|
16420
|
+
if (!config2.apiKey) return;
|
|
16421
|
+
batch.push({
|
|
16422
|
+
hostname: config2.hostname,
|
|
16423
|
+
ddsource: config2.ddsource,
|
|
16424
|
+
service: config2.service,
|
|
16425
|
+
ddtags: config2.ddtags,
|
|
16426
|
+
message
|
|
16427
|
+
});
|
|
16428
|
+
if (!flushTimer) {
|
|
16429
|
+
flushTimer = setInterval(flush, DD_BATCH_INTERVAL_MS);
|
|
16430
|
+
if (config2.unrefTimer) {
|
|
16431
|
+
flushTimer.unref();
|
|
16224
16432
|
}
|
|
16225
16433
|
}
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
|
|
16434
|
+
if (batch.length >= DD_BATCH_MAX_SIZE) {
|
|
16435
|
+
flush();
|
|
16436
|
+
}
|
|
16229
16437
|
}
|
|
16438
|
+
async function flushAsync() {
|
|
16439
|
+
flush();
|
|
16440
|
+
await pendingFlush;
|
|
16441
|
+
}
|
|
16442
|
+
function dispose() {
|
|
16443
|
+
flush();
|
|
16444
|
+
if (flushTimer) {
|
|
16445
|
+
clearInterval(flushTimer);
|
|
16446
|
+
flushTimer = null;
|
|
16447
|
+
}
|
|
16448
|
+
}
|
|
16449
|
+
function createPinoStream() {
|
|
16450
|
+
return new stream2.Writable({
|
|
16451
|
+
write(chunk, _encoding, callback) {
|
|
16452
|
+
callback();
|
|
16453
|
+
enqueue(chunk.toString());
|
|
16454
|
+
}
|
|
16455
|
+
});
|
|
16456
|
+
}
|
|
16457
|
+
return { enqueue, flush, flushAsync, dispose, createPinoStream };
|
|
16230
16458
|
}
|
|
16231
16459
|
|
|
16232
|
-
// src/
|
|
16233
|
-
import
|
|
16234
|
-
|
|
16235
|
-
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
|
|
16251
|
-
|
|
16252
|
-
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16460
|
+
// src/utils/shared-logger/hostname.ts
|
|
16461
|
+
import { createHash } from "crypto";
|
|
16462
|
+
import os4 from "os";
|
|
16463
|
+
function hashString(input) {
|
|
16464
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
16465
|
+
}
|
|
16466
|
+
function getPlainHostname() {
|
|
16467
|
+
try {
|
|
16468
|
+
return `${os4.userInfo().username}@${os4.hostname()}`;
|
|
16469
|
+
} catch {
|
|
16470
|
+
return `unknown@${os4.hostname()}`;
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16473
|
+
function getHashedHostname() {
|
|
16474
|
+
try {
|
|
16475
|
+
return `${hashString(os4.userInfo().username)}@${hashString(os4.hostname())}`;
|
|
16476
|
+
} catch {
|
|
16477
|
+
return `unknown@${hashString(os4.hostname())}`;
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
16480
|
+
|
|
16481
|
+
// src/utils/shared-logger/create-logger.ts
|
|
16482
|
+
function createLogger(config2) {
|
|
16483
|
+
const {
|
|
16484
|
+
namespace,
|
|
16485
|
+
scopePath,
|
|
16486
|
+
buffered = true,
|
|
16487
|
+
maxLogs,
|
|
16488
|
+
maxHeartbeat,
|
|
16489
|
+
dd,
|
|
16490
|
+
additionalStreams = []
|
|
16491
|
+
} = config2;
|
|
16492
|
+
const store = new Configstore2(namespace, {});
|
|
16493
|
+
const csStream = createConfigstoreStream(store, {
|
|
16494
|
+
buffered,
|
|
16495
|
+
scopePath,
|
|
16496
|
+
maxLogs,
|
|
16497
|
+
maxHeartbeat
|
|
16498
|
+
});
|
|
16499
|
+
let ddBatch = null;
|
|
16500
|
+
if (dd) {
|
|
16501
|
+
const hostname = dd.hostnameMode === "hashed" ? getHashedHostname() : getPlainHostname();
|
|
16502
|
+
const ddConfig = {
|
|
16503
|
+
apiKey: dd.apiKey,
|
|
16504
|
+
ddsource: dd.ddsource,
|
|
16505
|
+
service: dd.service,
|
|
16506
|
+
ddtags: dd.ddtags,
|
|
16507
|
+
hostname,
|
|
16508
|
+
unrefTimer: dd.unrefTimer,
|
|
16509
|
+
onError: dd.onError
|
|
16510
|
+
};
|
|
16511
|
+
ddBatch = createDdBatch(ddConfig);
|
|
16512
|
+
}
|
|
16513
|
+
const streams = [
|
|
16514
|
+
{ stream: csStream.writable, level: "info" }
|
|
16515
|
+
];
|
|
16516
|
+
if (ddBatch) {
|
|
16517
|
+
streams.push({ stream: ddBatch.createPinoStream(), level: "info" });
|
|
16518
|
+
}
|
|
16519
|
+
for (const extra of additionalStreams) {
|
|
16520
|
+
streams.push({
|
|
16521
|
+
stream: extra.stream,
|
|
16522
|
+
level: extra.level
|
|
16523
|
+
});
|
|
16524
|
+
}
|
|
16525
|
+
const pinoLogger = pino(
|
|
16526
|
+
{
|
|
16527
|
+
formatters: {
|
|
16528
|
+
level: (label) => ({ level: label })
|
|
16282
16529
|
}
|
|
16530
|
+
},
|
|
16531
|
+
pino.multistream(streams)
|
|
16532
|
+
);
|
|
16533
|
+
const info = pinoLogger.info.bind(pinoLogger);
|
|
16534
|
+
const warn = pinoLogger.warn.bind(pinoLogger);
|
|
16535
|
+
const error = pinoLogger.error.bind(pinoLogger);
|
|
16536
|
+
const debug23 = pinoLogger.debug.bind(pinoLogger);
|
|
16537
|
+
function heartbeat(message, data) {
|
|
16538
|
+
pinoLogger.info({ data, heartbeat: true }, message);
|
|
16539
|
+
}
|
|
16540
|
+
async function timed(label, fn) {
|
|
16541
|
+
const start = Date.now();
|
|
16542
|
+
try {
|
|
16543
|
+
const result = await fn();
|
|
16544
|
+
pinoLogger.info({ durationMs: Date.now() - start }, label);
|
|
16545
|
+
return result;
|
|
16546
|
+
} catch (err) {
|
|
16547
|
+
pinoLogger.error(
|
|
16548
|
+
{
|
|
16549
|
+
durationMs: Date.now() - start,
|
|
16550
|
+
data: err instanceof Error ? err.message : String(err)
|
|
16551
|
+
},
|
|
16552
|
+
`${label} [FAILED]`
|
|
16553
|
+
);
|
|
16554
|
+
throw err;
|
|
16283
16555
|
}
|
|
16284
16556
|
}
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
};
|
|
16292
|
-
}
|
|
16293
|
-
async function parseTranscriptAndCreateTrace(transcriptPath, hookData, inference) {
|
|
16294
|
-
const content = await fsPromises4.readFile(transcriptPath, "utf-8");
|
|
16295
|
-
const lines = content.trim().split("\n");
|
|
16296
|
-
let currentToolIndex = -1;
|
|
16297
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
16298
|
-
const line = lines[i]?.trim() ?? "";
|
|
16299
|
-
if (!line.includes('"type":"tool_use"')) continue;
|
|
16300
|
-
const isEditTool = line.includes('"name":"Edit"');
|
|
16301
|
-
const isWriteTool = line.includes('"name":"Write"');
|
|
16302
|
-
if (isEditTool || isWriteTool) {
|
|
16303
|
-
currentToolIndex = i;
|
|
16304
|
-
break;
|
|
16557
|
+
function flushLogs2() {
|
|
16558
|
+
csStream.flush();
|
|
16559
|
+
}
|
|
16560
|
+
async function flushDdAsync() {
|
|
16561
|
+
if (ddBatch) {
|
|
16562
|
+
await ddBatch.flushAsync();
|
|
16305
16563
|
}
|
|
16306
16564
|
}
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
let model;
|
|
16311
|
-
let latestDate;
|
|
16312
|
-
for (let i = startIndex; i <= endIndex; i++) {
|
|
16313
|
-
const line = lines[i]?.trim() ?? "";
|
|
16314
|
-
const entry = JSON.parse(line);
|
|
16315
|
-
const lineResult = processTranscriptLine(entry);
|
|
16316
|
-
prompts.push(...lineResult.prompts);
|
|
16317
|
-
if (lineResult.model && !model) {
|
|
16318
|
-
model = lineResult.model;
|
|
16319
|
-
}
|
|
16320
|
-
if (lineResult.date) {
|
|
16321
|
-
if (!latestDate || lineResult.date > latestDate) {
|
|
16322
|
-
latestDate = lineResult.date;
|
|
16323
|
-
}
|
|
16324
|
-
}
|
|
16325
|
-
}
|
|
16326
|
-
prompts.push({
|
|
16327
|
-
type: "TOOL_EXECUTION",
|
|
16328
|
-
date: latestDate || /* @__PURE__ */ new Date(),
|
|
16329
|
-
tool: {
|
|
16330
|
-
name: hookData.tool_name,
|
|
16331
|
-
parameters: JSON.stringify(hookData.tool_input, null, 2),
|
|
16332
|
-
result: JSON.stringify(hookData.tool_response, null, 2),
|
|
16333
|
-
rawArguments: JSON.stringify(hookData.tool_input),
|
|
16334
|
-
accepted: true
|
|
16565
|
+
function disposeDd() {
|
|
16566
|
+
if (ddBatch) {
|
|
16567
|
+
ddBatch.dispose();
|
|
16335
16568
|
}
|
|
16336
|
-
}
|
|
16569
|
+
}
|
|
16337
16570
|
return {
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16571
|
+
info,
|
|
16572
|
+
warn,
|
|
16573
|
+
error,
|
|
16574
|
+
debug: debug23,
|
|
16575
|
+
heartbeat,
|
|
16576
|
+
timed,
|
|
16577
|
+
flushLogs: flushLogs2,
|
|
16578
|
+
flushDdAsync,
|
|
16579
|
+
disposeDd,
|
|
16580
|
+
setScopePath: csStream.setScopePath
|
|
16344
16581
|
};
|
|
16345
16582
|
}
|
|
16346
16583
|
|
|
16584
|
+
// src/features/claude_code/hook_logger.ts
|
|
16585
|
+
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
16586
|
+
var CLI_VERSION = true ? "1.2.56" : "unknown";
|
|
16587
|
+
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
16588
|
+
function createHookLogger(scopePath) {
|
|
16589
|
+
return createLogger({
|
|
16590
|
+
namespace: NAMESPACE,
|
|
16591
|
+
scopePath,
|
|
16592
|
+
dd: {
|
|
16593
|
+
apiKey: DD_RUM_TOKEN,
|
|
16594
|
+
ddsource: "mobbdev-cli",
|
|
16595
|
+
service: "mobbdev-cli-hook",
|
|
16596
|
+
ddtags: `version:${CLI_VERSION}`,
|
|
16597
|
+
hostnameMode: "hashed",
|
|
16598
|
+
unrefTimer: true
|
|
16599
|
+
}
|
|
16600
|
+
});
|
|
16601
|
+
}
|
|
16602
|
+
var logger = createHookLogger();
|
|
16603
|
+
var activeScopedLoggers = [];
|
|
16604
|
+
var hookLog = logger;
|
|
16605
|
+
function flushLogs() {
|
|
16606
|
+
logger.flushLogs();
|
|
16607
|
+
for (const scoped of activeScopedLoggers) {
|
|
16608
|
+
scoped.flushLogs();
|
|
16609
|
+
}
|
|
16610
|
+
activeScopedLoggers.length = 0;
|
|
16611
|
+
}
|
|
16612
|
+
async function flushDdLogs() {
|
|
16613
|
+
await logger.flushDdAsync();
|
|
16614
|
+
for (const scoped of activeScopedLoggers) {
|
|
16615
|
+
await scoped.flushDdAsync();
|
|
16616
|
+
}
|
|
16617
|
+
}
|
|
16618
|
+
function createScopedHookLog(scopePath) {
|
|
16619
|
+
const scoped = createHookLogger(scopePath);
|
|
16620
|
+
activeScopedLoggers.push(scoped);
|
|
16621
|
+
return scoped;
|
|
16622
|
+
}
|
|
16623
|
+
|
|
16347
16624
|
// src/features/claude_code/data_collector.ts
|
|
16348
|
-
var
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
newLines: z33.number(),
|
|
16353
|
-
lines: z33.array(z33.string())
|
|
16354
|
-
});
|
|
16355
|
-
var EditToolInputSchema = z33.object({
|
|
16356
|
-
file_path: z33.string(),
|
|
16357
|
-
old_string: z33.string(),
|
|
16358
|
-
new_string: z33.string()
|
|
16359
|
-
});
|
|
16360
|
-
var WriteToolInputSchema = z33.object({
|
|
16361
|
-
file_path: z33.string(),
|
|
16362
|
-
content: z33.string()
|
|
16363
|
-
});
|
|
16364
|
-
var EditToolResponseSchema = z33.object({
|
|
16365
|
-
filePath: z33.string(),
|
|
16366
|
-
oldString: z33.string().optional(),
|
|
16367
|
-
newString: z33.string().optional(),
|
|
16368
|
-
originalFile: z33.string().optional(),
|
|
16369
|
-
structuredPatch: z33.array(StructuredPatchItemSchema),
|
|
16370
|
-
userModified: z33.boolean().optional(),
|
|
16371
|
-
replaceAll: z33.boolean().optional()
|
|
16372
|
-
});
|
|
16373
|
-
var WriteToolResponseSchema = z33.object({
|
|
16374
|
-
type: z33.string().optional(),
|
|
16375
|
-
filePath: z33.string(),
|
|
16376
|
-
content: z33.string().optional(),
|
|
16377
|
-
structuredPatch: z33.array(z33.any()).optional()
|
|
16378
|
-
});
|
|
16625
|
+
var HOOK_COOLDOWN_MS = 1e4;
|
|
16626
|
+
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
16627
|
+
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
16628
|
+
var COOLDOWN_KEY = "lastHookRunAt";
|
|
16379
16629
|
var HookDataSchema = z33.object({
|
|
16380
16630
|
session_id: z33.string(),
|
|
16381
16631
|
transcript_path: z33.string(),
|
|
16382
16632
|
cwd: z33.string(),
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
tool_response: z33.union([EditToolResponseSchema, WriteToolResponseSchema])
|
|
16633
|
+
hook_event_name: z33.string(),
|
|
16634
|
+
tool_name: z33.string(),
|
|
16635
|
+
tool_input: z33.unknown(),
|
|
16636
|
+
tool_response: z33.unknown()
|
|
16388
16637
|
});
|
|
16638
|
+
var STDIN_TIMEOUT_MS = 1e4;
|
|
16389
16639
|
async function readStdinData() {
|
|
16640
|
+
hookLog.debug("Reading stdin data");
|
|
16390
16641
|
return new Promise((resolve, reject) => {
|
|
16391
16642
|
let inputData = "";
|
|
16643
|
+
let settled = false;
|
|
16644
|
+
const timer = setTimeout(() => {
|
|
16645
|
+
if (!settled) {
|
|
16646
|
+
settled = true;
|
|
16647
|
+
process.stdin.destroy();
|
|
16648
|
+
reject(new Error("Timed out reading from stdin"));
|
|
16649
|
+
}
|
|
16650
|
+
}, STDIN_TIMEOUT_MS);
|
|
16392
16651
|
process.stdin.setEncoding("utf-8");
|
|
16393
16652
|
process.stdin.on("data", (chunk) => {
|
|
16394
16653
|
inputData += chunk;
|
|
16395
16654
|
});
|
|
16396
16655
|
process.stdin.on("end", () => {
|
|
16656
|
+
if (settled) return;
|
|
16657
|
+
settled = true;
|
|
16658
|
+
clearTimeout(timer);
|
|
16397
16659
|
try {
|
|
16398
16660
|
const parsedData = JSON.parse(inputData);
|
|
16661
|
+
hookLog.debug("Parsed stdin data", {
|
|
16662
|
+
keys: Object.keys(parsedData)
|
|
16663
|
+
});
|
|
16399
16664
|
resolve(parsedData);
|
|
16400
16665
|
} catch (error) {
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
)
|
|
16405
|
-
);
|
|
16666
|
+
const msg = `Failed to parse JSON from stdin: ${error.message}`;
|
|
16667
|
+
hookLog.error(msg);
|
|
16668
|
+
reject(new Error(msg));
|
|
16406
16669
|
}
|
|
16407
16670
|
});
|
|
16408
16671
|
process.stdin.on("error", (error) => {
|
|
16672
|
+
if (settled) return;
|
|
16673
|
+
settled = true;
|
|
16674
|
+
clearTimeout(timer);
|
|
16675
|
+
hookLog.error("Error reading from stdin", { error: error.message });
|
|
16409
16676
|
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
16410
16677
|
});
|
|
16411
16678
|
});
|
|
@@ -16413,144 +16680,329 @@ async function readStdinData() {
|
|
|
16413
16680
|
function validateHookData(data) {
|
|
16414
16681
|
return HookDataSchema.parse(data);
|
|
16415
16682
|
}
|
|
16416
|
-
function
|
|
16417
|
-
const
|
|
16418
|
-
const
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
}
|
|
16433
|
-
if (hookData.tool_name === "Edit") {
|
|
16434
|
-
const editInput = hookData.tool_input;
|
|
16683
|
+
function generateSyntheticId(sessionId, timestamp, type2, lineIndex) {
|
|
16684
|
+
const input = `${sessionId ?? ""}:${timestamp ?? ""}:${type2 ?? ""}:${lineIndex}`;
|
|
16685
|
+
const hash = createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
16686
|
+
return `synth:${hash}`;
|
|
16687
|
+
}
|
|
16688
|
+
function getCursorKey(transcriptPath) {
|
|
16689
|
+
const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
|
|
16690
|
+
return `cursor.${hash}`;
|
|
16691
|
+
}
|
|
16692
|
+
async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore) {
|
|
16693
|
+
const cursor = sessionStore.get(getCursorKey(transcriptPath));
|
|
16694
|
+
let content;
|
|
16695
|
+
let fileSize;
|
|
16696
|
+
let lineIndexOffset;
|
|
16697
|
+
if (cursor?.byteOffset) {
|
|
16698
|
+
const fh = await open4(transcriptPath, "r");
|
|
16435
16699
|
try {
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16700
|
+
const stat = await fh.stat();
|
|
16701
|
+
fileSize = stat.size;
|
|
16702
|
+
if (cursor.byteOffset >= stat.size) {
|
|
16703
|
+
hookLog.info("No new data in transcript file", { sessionId });
|
|
16704
|
+
return { entries: [], fileSize };
|
|
16705
|
+
}
|
|
16706
|
+
const buf = Buffer.alloc(stat.size - cursor.byteOffset);
|
|
16707
|
+
await fh.read(buf, 0, buf.length, cursor.byteOffset);
|
|
16708
|
+
content = buf.toString("utf-8");
|
|
16709
|
+
} finally {
|
|
16710
|
+
await fh.close();
|
|
16711
|
+
}
|
|
16712
|
+
lineIndexOffset = cursor.byteOffset;
|
|
16713
|
+
hookLog.debug("Read transcript file from offset", {
|
|
16714
|
+
transcriptPath,
|
|
16715
|
+
byteOffset: cursor.byteOffset,
|
|
16716
|
+
bytesRead: content.length
|
|
16717
|
+
});
|
|
16718
|
+
} else {
|
|
16719
|
+
content = await readFile(transcriptPath, "utf-8");
|
|
16720
|
+
fileSize = Buffer.byteLength(content, "utf-8");
|
|
16721
|
+
lineIndexOffset = 0;
|
|
16722
|
+
hookLog.debug("Read full transcript file", {
|
|
16723
|
+
transcriptPath,
|
|
16724
|
+
totalBytes: fileSize
|
|
16725
|
+
});
|
|
16726
|
+
}
|
|
16727
|
+
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
16728
|
+
const parsed = [];
|
|
16729
|
+
let malformedLines = 0;
|
|
16730
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16731
|
+
try {
|
|
16732
|
+
const entry = JSON.parse(lines[i]);
|
|
16733
|
+
const recordId = entry.uuid ?? generateSyntheticId(
|
|
16734
|
+
entry.sessionId,
|
|
16735
|
+
entry.timestamp,
|
|
16736
|
+
entry.type,
|
|
16737
|
+
lineIndexOffset + i
|
|
16439
16738
|
);
|
|
16739
|
+
parsed.push({ ...entry, _recordId: recordId });
|
|
16440
16740
|
} catch {
|
|
16441
|
-
|
|
16741
|
+
malformedLines++;
|
|
16442
16742
|
}
|
|
16443
16743
|
}
|
|
16444
|
-
|
|
16744
|
+
if (malformedLines > 0) {
|
|
16745
|
+
hookLog.warn("Skipped malformed lines", { malformedLines, transcriptPath });
|
|
16746
|
+
}
|
|
16747
|
+
if (!cursor) {
|
|
16748
|
+
hookLog.info("First invocation for session \u2014 uploading all entries", {
|
|
16749
|
+
sessionId,
|
|
16750
|
+
totalEntries: parsed.length
|
|
16751
|
+
});
|
|
16752
|
+
} else {
|
|
16753
|
+
hookLog.info("Resuming from byte offset", {
|
|
16754
|
+
sessionId,
|
|
16755
|
+
byteOffset: cursor.byteOffset,
|
|
16756
|
+
newEntries: parsed.length
|
|
16757
|
+
});
|
|
16758
|
+
}
|
|
16759
|
+
return { entries: parsed, fileSize };
|
|
16760
|
+
}
|
|
16761
|
+
var FILTERED_PROGRESS_SUBTYPES = /* @__PURE__ */ new Set([
|
|
16762
|
+
// Incremental streaming output from running bash commands, emitted every
|
|
16763
|
+
// ~1 second. The final complete output is already captured in the "user"
|
|
16764
|
+
// tool_result entry when the command finishes.
|
|
16765
|
+
"bash_progress",
|
|
16766
|
+
// Records that the hook itself fired. Pure meta-noise — the hook
|
|
16767
|
+
// recording the fact that the hook ran.
|
|
16768
|
+
"hook_progress",
|
|
16769
|
+
// UI-only "waiting" indicator for background tasks (TaskOutput polling).
|
|
16770
|
+
// Contains only a task description and type — no session-relevant data.
|
|
16771
|
+
"waiting_for_task",
|
|
16772
|
+
// MCP tool start/completed timing events. Only unique data is elapsedTimeMs
|
|
16773
|
+
// which can be derived from tool_use/tool_result timestamps.
|
|
16774
|
+
"mcp_progress"
|
|
16775
|
+
]);
|
|
16776
|
+
var FILTERED_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
16777
|
+
// Claude Code's internal undo/restore bookkeeping — tracks which files
|
|
16778
|
+
// have backups. No sessionId, no message, no model or tool data.
|
|
16779
|
+
"file-history-snapshot",
|
|
16780
|
+
// Internal task queue management (enqueue/remove/popAll). Duplicates data
|
|
16781
|
+
// already captured in user messages, agent_progress, and Task tool_use entries.
|
|
16782
|
+
"queue-operation",
|
|
16783
|
+
// Records the last user prompt text before a compaction or session restart.
|
|
16784
|
+
// Redundant — the actual user prompt is already captured in the 'user' entry.
|
|
16785
|
+
"last-prompt"
|
|
16786
|
+
]);
|
|
16787
|
+
var FILTERED_ASSISTANT_TOOLS = /* @__PURE__ */ new Set([
|
|
16788
|
+
// Polls for a sub-agent result. The input is just task_id + boilerplate
|
|
16789
|
+
// (block, timeout). The actual result is captured in the user:tool_result.
|
|
16790
|
+
"TaskOutput",
|
|
16791
|
+
// Discovers available deferred/MCP tools. The input is just a search query.
|
|
16792
|
+
// The discovered tools are captured in the user:tool_result.
|
|
16793
|
+
"ToolSearch"
|
|
16794
|
+
]);
|
|
16795
|
+
function filterEntries(entries) {
|
|
16796
|
+
const filtered = entries.filter((entry) => {
|
|
16797
|
+
const entryType = entry.type ?? "";
|
|
16798
|
+
if (FILTERED_ENTRY_TYPES.has(entryType)) {
|
|
16799
|
+
return false;
|
|
16800
|
+
}
|
|
16801
|
+
if (entryType === "progress") {
|
|
16802
|
+
const data = entry["data"];
|
|
16803
|
+
const subtype = typeof data?.["type"] === "string" ? data["type"] : "";
|
|
16804
|
+
return !FILTERED_PROGRESS_SUBTYPES.has(subtype);
|
|
16805
|
+
}
|
|
16806
|
+
if (entryType === "assistant") {
|
|
16807
|
+
const message = entry["message"];
|
|
16808
|
+
const content = message?.["content"];
|
|
16809
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
16810
|
+
const block = content[0];
|
|
16811
|
+
if (block["type"] === "tool_use" && typeof block["name"] === "string" && FILTERED_ASSISTANT_TOOLS.has(block["name"])) {
|
|
16812
|
+
return false;
|
|
16813
|
+
}
|
|
16814
|
+
}
|
|
16815
|
+
}
|
|
16816
|
+
return true;
|
|
16817
|
+
});
|
|
16818
|
+
return { filtered, filteredOut: entries.length - filtered.length };
|
|
16445
16819
|
}
|
|
16446
|
-
async function
|
|
16447
|
-
const
|
|
16448
|
-
|
|
16449
|
-
|
|
16450
|
-
|
|
16820
|
+
async function cleanupStaleSessions(sessionStore) {
|
|
16821
|
+
const lastCleanup = configStore.get("claudeCode.lastCleanupAt");
|
|
16822
|
+
if (lastCleanup && Date.now() - lastCleanup < CLEANUP_INTERVAL_MS) {
|
|
16823
|
+
return;
|
|
16824
|
+
}
|
|
16825
|
+
const now = Date.now();
|
|
16826
|
+
const prefix = getSessionFilePrefix();
|
|
16827
|
+
const configDir = path13.dirname(sessionStore.path);
|
|
16451
16828
|
try {
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
|
|
16455
|
-
|
|
16456
|
-
|
|
16457
|
-
|
|
16458
|
-
|
|
16459
|
-
|
|
16460
|
-
|
|
16461
|
-
|
|
16462
|
-
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
tool: {
|
|
16468
|
-
name: hookData.tool_name,
|
|
16469
|
-
parameters: JSON.stringify(hookData.tool_input, null, 2),
|
|
16470
|
-
result: JSON.stringify(hookData.tool_response, null, 2),
|
|
16471
|
-
rawArguments: JSON.stringify(hookData.tool_input),
|
|
16472
|
-
accepted: true
|
|
16829
|
+
const files = await readdir(configDir);
|
|
16830
|
+
let deletedCount = 0;
|
|
16831
|
+
for (const file of files) {
|
|
16832
|
+
if (!file.startsWith(prefix) || !file.endsWith(".json")) continue;
|
|
16833
|
+
const filePath = path13.join(configDir, file);
|
|
16834
|
+
try {
|
|
16835
|
+
const content = JSON.parse(await readFile(filePath, "utf-8"));
|
|
16836
|
+
let newest = 0;
|
|
16837
|
+
const cooldown = content[COOLDOWN_KEY];
|
|
16838
|
+
if (cooldown && cooldown > newest) newest = cooldown;
|
|
16839
|
+
const cursors = content["cursor"];
|
|
16840
|
+
if (cursors && typeof cursors === "object") {
|
|
16841
|
+
for (const val of Object.values(cursors)) {
|
|
16842
|
+
const c = val;
|
|
16843
|
+
if (c?.updatedAt && c.updatedAt > newest) newest = c.updatedAt;
|
|
16473
16844
|
}
|
|
16474
16845
|
}
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
inference,
|
|
16485
|
-
tracePayload
|
|
16486
|
-
};
|
|
16487
|
-
}
|
|
16488
|
-
async function getRepositoryUrl2(cwd) {
|
|
16489
|
-
try {
|
|
16490
|
-
const gitService = new GitService(cwd);
|
|
16491
|
-
const isRepo = await gitService.isGitRepository();
|
|
16492
|
-
if (!isRepo) {
|
|
16493
|
-
return null;
|
|
16846
|
+
if (newest > 0 && now - newest > STALE_KEY_MAX_AGE_MS) {
|
|
16847
|
+
await unlink(filePath);
|
|
16848
|
+
deletedCount++;
|
|
16849
|
+
}
|
|
16850
|
+
} catch {
|
|
16851
|
+
}
|
|
16852
|
+
}
|
|
16853
|
+
if (deletedCount > 0) {
|
|
16854
|
+
hookLog.info("Cleaned up stale session files", { deletedCount });
|
|
16494
16855
|
}
|
|
16495
|
-
const remoteUrl = await gitService.getRemoteUrl();
|
|
16496
|
-
const parsed = parseScmURL(remoteUrl);
|
|
16497
|
-
return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
|
|
16498
16856
|
} catch {
|
|
16499
|
-
return null;
|
|
16500
16857
|
}
|
|
16858
|
+
configStore.set("claudeCode.lastCleanupAt", now);
|
|
16501
16859
|
}
|
|
16502
|
-
async function
|
|
16503
|
-
|
|
16504
|
-
const
|
|
16505
|
-
|
|
16860
|
+
async function processAndUploadTranscriptEntries() {
|
|
16861
|
+
hookLog.info("Hook invoked");
|
|
16862
|
+
const rawData = await readStdinData();
|
|
16863
|
+
const hookData = validateHookData(rawData);
|
|
16864
|
+
const sessionStore = createSessionConfigStore(hookData.session_id);
|
|
16865
|
+
await cleanupStaleSessions(sessionStore);
|
|
16866
|
+
const lastRunAt = sessionStore.get(COOLDOWN_KEY);
|
|
16867
|
+
if (lastRunAt && Date.now() - lastRunAt < HOOK_COOLDOWN_MS) {
|
|
16868
|
+
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
16869
|
+
}
|
|
16870
|
+
sessionStore.set(COOLDOWN_KEY, Date.now());
|
|
16871
|
+
const log2 = createScopedHookLog(hookData.cwd);
|
|
16872
|
+
log2.info("Hook data validated", {
|
|
16873
|
+
sessionId: hookData.session_id,
|
|
16874
|
+
toolName: hookData.tool_name,
|
|
16875
|
+
hookEvent: hookData.hook_event_name,
|
|
16876
|
+
cwd: hookData.cwd
|
|
16877
|
+
});
|
|
16506
16878
|
try {
|
|
16507
|
-
await
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16879
|
+
return await processTranscript(hookData, sessionStore, log2);
|
|
16880
|
+
} finally {
|
|
16881
|
+
log2.flushLogs();
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
async function processTranscript(hookData, sessionStore, log2) {
|
|
16885
|
+
const cursorKey = getCursorKey(hookData.transcript_path);
|
|
16886
|
+
const { entries: rawEntries, fileSize } = await readNewTranscriptEntries(
|
|
16887
|
+
hookData.transcript_path,
|
|
16888
|
+
hookData.session_id,
|
|
16889
|
+
sessionStore
|
|
16890
|
+
);
|
|
16891
|
+
if (rawEntries.length === 0) {
|
|
16892
|
+
log2.info("No new entries to upload");
|
|
16893
|
+
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
16894
|
+
}
|
|
16895
|
+
const { filtered: entries, filteredOut } = filterEntries(rawEntries);
|
|
16896
|
+
if (filteredOut > 0) {
|
|
16897
|
+
log2.info("Filtered out noise entries", {
|
|
16898
|
+
filteredOut,
|
|
16899
|
+
remaining: entries.length
|
|
16900
|
+
});
|
|
16901
|
+
}
|
|
16902
|
+
if (entries.length === 0) {
|
|
16903
|
+
log2.info("All entries filtered out, nothing to upload");
|
|
16904
|
+
const lastEntry = rawEntries[rawEntries.length - 1];
|
|
16905
|
+
const prevCursor = sessionStore.get(cursorKey);
|
|
16906
|
+
const cursor = {
|
|
16907
|
+
id: lastEntry._recordId,
|
|
16908
|
+
byteOffset: fileSize,
|
|
16909
|
+
updatedAt: Date.now(),
|
|
16910
|
+
lastModel: prevCursor?.lastModel
|
|
16911
|
+
};
|
|
16912
|
+
sessionStore.set(cursorKey, cursor);
|
|
16913
|
+
return {
|
|
16914
|
+
entriesUploaded: 0,
|
|
16915
|
+
entriesSkipped: filteredOut,
|
|
16916
|
+
errors: 0
|
|
16917
|
+
};
|
|
16918
|
+
}
|
|
16919
|
+
const gqlClient = await log2.timed(
|
|
16920
|
+
"GQL auth",
|
|
16921
|
+
() => getAuthenticatedGQLClient({ isSkipPrompts: true })
|
|
16922
|
+
);
|
|
16923
|
+
const cursorForModel = sessionStore.get(cursorKey);
|
|
16924
|
+
let lastSeenModel = cursorForModel?.lastModel ?? null;
|
|
16925
|
+
const records = entries.map((entry) => {
|
|
16926
|
+
const { _recordId, ...rawEntry } = entry;
|
|
16927
|
+
const message = rawEntry["message"];
|
|
16928
|
+
const currentModel = message?.["model"] ?? null;
|
|
16929
|
+
if (currentModel && currentModel !== "<synthetic>") {
|
|
16930
|
+
lastSeenModel = currentModel;
|
|
16931
|
+
} else if (lastSeenModel && !currentModel) {
|
|
16932
|
+
if (message) {
|
|
16933
|
+
message["model"] = lastSeenModel;
|
|
16934
|
+
} else {
|
|
16935
|
+
rawEntry["message"] = { model: lastSeenModel };
|
|
16936
|
+
}
|
|
16937
|
+
}
|
|
16938
|
+
return {
|
|
16939
|
+
platform: "CLAUDE_CODE" /* ClaudeCode */,
|
|
16940
|
+
recordId: _recordId,
|
|
16941
|
+
recordTimestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
16513
16942
|
blameType: "CHAT" /* Chat */,
|
|
16514
|
-
|
|
16515
|
-
|
|
16943
|
+
rawData: rawEntry
|
|
16944
|
+
};
|
|
16945
|
+
});
|
|
16946
|
+
log2.info("Uploading batch", {
|
|
16947
|
+
count: records.length,
|
|
16948
|
+
skipped: filteredOut,
|
|
16949
|
+
firstRecordId: records[0]?.recordId,
|
|
16950
|
+
lastRecordId: records[records.length - 1]?.recordId
|
|
16951
|
+
});
|
|
16952
|
+
const result = await log2.timed(
|
|
16953
|
+
"Batch upload",
|
|
16954
|
+
() => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd)
|
|
16955
|
+
);
|
|
16956
|
+
if (result.ok) {
|
|
16957
|
+
const lastRawEntry = rawEntries[rawEntries.length - 1];
|
|
16958
|
+
const cursor = {
|
|
16959
|
+
id: lastRawEntry._recordId,
|
|
16960
|
+
byteOffset: fileSize,
|
|
16961
|
+
updatedAt: Date.now(),
|
|
16962
|
+
lastModel: lastSeenModel ?? void 0
|
|
16963
|
+
};
|
|
16964
|
+
sessionStore.set(cursorKey, cursor);
|
|
16965
|
+
log2.heartbeat("Upload ok", {
|
|
16966
|
+
entriesUploaded: entries.length,
|
|
16967
|
+
entriesSkipped: filteredOut
|
|
16516
16968
|
});
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
);
|
|
16523
|
-
uploadSuccess = false;
|
|
16969
|
+
return {
|
|
16970
|
+
entriesUploaded: entries.length,
|
|
16971
|
+
entriesSkipped: filteredOut,
|
|
16972
|
+
errors: 0
|
|
16973
|
+
};
|
|
16524
16974
|
}
|
|
16975
|
+
log2.error("Batch upload had errors", { errors: result.errors });
|
|
16525
16976
|
return {
|
|
16526
|
-
|
|
16527
|
-
|
|
16977
|
+
entriesUploaded: 0,
|
|
16978
|
+
entriesSkipped: filteredOut,
|
|
16979
|
+
errors: entries.length
|
|
16528
16980
|
};
|
|
16529
16981
|
}
|
|
16530
16982
|
|
|
16531
16983
|
// src/features/claude_code/install_hook.ts
|
|
16532
|
-
import
|
|
16533
|
-
import
|
|
16984
|
+
import fsPromises4 from "fs/promises";
|
|
16985
|
+
import os5 from "os";
|
|
16534
16986
|
import path14 from "path";
|
|
16535
16987
|
import chalk11 from "chalk";
|
|
16536
|
-
var CLAUDE_SETTINGS_PATH = path14.join(
|
|
16988
|
+
var CLAUDE_SETTINGS_PATH = path14.join(os5.homedir(), ".claude", "settings.json");
|
|
16537
16989
|
async function claudeSettingsExists() {
|
|
16538
16990
|
try {
|
|
16539
|
-
await
|
|
16991
|
+
await fsPromises4.access(CLAUDE_SETTINGS_PATH);
|
|
16540
16992
|
return true;
|
|
16541
16993
|
} catch {
|
|
16542
16994
|
return false;
|
|
16543
16995
|
}
|
|
16544
16996
|
}
|
|
16545
16997
|
async function readClaudeSettings() {
|
|
16546
|
-
const settingsContent = await
|
|
16998
|
+
const settingsContent = await fsPromises4.readFile(
|
|
16547
16999
|
CLAUDE_SETTINGS_PATH,
|
|
16548
17000
|
"utf-8"
|
|
16549
17001
|
);
|
|
16550
17002
|
return JSON.parse(settingsContent);
|
|
16551
17003
|
}
|
|
16552
17004
|
async function writeClaudeSettings(settings) {
|
|
16553
|
-
await
|
|
17005
|
+
await fsPromises4.writeFile(
|
|
16554
17006
|
CLAUDE_SETTINGS_PATH,
|
|
16555
17007
|
JSON.stringify(settings, null, 2),
|
|
16556
17008
|
"utf-8"
|
|
@@ -16593,7 +17045,8 @@ async function installMobbHooks(options = {}) {
|
|
|
16593
17045
|
}
|
|
16594
17046
|
}
|
|
16595
17047
|
const mobbHookConfig = {
|
|
16596
|
-
matcher:
|
|
17048
|
+
// Empty matcher = match all tools (Claude Code hook spec: empty string matches every PostToolUse event)
|
|
17049
|
+
matcher: "",
|
|
16597
17050
|
hooks: [
|
|
16598
17051
|
{
|
|
16599
17052
|
type: "command",
|
|
@@ -16602,7 +17055,7 @@ async function installMobbHooks(options = {}) {
|
|
|
16602
17055
|
]
|
|
16603
17056
|
};
|
|
16604
17057
|
const existingHookIndex = settings.hooks.PostToolUse.findIndex(
|
|
16605
|
-
(hook) => hook.
|
|
17058
|
+
(hook) => hook.hooks.some(
|
|
16606
17059
|
(h) => h.command?.includes("mobbdev@latest claude-code-process-hook")
|
|
16607
17060
|
)
|
|
16608
17061
|
);
|
|
@@ -16652,51 +17105,45 @@ var claudeCodeInstallHookHandler = async (argv) => {
|
|
|
16652
17105
|
}
|
|
16653
17106
|
};
|
|
16654
17107
|
var claudeCodeProcessHookHandler = async () => {
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
const userPrompts = tracePayload.prompts.filter(
|
|
16663
|
-
(p) => p.type === "USER_PROMPT"
|
|
16664
|
-
);
|
|
16665
|
-
const assistantResponses = tracePayload.prompts.filter(
|
|
16666
|
-
(p) => p.type === "AI_RESPONSE"
|
|
16667
|
-
);
|
|
16668
|
-
const aiThinking = tracePayload.prompts.filter(
|
|
16669
|
-
(p) => p.type === "AI_THINKING"
|
|
16670
|
-
);
|
|
16671
|
-
console.log("Conversation context extracted:");
|
|
16672
|
-
console.log("- User prompts:", userPrompts.length);
|
|
16673
|
-
console.log("- Assistant responses:", assistantResponses.length);
|
|
16674
|
-
console.log("- AI thinking entries:", aiThinking.length);
|
|
16675
|
-
console.log("- Model:", tracePayload.model);
|
|
16676
|
-
const totalInputTokens = tracePayload.prompts.reduce(
|
|
16677
|
-
(sum, p) => sum + (p.tokens?.inputCount || 0),
|
|
16678
|
-
0
|
|
16679
|
-
);
|
|
16680
|
-
const totalOutputTokens = tracePayload.prompts.reduce(
|
|
16681
|
-
(sum, p) => sum + (p.tokens?.outputCount || 0),
|
|
16682
|
-
0
|
|
16683
|
-
);
|
|
16684
|
-
console.log("- Input tokens:", totalInputTokens);
|
|
16685
|
-
console.log("- Output tokens:", totalOutputTokens);
|
|
16686
|
-
console.log("Trace data formatted:");
|
|
16687
|
-
console.log("- Prompt items:", tracePayload.prompts.length);
|
|
16688
|
-
console.log("- Model:", tracePayload.model);
|
|
16689
|
-
console.log("- Tool:", tracePayload.tool);
|
|
16690
|
-
console.log("- Response time:", tracePayload.responseTime);
|
|
16691
|
-
console.log("- Upload success:", uploadSuccess ? "\u2705" : "\u274C");
|
|
16692
|
-
if (uploadSuccess) {
|
|
16693
|
-
console.log("\u2705 Claude Code trace uploaded successfully to Mobb backend");
|
|
17108
|
+
async function flushAndExit(code) {
|
|
17109
|
+
try {
|
|
17110
|
+
flushLogs();
|
|
17111
|
+
await flushDdLogs();
|
|
17112
|
+
} catch {
|
|
17113
|
+
} finally {
|
|
17114
|
+
process.exit(code);
|
|
16694
17115
|
}
|
|
16695
|
-
|
|
17116
|
+
}
|
|
17117
|
+
process.on("uncaughtException", (error) => {
|
|
17118
|
+
hookLog.error("Uncaught exception in hook", {
|
|
17119
|
+
error: String(error),
|
|
17120
|
+
stack: error.stack
|
|
17121
|
+
});
|
|
17122
|
+
void flushAndExit(1);
|
|
17123
|
+
});
|
|
17124
|
+
process.on("unhandledRejection", (reason) => {
|
|
17125
|
+
hookLog.error("Unhandled rejection in hook", {
|
|
17126
|
+
error: String(reason),
|
|
17127
|
+
stack: reason instanceof Error ? reason.stack : void 0
|
|
17128
|
+
});
|
|
17129
|
+
void flushAndExit(1);
|
|
17130
|
+
});
|
|
17131
|
+
let exitCode = 0;
|
|
17132
|
+
try {
|
|
17133
|
+
const result = await processAndUploadTranscriptEntries();
|
|
17134
|
+
hookLog.info("Claude Code upload complete", {
|
|
17135
|
+
entriesUploaded: result.entriesUploaded,
|
|
17136
|
+
entriesSkipped: result.entriesSkipped,
|
|
17137
|
+
errors: result.errors
|
|
17138
|
+
});
|
|
16696
17139
|
} catch (error) {
|
|
16697
|
-
|
|
16698
|
-
|
|
17140
|
+
exitCode = 1;
|
|
17141
|
+
hookLog.error("Failed to process Claude Code hook", {
|
|
17142
|
+
error: String(error),
|
|
17143
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
17144
|
+
});
|
|
16699
17145
|
}
|
|
17146
|
+
await flushAndExit(exitCode);
|
|
16700
17147
|
};
|
|
16701
17148
|
|
|
16702
17149
|
// src/mcp/core/McpServer.ts
|
|
@@ -16710,7 +17157,7 @@ import {
|
|
|
16710
17157
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
16711
17158
|
|
|
16712
17159
|
// src/mcp/Logger.ts
|
|
16713
|
-
import
|
|
17160
|
+
import Configstore3 from "configstore";
|
|
16714
17161
|
|
|
16715
17162
|
// src/mcp/services/WorkspaceService.ts
|
|
16716
17163
|
var WorkspaceService = class {
|
|
@@ -16796,7 +17243,7 @@ var Logger = class {
|
|
|
16796
17243
|
__publicField(this, "lastKnownPath", null);
|
|
16797
17244
|
this.host = WorkspaceService.getHost();
|
|
16798
17245
|
this.unknownPathSuffix = Math.floor(1e3 + Math.random() * 9e3).toString();
|
|
16799
|
-
this.mobbConfigStore = new
|
|
17246
|
+
this.mobbConfigStore = new Configstore3("mobb-logs", {});
|
|
16800
17247
|
this.mobbConfigStore.set("version", packageJson.version);
|
|
16801
17248
|
}
|
|
16802
17249
|
/**
|
|
@@ -16852,12 +17299,12 @@ var Logger = class {
|
|
|
16852
17299
|
this.mobbConfigStore.set(currentPath, [...logs, logMessage]);
|
|
16853
17300
|
}
|
|
16854
17301
|
};
|
|
16855
|
-
var
|
|
16856
|
-
var logInfo = (message, data) =>
|
|
16857
|
-
var logError = (message, data) =>
|
|
16858
|
-
var logWarn = (message, data) =>
|
|
16859
|
-
var logDebug = (message, data) =>
|
|
16860
|
-
var log =
|
|
17302
|
+
var logger2 = new Logger();
|
|
17303
|
+
var logInfo = (message, data) => logger2.log(message, "info", data);
|
|
17304
|
+
var logError = (message, data) => logger2.log(message, "error", data);
|
|
17305
|
+
var logWarn = (message, data) => logger2.log(message, "warn", data);
|
|
17306
|
+
var logDebug = (message, data) => logger2.log(message, "debug", data);
|
|
17307
|
+
var log = logger2.log.bind(logger2);
|
|
16861
17308
|
|
|
16862
17309
|
// src/mcp/services/McpGQLClient.ts
|
|
16863
17310
|
import crypto2 from "crypto";
|
|
@@ -17579,7 +18026,7 @@ async function createAuthenticatedMcpGQLClient({
|
|
|
17579
18026
|
// src/mcp/services/McpUsageService/host.ts
|
|
17580
18027
|
import { execSync as execSync2 } from "child_process";
|
|
17581
18028
|
import fs13 from "fs";
|
|
17582
|
-
import
|
|
18029
|
+
import os6 from "os";
|
|
17583
18030
|
import path15 from "path";
|
|
17584
18031
|
var IDEs = ["cursor", "windsurf", "webstorm", "vscode", "claude"];
|
|
17585
18032
|
var runCommand = (cmd) => {
|
|
@@ -17594,7 +18041,7 @@ var gitInfo = {
|
|
|
17594
18041
|
email: runCommand("git config user.email")
|
|
17595
18042
|
};
|
|
17596
18043
|
var getClaudeWorkspacePaths = () => {
|
|
17597
|
-
const home =
|
|
18044
|
+
const home = os6.homedir();
|
|
17598
18045
|
const claudeIdePath = path15.join(home, ".claude", "ide");
|
|
17599
18046
|
const workspacePaths = [];
|
|
17600
18047
|
if (!fs13.existsSync(claudeIdePath)) {
|
|
@@ -17623,7 +18070,7 @@ var getClaudeWorkspacePaths = () => {
|
|
|
17623
18070
|
return workspacePaths;
|
|
17624
18071
|
};
|
|
17625
18072
|
var getMCPConfigPaths = (hostName) => {
|
|
17626
|
-
const home =
|
|
18073
|
+
const home = os6.homedir();
|
|
17627
18074
|
const currentDir = process.env["WORKSPACE_FOLDER_PATHS"] || process.env["PWD"] || process.cwd();
|
|
17628
18075
|
switch (hostName.toLowerCase()) {
|
|
17629
18076
|
case "cursor":
|
|
@@ -17713,7 +18160,7 @@ var readMCPConfig = (hostName) => {
|
|
|
17713
18160
|
};
|
|
17714
18161
|
var getRunningProcesses = () => {
|
|
17715
18162
|
try {
|
|
17716
|
-
return
|
|
18163
|
+
return os6.platform() === "win32" ? execSync2("tasklist", { encoding: "utf8" }) : execSync2("ps aux", { encoding: "utf8" });
|
|
17717
18164
|
} catch {
|
|
17718
18165
|
return "";
|
|
17719
18166
|
}
|
|
@@ -17788,7 +18235,7 @@ var versionCommands = {
|
|
|
17788
18235
|
}
|
|
17789
18236
|
};
|
|
17790
18237
|
var getProcessInfo = (pid) => {
|
|
17791
|
-
const platform2 =
|
|
18238
|
+
const platform2 = os6.platform();
|
|
17792
18239
|
try {
|
|
17793
18240
|
if (platform2 === "linux" || platform2 === "darwin") {
|
|
17794
18241
|
const output = execSync2(`ps -o pid=,ppid=,comm= -p ${pid}`, {
|
|
@@ -17907,7 +18354,7 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
17907
18354
|
const config2 = allConfigs[ide] || null;
|
|
17908
18355
|
const ideName = ide.charAt(0).toUpperCase() + ide.slice(1) || "Unknown";
|
|
17909
18356
|
let ideVersion = "Unknown";
|
|
17910
|
-
const platform2 =
|
|
18357
|
+
const platform2 = os6.platform();
|
|
17911
18358
|
const cmds = versionCommands[ideName]?.[platform2] ?? [];
|
|
17912
18359
|
for (const cmd of cmds) {
|
|
17913
18360
|
try {
|
|
@@ -17939,15 +18386,15 @@ var getHostInfo = (additionalMcpList) => {
|
|
|
17939
18386
|
};
|
|
17940
18387
|
|
|
17941
18388
|
// src/mcp/services/McpUsageService/McpUsageService.ts
|
|
17942
|
-
import
|
|
17943
|
-
import
|
|
18389
|
+
import fetch6 from "node-fetch";
|
|
18390
|
+
import os8 from "os";
|
|
17944
18391
|
import { v4 as uuidv42, v5 as uuidv5 } from "uuid";
|
|
17945
18392
|
init_configs();
|
|
17946
18393
|
|
|
17947
18394
|
// src/mcp/services/McpUsageService/system.ts
|
|
17948
18395
|
init_configs();
|
|
17949
18396
|
import fs14 from "fs";
|
|
17950
|
-
import
|
|
18397
|
+
import os7 from "os";
|
|
17951
18398
|
import path16 from "path";
|
|
17952
18399
|
var MAX_DEPTH = 2;
|
|
17953
18400
|
var patterns = ["mcp", "claude"];
|
|
@@ -17982,8 +18429,8 @@ var searchDir = async (dir, depth = 0) => {
|
|
|
17982
18429
|
};
|
|
17983
18430
|
var findSystemMCPConfigs = async () => {
|
|
17984
18431
|
try {
|
|
17985
|
-
const home =
|
|
17986
|
-
const platform2 =
|
|
18432
|
+
const home = os7.homedir();
|
|
18433
|
+
const platform2 = os7.platform();
|
|
17987
18434
|
const knownDirs = platform2 === "win32" ? [
|
|
17988
18435
|
path16.join(home, ".cursor"),
|
|
17989
18436
|
path16.join(home, "Documents"),
|
|
@@ -18055,7 +18502,7 @@ var McpUsageService = class {
|
|
|
18055
18502
|
generateHostId() {
|
|
18056
18503
|
const stored = configStore.get(this.configKey);
|
|
18057
18504
|
if (stored?.mcpHostId) return stored.mcpHostId;
|
|
18058
|
-
const interfaces =
|
|
18505
|
+
const interfaces = os8.networkInterfaces();
|
|
18059
18506
|
const macs = [];
|
|
18060
18507
|
for (const iface of Object.values(interfaces)) {
|
|
18061
18508
|
if (!iface) continue;
|
|
@@ -18063,7 +18510,7 @@ var McpUsageService = class {
|
|
|
18063
18510
|
if (net.mac && net.mac !== "00:00:00:00:00:00") macs.push(net.mac);
|
|
18064
18511
|
}
|
|
18065
18512
|
}
|
|
18066
|
-
const macString = macs.length ? macs.sort().join(",") : `${
|
|
18513
|
+
const macString = macs.length ? macs.sort().join(",") : `${os8.hostname()}-${uuidv42()}`;
|
|
18067
18514
|
const hostId = uuidv5(macString, uuidv5.DNS);
|
|
18068
18515
|
logDebug("[UsageService] Generated new host ID", { hostId });
|
|
18069
18516
|
return hostId;
|
|
@@ -18086,7 +18533,7 @@ var McpUsageService = class {
|
|
|
18086
18533
|
mcpHostId,
|
|
18087
18534
|
organizationId,
|
|
18088
18535
|
mcpVersion: packageJson.version,
|
|
18089
|
-
mcpOsName:
|
|
18536
|
+
mcpOsName: os8.platform(),
|
|
18090
18537
|
mcps: JSON.stringify(mcps),
|
|
18091
18538
|
status,
|
|
18092
18539
|
userName: user.name,
|
|
@@ -18126,7 +18573,7 @@ var McpUsageService = class {
|
|
|
18126
18573
|
}
|
|
18127
18574
|
logDebug("[UsageService] Before", { usageData });
|
|
18128
18575
|
try {
|
|
18129
|
-
const res = await
|
|
18576
|
+
const res = await fetch6(this.REST_API_URL, {
|
|
18130
18577
|
method: "POST",
|
|
18131
18578
|
headers: {
|
|
18132
18579
|
Accept: "application/json"
|
|
@@ -20407,22 +20854,22 @@ For a complete security audit workflow, use the \`full-security-audit\` prompt.
|
|
|
20407
20854
|
|
|
20408
20855
|
// src/mcp/services/McpDetectionService/CursorMcpDetectionService.ts
|
|
20409
20856
|
import * as fs17 from "fs";
|
|
20410
|
-
import * as
|
|
20857
|
+
import * as os10 from "os";
|
|
20411
20858
|
import * as path18 from "path";
|
|
20412
20859
|
|
|
20413
20860
|
// src/mcp/services/McpDetectionService/BaseMcpDetectionService.ts
|
|
20414
20861
|
init_configs();
|
|
20415
20862
|
import * as fs16 from "fs";
|
|
20416
|
-
import
|
|
20863
|
+
import fetch7 from "node-fetch";
|
|
20417
20864
|
import * as path17 from "path";
|
|
20418
20865
|
|
|
20419
20866
|
// src/mcp/services/McpDetectionService/McpDetectionServiceUtils.ts
|
|
20420
20867
|
import * as fs15 from "fs";
|
|
20421
|
-
import * as
|
|
20868
|
+
import * as os9 from "os";
|
|
20422
20869
|
|
|
20423
20870
|
// src/mcp/services/McpDetectionService/VscodeMcpDetectionService.ts
|
|
20424
20871
|
import * as fs18 from "fs";
|
|
20425
|
-
import * as
|
|
20872
|
+
import * as os11 from "os";
|
|
20426
20873
|
import * as path19 from "path";
|
|
20427
20874
|
|
|
20428
20875
|
// src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesTool.ts
|
|
@@ -21980,7 +22427,7 @@ import {
|
|
|
21980
22427
|
mkdirSync,
|
|
21981
22428
|
readFileSync as readFileSync3,
|
|
21982
22429
|
unlinkSync,
|
|
21983
|
-
writeFileSync
|
|
22430
|
+
writeFileSync as writeFileSync2
|
|
21984
22431
|
} from "fs";
|
|
21985
22432
|
import fs22 from "fs/promises";
|
|
21986
22433
|
import parseDiff2 from "parse-diff";
|
|
@@ -22201,7 +22648,7 @@ var PatchApplicationService = class {
|
|
|
22201
22648
|
}
|
|
22202
22649
|
const dirPath = path22.dirname(normalizedFilePath);
|
|
22203
22650
|
mkdirSync(dirPath, { recursive: true });
|
|
22204
|
-
|
|
22651
|
+
writeFileSync2(normalizedFilePath, finalContent, "utf8");
|
|
22205
22652
|
return normalizedFilePath;
|
|
22206
22653
|
}
|
|
22207
22654
|
static resolvePathWithinRepo({
|
|
@@ -24867,18 +25314,18 @@ async function getGrpcClient(port, csrf3) {
|
|
|
24867
25314
|
|
|
24868
25315
|
// src/features/codeium_intellij/parse_intellij_logs.ts
|
|
24869
25316
|
import fs25 from "fs";
|
|
24870
|
-
import
|
|
25317
|
+
import os12 from "os";
|
|
24871
25318
|
import path25 from "path";
|
|
24872
25319
|
function getLogsDir() {
|
|
24873
25320
|
if (process.platform === "darwin") {
|
|
24874
|
-
return path25.join(
|
|
25321
|
+
return path25.join(os12.homedir(), "Library/Logs/JetBrains");
|
|
24875
25322
|
} else if (process.platform === "win32") {
|
|
24876
25323
|
return path25.join(
|
|
24877
|
-
process.env["LOCALAPPDATA"] || path25.join(
|
|
25324
|
+
process.env["LOCALAPPDATA"] || path25.join(os12.homedir(), "AppData/Local"),
|
|
24878
25325
|
"JetBrains"
|
|
24879
25326
|
);
|
|
24880
25327
|
} else {
|
|
24881
|
-
return path25.join(
|
|
25328
|
+
return path25.join(os12.homedir(), ".cache/JetBrains");
|
|
24882
25329
|
}
|
|
24883
25330
|
}
|
|
24884
25331
|
function parseIdeLogDir(ideLogDir) {
|
|
@@ -24936,7 +25383,7 @@ function findRunningCodeiumLanguageServers() {
|
|
|
24936
25383
|
var HookDataSchema2 = z47.object({
|
|
24937
25384
|
trajectory_id: z47.string()
|
|
24938
25385
|
});
|
|
24939
|
-
async function
|
|
25386
|
+
async function processAndUploadHookData() {
|
|
24940
25387
|
const tracePayload = await getTraceDataForHook();
|
|
24941
25388
|
if (!tracePayload) {
|
|
24942
25389
|
console.warn("Warning: Failed to retrieve chat data.");
|
|
@@ -25100,17 +25547,17 @@ function processChatStepCodeAction(step) {
|
|
|
25100
25547
|
}
|
|
25101
25548
|
|
|
25102
25549
|
// src/features/codeium_intellij/install_hook.ts
|
|
25103
|
-
import
|
|
25104
|
-
import
|
|
25550
|
+
import fsPromises5 from "fs/promises";
|
|
25551
|
+
import os13 from "os";
|
|
25105
25552
|
import path26 from "path";
|
|
25106
25553
|
import chalk14 from "chalk";
|
|
25107
25554
|
function getCodeiumHooksPath() {
|
|
25108
|
-
return path26.join(
|
|
25555
|
+
return path26.join(os13.homedir(), ".codeium", "hooks.json");
|
|
25109
25556
|
}
|
|
25110
25557
|
async function readCodeiumHooks() {
|
|
25111
25558
|
const hooksPath = getCodeiumHooksPath();
|
|
25112
25559
|
try {
|
|
25113
|
-
const content = await
|
|
25560
|
+
const content = await fsPromises5.readFile(hooksPath, "utf-8");
|
|
25114
25561
|
return JSON.parse(content);
|
|
25115
25562
|
} catch {
|
|
25116
25563
|
return {};
|
|
@@ -25119,8 +25566,8 @@ async function readCodeiumHooks() {
|
|
|
25119
25566
|
async function writeCodeiumHooks(config2) {
|
|
25120
25567
|
const hooksPath = getCodeiumHooksPath();
|
|
25121
25568
|
const dir = path26.dirname(hooksPath);
|
|
25122
|
-
await
|
|
25123
|
-
await
|
|
25569
|
+
await fsPromises5.mkdir(dir, { recursive: true });
|
|
25570
|
+
await fsPromises5.writeFile(
|
|
25124
25571
|
hooksPath,
|
|
25125
25572
|
JSON.stringify(config2, null, 2),
|
|
25126
25573
|
"utf-8"
|
|
@@ -25208,7 +25655,7 @@ var windsurfIntellijInstallHookHandler = async (argv) => {
|
|
|
25208
25655
|
};
|
|
25209
25656
|
var windsurfIntellijProcessHookHandler = async () => {
|
|
25210
25657
|
try {
|
|
25211
|
-
await
|
|
25658
|
+
await processAndUploadHookData();
|
|
25212
25659
|
process.exit(0);
|
|
25213
25660
|
} catch (error) {
|
|
25214
25661
|
console.error("Failed to process Windsurf IntelliJ hook data:", error);
|