@fml-inc/fml 0.8.0-rc.1 → 0.8.0-rc.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/.claude-plugin/plugin.json +1 -1
- package/README.md +29 -16
- package/dist/{chunk-CK5MEL5J.js → chunk-3KISRT57.js} +1 -1
- package/dist/{chunk-HUR2HCUG.js → chunk-7DWFJENY.js} +2 -2
- package/dist/{chunk-T5YVLSUC.js → chunk-7V7G7NVF.js} +4 -0
- package/dist/{chunk-7DR5X7XI.js → chunk-A6HAEVJX.js} +136 -52
- package/dist/{chunk-F66IBM3Z.js → chunk-DZKYW327.js} +2 -2
- package/dist/{chunk-I4GZPTBM.js → chunk-ENW2GJNO.js} +1 -1
- package/dist/{chunk-DEMVOVVY.js → chunk-KBT5RMAU.js} +263 -32
- package/dist/{chunk-P3QLARXE.js → chunk-R45CWWZO.js} +3 -3
- package/dist/{chunk-JXSSPQ5B.js → chunk-YSU5CS2V.js} +1 -1
- package/dist/cli.js +1708 -1351
- package/dist/hook-handler.js +299 -42
- package/dist/mcp/server.js +7 -7
- package/dist/observer-server.js +8 -8
- package/dist/pi/extension.js +350 -0
- package/dist/sync-cycle.js +3 -3
- package/dist/{token-store-R7WXEPLI.js → token-store-BT5THIAJ.js} +1 -1
- package/package.json +6 -4
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createFmlClient,
|
|
4
4
|
getAuthenticatedClient
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-R45CWWZO.js";
|
|
6
6
|
import {
|
|
7
7
|
OBSERVER_HEARTBEAT_STALE_MS,
|
|
8
8
|
getProcessIdentity,
|
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
isPidAlive,
|
|
11
11
|
readObserverStateStatus,
|
|
12
12
|
removeObserverStateIfOwned
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-7DWFJENY.js";
|
|
14
14
|
import {
|
|
15
15
|
readJsonFile,
|
|
16
16
|
setHookCaptureDisabled,
|
|
17
17
|
writeJsonFile,
|
|
18
18
|
writeTextFileAtomic
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ENW2GJNO.js";
|
|
20
20
|
import {
|
|
21
21
|
CODEX_HOOK_EVENTS,
|
|
22
22
|
GEMINI_HOOK_EVENTS,
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
isObserverHealth,
|
|
25
25
|
readHookInboxStatus,
|
|
26
26
|
readObserverAuthToken
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
27
|
+
} from "./chunk-KBT5RMAU.js";
|
|
28
|
+
import "./chunk-7V7G7NVF.js";
|
|
29
29
|
import {
|
|
30
30
|
claudeConfigDirectory,
|
|
31
31
|
createCliRawSync,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
initializeSyncSettings,
|
|
35
35
|
resolvePackageRoot,
|
|
36
36
|
runRawSyncOnce
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-YSU5CS2V.js";
|
|
38
38
|
import {
|
|
39
39
|
DEFAULT_PROD_URL,
|
|
40
40
|
DEFAULT_TARGET_NAME,
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
storeServiceRefreshToken,
|
|
77
77
|
writeEnvConfig,
|
|
78
78
|
writeTokens
|
|
79
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-A6HAEVJX.js";
|
|
80
80
|
import "./chunk-MLKGABMK.js";
|
|
81
81
|
|
|
82
82
|
// src/cli.ts
|
|
@@ -124,7 +124,7 @@ var POLL_INTERVAL_MS = 50;
|
|
|
124
124
|
var LOG_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
125
125
|
var LOG_ROTATE_FILES = 3;
|
|
126
126
|
function expectedObserverVersion() {
|
|
127
|
-
return true ? "0.8.0-rc.
|
|
127
|
+
return true ? "0.8.0-rc.3+246af775" : "dev";
|
|
128
128
|
}
|
|
129
129
|
function delay(ms) {
|
|
130
130
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -776,20 +776,58 @@ function probeLocalStorage() {
|
|
|
776
776
|
});
|
|
777
777
|
}
|
|
778
778
|
}
|
|
779
|
+
function humanCheckResult(result) {
|
|
780
|
+
switch (result.label) {
|
|
781
|
+
case "Auth":
|
|
782
|
+
return { label: "Account", detail: result.detail };
|
|
783
|
+
case "Environment": {
|
|
784
|
+
const environmentName = result.detail.replace(
|
|
785
|
+
/ has no backend URL$/u,
|
|
786
|
+
""
|
|
787
|
+
);
|
|
788
|
+
return {
|
|
789
|
+
label: "Environment",
|
|
790
|
+
detail: result.status === "ok" ? result.detail.split(" \u2192 ")[0] ?? result.detail : `No service URL is configured for ${environmentName}. Run \`fml env <name-or-url>\`.`
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
case "Organization":
|
|
794
|
+
return { label: "Organization", detail: result.detail };
|
|
795
|
+
case "Local data":
|
|
796
|
+
return { label: "Local storage", detail: result.detail };
|
|
797
|
+
case "Database lease":
|
|
798
|
+
return {
|
|
799
|
+
label: "Runtime state",
|
|
800
|
+
detail: result.detail.replace("observer", "FML service")
|
|
801
|
+
};
|
|
802
|
+
case "Raw sync":
|
|
803
|
+
return {
|
|
804
|
+
label: "Agent activity sync",
|
|
805
|
+
detail: result.detail.replace("observer state", "FML service state").replace("raw sync", "agent activity sync")
|
|
806
|
+
};
|
|
807
|
+
case "API":
|
|
808
|
+
return { label: "FML API", detail: result.detail };
|
|
809
|
+
case "Observer":
|
|
810
|
+
return {
|
|
811
|
+
label: "FML service",
|
|
812
|
+
detail: result.detail.replace("observer", "FML service").replace("continuous sync", "agent activity sync").replace("raw sync", "agent activity sync")
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
779
817
|
function reportResult(result, spinner) {
|
|
780
|
-
const text = `${result.label} \u2014 ${result.detail}`;
|
|
781
818
|
if (spinner) {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
if (result.status === "fail") spinner.fail(text);
|
|
785
|
-
return;
|
|
819
|
+
spinner.indent = 0;
|
|
820
|
+
spinner.stop();
|
|
786
821
|
}
|
|
822
|
+
const humanResult = humanCheckResult(result);
|
|
823
|
+
if (!humanResult) return;
|
|
787
824
|
const icon = result.status === "ok" ? pc.green("\u2713") : result.status === "warn" ? pc.yellow("!") : pc.red("\u2717");
|
|
788
|
-
console.log(` ${icon} ${
|
|
825
|
+
console.log(` ${icon} ${humanResult.label} \u2014 ${humanResult.detail}`);
|
|
789
826
|
}
|
|
790
827
|
async function handleDoctor(opts) {
|
|
791
828
|
const environment = getActiveEnv();
|
|
792
829
|
const isTTY = !opts.json && process.stdout.isTTY === true;
|
|
830
|
+
if (!opts.json) console.log("FML doctor\n");
|
|
793
831
|
const checks = [];
|
|
794
832
|
const startSpinner = (label) => isTTY ? ora({ text: `Checking ${label}...`, indent: 2 }).start() : null;
|
|
795
833
|
const record = (result, spinner) => {
|
|
@@ -909,7 +947,7 @@ async function handleDoctor(opts) {
|
|
|
909
947
|
}
|
|
910
948
|
}
|
|
911
949
|
{
|
|
912
|
-
const spinner = startSpinner("
|
|
950
|
+
const spinner = startSpinner("agent activity sync");
|
|
913
951
|
try {
|
|
914
952
|
try {
|
|
915
953
|
probeDurableDirectory(FML_OBSERVER_DIR, {
|
|
@@ -977,7 +1015,7 @@ async function handleDoctor(opts) {
|
|
|
977
1015
|
}
|
|
978
1016
|
}
|
|
979
1017
|
{
|
|
980
|
-
const spinner = startSpinner("
|
|
1018
|
+
const spinner = startSpinner("FML service");
|
|
981
1019
|
const rawSync2 = createRawArtifactSync({ stateDir: FML_OBSERVER_DIR });
|
|
982
1020
|
const syncEnabled = rawSync2.getSettings().enabled;
|
|
983
1021
|
const observer = await readObserverRuntimeStatus();
|
|
@@ -1018,12 +1056,15 @@ async function handleDoctor(opts) {
|
|
|
1018
1056
|
console.log(`
|
|
1019
1057
|
${parts.join(", ")}
|
|
1020
1058
|
`);
|
|
1059
|
+
if (failed > 0) {
|
|
1060
|
+
console.log("Fix the failed checks above, then run `fml doctor` again.");
|
|
1061
|
+
}
|
|
1021
1062
|
}
|
|
1022
1063
|
|
|
1023
1064
|
// src/commands/install.ts
|
|
1024
|
-
import
|
|
1065
|
+
import fs10 from "fs";
|
|
1025
1066
|
import os5 from "os";
|
|
1026
|
-
import
|
|
1067
|
+
import path22 from "path";
|
|
1027
1068
|
import { fileURLToPath } from "url";
|
|
1028
1069
|
|
|
1029
1070
|
// ../../packages/agent-targets/src/install/claude/index.ts
|
|
@@ -1264,39 +1305,166 @@ var CLAUDE_PLUGIN_INSTALLER = Object.freeze({
|
|
|
1264
1305
|
})
|
|
1265
1306
|
});
|
|
1266
1307
|
|
|
1267
|
-
// src/
|
|
1268
|
-
import
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
"
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1308
|
+
// ../../packages/agent-targets/src/install/pi/index.ts
|
|
1309
|
+
import fs3 from "fs";
|
|
1310
|
+
import path4 from "path";
|
|
1311
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1312
|
+
var FML_PI_EXTENSION_MARKER = "__FML_PI_EXTENSION_V1__";
|
|
1313
|
+
var LEGACY_PI_EXTENSION_MARKER = "__panopticonPiExtensionTest";
|
|
1314
|
+
var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
|
|
1315
|
+
"PANOPTICON_PORT",
|
|
1316
|
+
'"/hooks"',
|
|
1317
|
+
"UserPromptSubmit",
|
|
1318
|
+
"PreToolUse",
|
|
1319
|
+
"PostToolUse",
|
|
1320
|
+
"SessionEnd"
|
|
1321
|
+
]);
|
|
1322
|
+
function pathApiFor(platform) {
|
|
1323
|
+
return platform === "win32" ? path4.win32 : path4.posix;
|
|
1324
|
+
}
|
|
1325
|
+
function resolvePiAgentDir(context, pathApi) {
|
|
1326
|
+
const configured = context.env.PI_CODING_AGENT_DIR?.trim();
|
|
1327
|
+
if (!configured) {
|
|
1328
|
+
return pathApi.join(context.homeDir, ".pi", "agent");
|
|
1329
|
+
}
|
|
1330
|
+
if (configured === "~") return context.homeDir;
|
|
1331
|
+
if (configured.startsWith("~/") || configured.startsWith("~\\")) {
|
|
1332
|
+
return pathApi.join(context.homeDir, configured.slice(2));
|
|
1333
|
+
}
|
|
1334
|
+
return pathApi.normalize(configured);
|
|
1335
|
+
}
|
|
1336
|
+
function readFileIfPresent(filePath) {
|
|
1337
|
+
try {
|
|
1338
|
+
return fs3.readFileSync(filePath, "utf8");
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
if (error.code === "ENOENT") return null;
|
|
1341
|
+
throw error;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
function isOwnedExtension(source, legacy = false) {
|
|
1345
|
+
return source.includes(FML_PI_EXTENSION_MARKER) || legacy && (source.includes(LEGACY_PI_EXTENSION_MARKER) || LEGACY_PI_EXTENSION_SIGNATURES.every(
|
|
1346
|
+
(signature) => source.includes(signature)
|
|
1347
|
+
));
|
|
1348
|
+
}
|
|
1349
|
+
function removeLegacyExtension(filePath) {
|
|
1350
|
+
const source = readFileIfPresent(filePath);
|
|
1351
|
+
if (source === null) return { removed: false, preserved: false };
|
|
1352
|
+
if (!isOwnedExtension(source, true)) {
|
|
1353
|
+
return { removed: false, preserved: true };
|
|
1354
|
+
}
|
|
1355
|
+
fs3.rmSync(filePath);
|
|
1356
|
+
return { removed: true, preserved: false };
|
|
1357
|
+
}
|
|
1358
|
+
function writeExtensionAtomically(filePath, source) {
|
|
1359
|
+
fs3.mkdirSync(path4.dirname(filePath), { recursive: true, mode: 448 });
|
|
1360
|
+
const temporaryPath = path4.join(
|
|
1361
|
+
path4.dirname(filePath),
|
|
1362
|
+
`.${path4.basename(filePath)}.${process.pid}.${randomUUID2()}.tmp`
|
|
1363
|
+
);
|
|
1364
|
+
try {
|
|
1365
|
+
fs3.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
|
|
1366
|
+
if (process.platform === "win32" && fs3.existsSync(filePath)) {
|
|
1367
|
+
fs3.rmSync(filePath);
|
|
1368
|
+
}
|
|
1369
|
+
fs3.renameSync(temporaryPath, filePath);
|
|
1370
|
+
} finally {
|
|
1371
|
+
fs3.rmSync(temporaryPath, { force: true });
|
|
1281
1372
|
}
|
|
1282
|
-
console.log("");
|
|
1283
1373
|
}
|
|
1374
|
+
var PI_EXTENSION_INSTALLER = Object.freeze({
|
|
1375
|
+
resolvePaths(context) {
|
|
1376
|
+
const pathApi = pathApiFor(context.platform);
|
|
1377
|
+
const extensionsDir = pathApi.join(
|
|
1378
|
+
resolvePiAgentDir(context, pathApi),
|
|
1379
|
+
"extensions"
|
|
1380
|
+
);
|
|
1381
|
+
return {
|
|
1382
|
+
sourcePath: pathApi.join(
|
|
1383
|
+
context.packageRoot,
|
|
1384
|
+
"dist",
|
|
1385
|
+
"pi",
|
|
1386
|
+
"extension.js"
|
|
1387
|
+
),
|
|
1388
|
+
extensionsDir,
|
|
1389
|
+
destinationPath: pathApi.join(extensionsDir, "fml.js"),
|
|
1390
|
+
legacyDestinationPath: pathApi.join(extensionsDir, "panopticon.js")
|
|
1391
|
+
};
|
|
1392
|
+
},
|
|
1393
|
+
install(paths) {
|
|
1394
|
+
const source = fs3.readFileSync(paths.sourcePath, "utf8");
|
|
1395
|
+
if (!source.includes(FML_PI_EXTENSION_MARKER)) {
|
|
1396
|
+
throw new Error(
|
|
1397
|
+
`Packaged Pi extension is invalid: ${paths.sourcePath} does not contain the FML ownership marker`
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
const existing = readFileIfPresent(paths.destinationPath);
|
|
1401
|
+
if (existing !== null && !isOwnedExtension(existing)) {
|
|
1402
|
+
const legacy2 = removeLegacyExtension(paths.legacyDestinationPath);
|
|
1403
|
+
return {
|
|
1404
|
+
status: "preserved",
|
|
1405
|
+
destinationPath: paths.destinationPath,
|
|
1406
|
+
reason: "an extension not managed by FML already exists at this path",
|
|
1407
|
+
legacyRemoved: legacy2.removed,
|
|
1408
|
+
legacyPreserved: legacy2.preserved
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
const status = existing === null ? "installed" : existing === source ? "unchanged" : "updated";
|
|
1412
|
+
if (status !== "unchanged") {
|
|
1413
|
+
writeExtensionAtomically(paths.destinationPath, source);
|
|
1414
|
+
}
|
|
1415
|
+
const legacy = removeLegacyExtension(paths.legacyDestinationPath);
|
|
1416
|
+
return {
|
|
1417
|
+
status,
|
|
1418
|
+
destinationPath: paths.destinationPath,
|
|
1419
|
+
legacyRemoved: legacy.removed,
|
|
1420
|
+
legacyPreserved: legacy.preserved
|
|
1421
|
+
};
|
|
1422
|
+
},
|
|
1423
|
+
uninstall(paths) {
|
|
1424
|
+
const existing = readFileIfPresent(paths.destinationPath);
|
|
1425
|
+
const legacy = removeLegacyExtension(paths.legacyDestinationPath);
|
|
1426
|
+
if (existing === null) {
|
|
1427
|
+
return {
|
|
1428
|
+
status: "absent",
|
|
1429
|
+
destinationPath: paths.destinationPath,
|
|
1430
|
+
legacyRemoved: legacy.removed,
|
|
1431
|
+
legacyPreserved: legacy.preserved
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
if (!isOwnedExtension(existing)) {
|
|
1435
|
+
return {
|
|
1436
|
+
status: "preserved",
|
|
1437
|
+
destinationPath: paths.destinationPath,
|
|
1438
|
+
reason: "the existing extension is not managed by FML",
|
|
1439
|
+
legacyRemoved: legacy.removed,
|
|
1440
|
+
legacyPreserved: legacy.preserved
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
fs3.rmSync(paths.destinationPath);
|
|
1444
|
+
return {
|
|
1445
|
+
status: "removed",
|
|
1446
|
+
destinationPath: paths.destinationPath,
|
|
1447
|
+
legacyRemoved: legacy.removed,
|
|
1448
|
+
legacyPreserved: legacy.preserved
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1284
1452
|
|
|
1285
1453
|
// src/bin-utils.ts
|
|
1286
|
-
import
|
|
1287
|
-
import
|
|
1454
|
+
import fs4 from "fs";
|
|
1455
|
+
import path5 from "path";
|
|
1288
1456
|
import { execFileSync, execSync } from "child_process";
|
|
1289
1457
|
var IS_WIN = process.platform === "win32";
|
|
1290
1458
|
var DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
1291
1459
|
function resolveBin(name) {
|
|
1292
|
-
const pathDirs = (process.env.PATH ?? "").split(
|
|
1293
|
-
const hasExt =
|
|
1460
|
+
const pathDirs = (process.env.PATH ?? "").split(path5.delimiter).filter(Boolean);
|
|
1461
|
+
const hasExt = path5.extname(name) !== "";
|
|
1294
1462
|
const exts = !IS_WIN || hasExt ? [""] : (process.env.PATHEXT ?? DEFAULT_PATHEXT).split(";").filter(Boolean);
|
|
1295
1463
|
for (const dir of pathDirs) {
|
|
1296
1464
|
for (const ext of exts) {
|
|
1297
|
-
const candidate =
|
|
1465
|
+
const candidate = path5.join(dir, name + ext);
|
|
1298
1466
|
try {
|
|
1299
|
-
if (
|
|
1467
|
+
if (fs4.statSync(candidate).isFile()) return candidate;
|
|
1300
1468
|
} catch {
|
|
1301
1469
|
}
|
|
1302
1470
|
}
|
|
@@ -1328,18 +1496,18 @@ function execBinSync(bin, args, opts = {}) {
|
|
|
1328
1496
|
}
|
|
1329
1497
|
|
|
1330
1498
|
// src/managed-directories.ts
|
|
1331
|
-
import
|
|
1499
|
+
import fs5 from "fs";
|
|
1332
1500
|
import os from "os";
|
|
1333
|
-
import
|
|
1501
|
+
import path6 from "path";
|
|
1334
1502
|
var FML_MANAGED_DIRECTORY_MARKER = ".fml-managed-root";
|
|
1335
1503
|
var FML_MANAGED_DIRECTORY_MARKER_CONTENT = "fml-managed-directory-v1\n";
|
|
1336
1504
|
function comparisonPathApi(platform, targetPaths) {
|
|
1337
1505
|
if (platform === "win32" && targetPaths.some(
|
|
1338
1506
|
(targetPath) => /^[A-Za-z]:[\\/]/u.test(targetPath) || targetPath.startsWith("\\\\") || targetPath.startsWith("//")
|
|
1339
1507
|
)) {
|
|
1340
|
-
return
|
|
1508
|
+
return path6.win32;
|
|
1341
1509
|
}
|
|
1342
|
-
return
|
|
1510
|
+
return path6;
|
|
1343
1511
|
}
|
|
1344
1512
|
function resolveComparisonPath(targetPath, platform) {
|
|
1345
1513
|
return comparisonPathApi(platform, [targetPath]).resolve(targetPath);
|
|
@@ -1412,7 +1580,7 @@ function configuredManagedRoots(platform = process.platform) {
|
|
|
1412
1580
|
);
|
|
1413
1581
|
}
|
|
1414
1582
|
function managedDirectoryMarker(directory) {
|
|
1415
|
-
return
|
|
1583
|
+
return path6.join(directory, FML_MANAGED_DIRECTORY_MARKER);
|
|
1416
1584
|
}
|
|
1417
1585
|
function ownershipStatus(stats, platform) {
|
|
1418
1586
|
if (platform === "win32") return "unverifiable";
|
|
@@ -1423,8 +1591,8 @@ function ownershipStatus(stats, platform) {
|
|
|
1423
1591
|
function hasManagedDirectoryMarkerContents(directory) {
|
|
1424
1592
|
const marker = managedDirectoryMarker(directory);
|
|
1425
1593
|
try {
|
|
1426
|
-
const stats =
|
|
1427
|
-
return stats.isFile() &&
|
|
1594
|
+
const stats = fs5.lstatSync(marker);
|
|
1595
|
+
return stats.isFile() && fs5.readFileSync(marker, "utf8") === FML_MANAGED_DIRECTORY_MARKER_CONTENT;
|
|
1428
1596
|
} catch {
|
|
1429
1597
|
return false;
|
|
1430
1598
|
}
|
|
@@ -1432,11 +1600,11 @@ function hasManagedDirectoryMarkerContents(directory) {
|
|
|
1432
1600
|
function hasManagedDirectoryMarker(directory, platform = process.platform) {
|
|
1433
1601
|
const marker = managedDirectoryMarker(directory);
|
|
1434
1602
|
try {
|
|
1435
|
-
const stats =
|
|
1603
|
+
const stats = fs5.lstatSync(marker);
|
|
1436
1604
|
if (!stats.isFile() || ownershipStatus(stats, platform) !== "owned") {
|
|
1437
1605
|
return false;
|
|
1438
1606
|
}
|
|
1439
|
-
return
|
|
1607
|
+
return fs5.readFileSync(marker, "utf8") === FML_MANAGED_DIRECTORY_MARKER_CONTENT;
|
|
1440
1608
|
} catch {
|
|
1441
1609
|
return false;
|
|
1442
1610
|
}
|
|
@@ -1444,7 +1612,7 @@ function hasManagedDirectoryMarker(directory, platform = process.platform) {
|
|
|
1444
1612
|
function writeManagedDirectoryMarker(directory, platform) {
|
|
1445
1613
|
const marker = managedDirectoryMarker(directory);
|
|
1446
1614
|
try {
|
|
1447
|
-
|
|
1615
|
+
fs5.writeFileSync(marker, FML_MANAGED_DIRECTORY_MARKER_CONTENT, {
|
|
1448
1616
|
encoding: "utf8",
|
|
1449
1617
|
flag: "wx",
|
|
1450
1618
|
mode: 384
|
|
@@ -1469,23 +1637,23 @@ function prepareManagedDirectory(root, platform = process.platform) {
|
|
|
1469
1637
|
let created = false;
|
|
1470
1638
|
let existingStats = null;
|
|
1471
1639
|
try {
|
|
1472
|
-
existingStats =
|
|
1640
|
+
existingStats = fs5.lstatSync(root.directory);
|
|
1473
1641
|
} catch (error) {
|
|
1474
1642
|
if (error.code !== "ENOENT") throw error;
|
|
1475
1643
|
existed = false;
|
|
1476
1644
|
}
|
|
1477
1645
|
if (!existed) {
|
|
1478
|
-
|
|
1646
|
+
fs5.mkdirSync(path6.dirname(root.directory), {
|
|
1479
1647
|
recursive: true,
|
|
1480
1648
|
mode: 448
|
|
1481
1649
|
});
|
|
1482
1650
|
try {
|
|
1483
|
-
|
|
1651
|
+
fs5.mkdirSync(root.directory, { mode: 448 });
|
|
1484
1652
|
created = true;
|
|
1485
1653
|
} catch (error) {
|
|
1486
1654
|
if (error.code !== "EEXIST") throw error;
|
|
1487
1655
|
existed = true;
|
|
1488
|
-
existingStats =
|
|
1656
|
+
existingStats = fs5.lstatSync(root.directory);
|
|
1489
1657
|
}
|
|
1490
1658
|
}
|
|
1491
1659
|
if (hasSymlinkedParentBelowTrustedAnchor(root.directory, platform)) {
|
|
@@ -1495,7 +1663,7 @@ function prepareManagedDirectory(root, platform = process.platform) {
|
|
|
1495
1663
|
markerCreated: false
|
|
1496
1664
|
};
|
|
1497
1665
|
}
|
|
1498
|
-
const directoryStats = existingStats ??
|
|
1666
|
+
const directoryStats = existingStats ?? fs5.lstatSync(root.directory);
|
|
1499
1667
|
const ownership = ownershipStatus(directoryStats, platform);
|
|
1500
1668
|
const recognizedWindowsMarker = platform === "win32" && hasManagedDirectoryMarkerContents(root.directory);
|
|
1501
1669
|
if (directoryStats.isSymbolicLink() || !directoryStats.isDirectory() || ownership === "foreign" || !root.nativeDefault && ownership !== "owned" && !(created && ownership === "unverifiable") && !recognizedWindowsMarker) {
|
|
@@ -1519,7 +1687,7 @@ function prepareManagedDirectory(root, platform = process.platform) {
|
|
|
1519
1687
|
markerCreated: false
|
|
1520
1688
|
};
|
|
1521
1689
|
}
|
|
1522
|
-
const mayClaim =
|
|
1690
|
+
const mayClaim = fs5.readdirSync(root.directory).length === 0;
|
|
1523
1691
|
if (!mayClaim) {
|
|
1524
1692
|
return {
|
|
1525
1693
|
directory: root.directory,
|
|
@@ -1578,7 +1746,7 @@ function hasSymlinkedParentBelowTrustedAnchor(targetPath, platform = process.pla
|
|
|
1578
1746
|
for (const component of relativeParent.split(pathApi.sep)) {
|
|
1579
1747
|
current = pathApi.join(current, component);
|
|
1580
1748
|
try {
|
|
1581
|
-
if (
|
|
1749
|
+
if (fs5.lstatSync(current).isSymbolicLink()) return true;
|
|
1582
1750
|
} catch (error) {
|
|
1583
1751
|
if (error.code === "ENOENT") return false;
|
|
1584
1752
|
return true;
|
|
@@ -1603,7 +1771,7 @@ function unsafePurgeRoots(roots, platform = process.platform) {
|
|
|
1603
1771
|
}
|
|
1604
1772
|
let stats;
|
|
1605
1773
|
try {
|
|
1606
|
-
stats =
|
|
1774
|
+
stats = fs5.lstatSync(root.directory);
|
|
1607
1775
|
} catch (error) {
|
|
1608
1776
|
if (error.code === "ENOENT") continue;
|
|
1609
1777
|
unsafe.push({
|
|
@@ -1652,14 +1820,14 @@ function unsafePurgeRoots(roots, platform = process.platform) {
|
|
|
1652
1820
|
}
|
|
1653
1821
|
|
|
1654
1822
|
// src/commands/hook-config.ts
|
|
1655
|
-
import
|
|
1823
|
+
import fs6 from "fs";
|
|
1656
1824
|
import os2 from "os";
|
|
1657
|
-
import
|
|
1825
|
+
import path11 from "path";
|
|
1658
1826
|
|
|
1659
1827
|
// ../../packages/agent-targets/src/install/hooks/codex.ts
|
|
1660
1828
|
import { createHash } from "crypto";
|
|
1661
1829
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
1662
|
-
import
|
|
1830
|
+
import path8 from "path";
|
|
1663
1831
|
import { parse as parse2 } from "smol-toml";
|
|
1664
1832
|
|
|
1665
1833
|
// ../../packages/agent-targets/src/toml.ts
|
|
@@ -1669,7 +1837,7 @@ function asRecord2(value) {
|
|
|
1669
1837
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1670
1838
|
}
|
|
1671
1839
|
function singlePath(value) {
|
|
1672
|
-
const
|
|
1840
|
+
const path30 = [];
|
|
1673
1841
|
let current = value;
|
|
1674
1842
|
while (true) {
|
|
1675
1843
|
if (Array.isArray(current) && current.length === 1) {
|
|
@@ -1677,13 +1845,13 @@ function singlePath(value) {
|
|
|
1677
1845
|
continue;
|
|
1678
1846
|
}
|
|
1679
1847
|
const record = asRecord2(current);
|
|
1680
|
-
if (!record) return
|
|
1848
|
+
if (!record) return path30;
|
|
1681
1849
|
const keys = Object.keys(record);
|
|
1682
|
-
if (keys.length === 0) return
|
|
1850
|
+
if (keys.length === 0) return path30;
|
|
1683
1851
|
if (keys.length !== 1) throw new Error("ambiguous TOML statement path");
|
|
1684
1852
|
const key = keys[0];
|
|
1685
1853
|
if (key === void 0) throw new Error("missing TOML statement key");
|
|
1686
|
-
|
|
1854
|
+
path30.push(key);
|
|
1687
1855
|
current = record[key];
|
|
1688
1856
|
}
|
|
1689
1857
|
}
|
|
@@ -1765,8 +1933,8 @@ function scanStatements(source) {
|
|
|
1765
1933
|
}
|
|
1766
1934
|
return statements;
|
|
1767
1935
|
}
|
|
1768
|
-
function startsWithPath(
|
|
1769
|
-
return prefix.every((part, index) =>
|
|
1936
|
+
function startsWithPath(path30, prefix) {
|
|
1937
|
+
return prefix.every((part, index) => path30[index] === part);
|
|
1770
1938
|
}
|
|
1771
1939
|
function pathsEqual(left, right) {
|
|
1772
1940
|
return left.length === right.length && left.every((part, index) => part === right[index]);
|
|
@@ -1792,9 +1960,9 @@ function appendToml(source, addition) {
|
|
|
1792
1960
|
function hasOwn(record, key) {
|
|
1793
1961
|
return Object.prototype.hasOwnProperty.call(record, key);
|
|
1794
1962
|
}
|
|
1795
|
-
function updatedValueAtPath(updated,
|
|
1963
|
+
function updatedValueAtPath(updated, path30) {
|
|
1796
1964
|
let current = updated;
|
|
1797
|
-
for (const part of
|
|
1965
|
+
for (const part of path30) {
|
|
1798
1966
|
const record = asRecord2(current);
|
|
1799
1967
|
if (!record || !hasOwn(record, part)) return { exists: false };
|
|
1800
1968
|
current = record[part];
|
|
@@ -1808,22 +1976,22 @@ function normalizedPaths(updates) {
|
|
|
1808
1976
|
throw new Error("TOML update path cannot be empty");
|
|
1809
1977
|
}
|
|
1810
1978
|
if (paths.some(
|
|
1811
|
-
(
|
|
1979
|
+
(path30) => pathsEqual(path30, update.path) || startsWithPath(update.path, path30)
|
|
1812
1980
|
)) {
|
|
1813
1981
|
continue;
|
|
1814
1982
|
}
|
|
1815
1983
|
for (let index = paths.length - 1; index >= 0; index -= 1) {
|
|
1816
|
-
const
|
|
1817
|
-
if (
|
|
1984
|
+
const path30 = paths[index];
|
|
1985
|
+
if (path30 && startsWithPath(path30, update.path)) paths.splice(index, 1);
|
|
1818
1986
|
}
|
|
1819
1987
|
paths.push([...update.path]);
|
|
1820
1988
|
}
|
|
1821
1989
|
return paths;
|
|
1822
1990
|
}
|
|
1823
|
-
function valueDocument(
|
|
1991
|
+
function valueDocument(path30, value) {
|
|
1824
1992
|
let nested = value;
|
|
1825
|
-
for (let index =
|
|
1826
|
-
const part =
|
|
1993
|
+
for (let index = path30.length - 1; index >= 0; index -= 1) {
|
|
1994
|
+
const part = path30[index];
|
|
1827
1995
|
if (part === void 0) throw new Error("TOML update path is invalid");
|
|
1828
1996
|
nested = { [part]: nested };
|
|
1829
1997
|
}
|
|
@@ -1842,12 +2010,12 @@ function insertAfterStatement(source, statement, addition) {
|
|
|
1842
2010
|
const separator = before.endsWith(eol) ? "" : eol;
|
|
1843
2011
|
return `${before}${separator}${normalized}${eol}${source.slice(statement.end)}`;
|
|
1844
2012
|
}
|
|
1845
|
-
function insertUpdatedValue(source,
|
|
1846
|
-
const key =
|
|
2013
|
+
function insertUpdatedValue(source, path30, value) {
|
|
2014
|
+
const key = path30.at(-1);
|
|
1847
2015
|
if (key === void 0) throw new Error("TOML update path cannot be empty");
|
|
1848
2016
|
const assignment = localAssignment(key, value);
|
|
1849
2017
|
if (assignment !== null) {
|
|
1850
|
-
const parent =
|
|
2018
|
+
const parent = path30.slice(0, -1);
|
|
1851
2019
|
const parentTables = scanStatements(source).filter(
|
|
1852
2020
|
(statement) => statement.isTable && pathsEqual(statement.path, parent)
|
|
1853
2021
|
);
|
|
@@ -1861,17 +2029,17 @@ function insertUpdatedValue(source, path29, value) {
|
|
|
1861
2029
|
return insertAfterStatement(source, parentTable, assignment);
|
|
1862
2030
|
}
|
|
1863
2031
|
}
|
|
1864
|
-
return appendToml(source, stringify(valueDocument(
|
|
2032
|
+
return appendToml(source, stringify(valueDocument(path30, value)));
|
|
1865
2033
|
}
|
|
1866
2034
|
function updateTomlPaths(source, updated, updates) {
|
|
1867
2035
|
const paths = normalizedPaths(updates);
|
|
1868
2036
|
if (paths.length === 0) return source;
|
|
1869
2037
|
const stripped = removeStatements(source, scanStatements(source), paths);
|
|
1870
2038
|
let result = stripped;
|
|
1871
|
-
for (const
|
|
1872
|
-
const next = updatedValueAtPath(updated,
|
|
2039
|
+
for (const path30 of paths) {
|
|
2040
|
+
const next = updatedValueAtPath(updated, path30);
|
|
1873
2041
|
if (!next.exists) continue;
|
|
1874
|
-
result = insertUpdatedValue(result,
|
|
2042
|
+
result = insertUpdatedValue(result, path30, next.value);
|
|
1875
2043
|
}
|
|
1876
2044
|
let reparsed;
|
|
1877
2045
|
try {
|
|
@@ -1890,7 +2058,7 @@ function updateTomlPaths(source, updated, updates) {
|
|
|
1890
2058
|
}
|
|
1891
2059
|
|
|
1892
2060
|
// ../../packages/agent-targets/src/install/hooks/shared.ts
|
|
1893
|
-
import
|
|
2061
|
+
import path7 from "path";
|
|
1894
2062
|
function asRecord3(value) {
|
|
1895
2063
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1896
2064
|
}
|
|
@@ -1950,7 +2118,7 @@ function changedMutation(snapshot, after) {
|
|
|
1950
2118
|
return before === after ? null : { path: snapshot.path, before, after };
|
|
1951
2119
|
}
|
|
1952
2120
|
function fmlHookCommand(pluginRoot, target2) {
|
|
1953
|
-
const handlerPath =
|
|
2121
|
+
const handlerPath = path7.join(pluginRoot, "bin", "hook-handler");
|
|
1954
2122
|
const encodedPath = Buffer.from(handlerPath, "utf8").toString("base64url");
|
|
1955
2123
|
return `node -e "${FML_HOOK_LOADER}" ${encodedPath} ${target2}`;
|
|
1956
2124
|
}
|
|
@@ -2063,11 +2231,11 @@ function uniquePaths(paths) {
|
|
|
2063
2231
|
function codexPaths(context) {
|
|
2064
2232
|
const configuredHome = context.env.CODEX_HOME;
|
|
2065
2233
|
const trimmedHome = typeof configuredHome === "string" ? configuredHome.trim() : "";
|
|
2066
|
-
const dir =
|
|
2067
|
-
trimmedHome.length > 0 ? trimmedHome :
|
|
2234
|
+
const dir = path8.resolve(
|
|
2235
|
+
trimmedHome.length > 0 ? trimmedHome : path8.join(context.homeDir, ".codex")
|
|
2068
2236
|
);
|
|
2069
|
-
const configPath4 =
|
|
2070
|
-
const hooksPath =
|
|
2237
|
+
const configPath4 = path8.join(dir, "config.toml");
|
|
2238
|
+
const hooksPath = path8.join(dir, "hooks.json");
|
|
2071
2239
|
return {
|
|
2072
2240
|
configPath: configPath4,
|
|
2073
2241
|
hooksPath,
|
|
@@ -2499,7 +2667,7 @@ function codexConfigUpdatePaths(original, updated) {
|
|
|
2499
2667
|
}
|
|
2500
2668
|
}
|
|
2501
2669
|
}
|
|
2502
|
-
return paths.map((
|
|
2670
|
+
return paths.map((path30) => ({ path: path30 }));
|
|
2503
2671
|
}
|
|
2504
2672
|
function updateCodexConfigToml(source, original, updated) {
|
|
2505
2673
|
return updateTomlPaths(
|
|
@@ -2949,7 +3117,7 @@ var CODEX_HOOK_INSTALLER = Object.freeze({
|
|
|
2949
3117
|
|
|
2950
3118
|
// ../../packages/agent-targets/src/install/hooks/gemini.ts
|
|
2951
3119
|
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
2952
|
-
import
|
|
3120
|
+
import path9 from "path";
|
|
2953
3121
|
|
|
2954
3122
|
// ../../packages/agent-targets/src/mcp/jsonc.ts
|
|
2955
3123
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
@@ -3005,7 +3173,7 @@ function longestCommonArraySubsequence(original, updated) {
|
|
|
3005
3173
|
}
|
|
3006
3174
|
return matches;
|
|
3007
3175
|
}
|
|
3008
|
-
function arrayDiff(original, updated,
|
|
3176
|
+
function arrayDiff(original, updated, path30) {
|
|
3009
3177
|
const updates = [];
|
|
3010
3178
|
const matches = longestCommonArraySubsequence(original, updated);
|
|
3011
3179
|
let originalStart = 0;
|
|
@@ -3023,19 +3191,19 @@ function arrayDiff(original, updated, path29) {
|
|
|
3023
3191
|
...jsoncUpdatesForObjectDiff(
|
|
3024
3192
|
original[originalStart + offset],
|
|
3025
3193
|
updated[updatedStart + offset],
|
|
3026
|
-
[...
|
|
3194
|
+
[...path30, currentIndex + offset]
|
|
3027
3195
|
)
|
|
3028
3196
|
);
|
|
3029
3197
|
}
|
|
3030
3198
|
for (let offset = pairedCount; offset < originalCount; offset += 1) {
|
|
3031
3199
|
updates.push({
|
|
3032
|
-
path: [...
|
|
3200
|
+
path: [...path30, currentIndex + pairedCount],
|
|
3033
3201
|
value: void 0
|
|
3034
3202
|
});
|
|
3035
3203
|
}
|
|
3036
3204
|
for (let offset = pairedCount; offset < updatedCount; offset += 1) {
|
|
3037
3205
|
updates.push({
|
|
3038
|
-
path: [...
|
|
3206
|
+
path: [...path30, currentIndex + offset],
|
|
3039
3207
|
value: updated[updatedStart + offset],
|
|
3040
3208
|
isArrayInsertion: true
|
|
3041
3209
|
});
|
|
@@ -3048,21 +3216,21 @@ function arrayDiff(original, updated, path29) {
|
|
|
3048
3216
|
}
|
|
3049
3217
|
return updates;
|
|
3050
3218
|
}
|
|
3051
|
-
function jsoncUpdatesForObjectDiff(original, updated,
|
|
3219
|
+
function jsoncUpdatesForObjectDiff(original, updated, path30 = []) {
|
|
3052
3220
|
if (isDeepStrictEqual3(original, updated)) return [];
|
|
3053
3221
|
if (Array.isArray(original) && Array.isArray(updated)) {
|
|
3054
|
-
return arrayDiff(original, updated,
|
|
3222
|
+
return arrayDiff(original, updated, path30);
|
|
3055
3223
|
}
|
|
3056
3224
|
const originalObject = asObject(original);
|
|
3057
3225
|
const updatedObject = asObject(updated);
|
|
3058
|
-
if (!originalObject || !updatedObject) return [{ path:
|
|
3226
|
+
if (!originalObject || !updatedObject) return [{ path: path30, value: updated }];
|
|
3059
3227
|
const keys = /* @__PURE__ */ new Set([
|
|
3060
3228
|
...Object.keys(originalObject),
|
|
3061
3229
|
...Object.keys(updatedObject)
|
|
3062
3230
|
]);
|
|
3063
3231
|
return [...keys].flatMap(
|
|
3064
3232
|
(key) => jsoncUpdatesForObjectDiff(originalObject[key], updatedObject[key], [
|
|
3065
|
-
...
|
|
3233
|
+
...path30,
|
|
3066
3234
|
key
|
|
3067
3235
|
])
|
|
3068
3236
|
);
|
|
@@ -3110,7 +3278,7 @@ var TARGET_ID3 = TARGET3.id;
|
|
|
3110
3278
|
var DISPLAY_NAME3 = TARGET3.displayName;
|
|
3111
3279
|
var MANAGED_HOOK_NAME = "fml-capture";
|
|
3112
3280
|
function settingsPath(context) {
|
|
3113
|
-
return
|
|
3281
|
+
return path9.join(geminiConfigDirectory(context), "settings.json");
|
|
3114
3282
|
}
|
|
3115
3283
|
function readGeminiSettings(snapshot) {
|
|
3116
3284
|
const failure = snapshotError(snapshot);
|
|
@@ -3343,15 +3511,15 @@ var GEMINI_HOOK_INSTALLER = Object.freeze({
|
|
|
3343
3511
|
});
|
|
3344
3512
|
|
|
3345
3513
|
// ../../packages/agent-targets/src/install/hooks/grok.ts
|
|
3346
|
-
import
|
|
3514
|
+
import path10 from "path";
|
|
3347
3515
|
var TARGET4 = agentTarget("grok");
|
|
3348
3516
|
var TARGET_ID4 = TARGET4.id;
|
|
3349
3517
|
var DISPLAY_NAME4 = TARGET4.displayName;
|
|
3350
3518
|
var HOOK_TIMEOUT_SECONDS2 = 10;
|
|
3351
3519
|
function grokHooksPath(context) {
|
|
3352
3520
|
const configuredHome = context.env.GROK_HOME?.trim();
|
|
3353
|
-
const grokHome = configuredHome ?
|
|
3354
|
-
return
|
|
3521
|
+
const grokHome = configuredHome ? path10.resolve(configuredHome) : path10.join(context.homeDir, ".grok");
|
|
3522
|
+
return path10.join(grokHome, "hooks", "fml.json");
|
|
3355
3523
|
}
|
|
3356
3524
|
function planInstall3(context) {
|
|
3357
3525
|
const configPath4 = grokHooksPath(context);
|
|
@@ -3470,14 +3638,14 @@ var HOOK_REGISTRATION_CAPABILITIES = Object.freeze([
|
|
|
3470
3638
|
]);
|
|
3471
3639
|
|
|
3472
3640
|
// src/commands/hook-config.ts
|
|
3473
|
-
var DEFAULT_CODEX_HOOK_OWNERSHIP_STATE_PATH =
|
|
3641
|
+
var DEFAULT_CODEX_HOOK_OWNERSHIP_STATE_PATH = path11.join(
|
|
3474
3642
|
FML_DATA_DIR,
|
|
3475
3643
|
"codex-hook-install.json"
|
|
3476
3644
|
);
|
|
3477
3645
|
function readHookFile(filePath) {
|
|
3478
3646
|
let stat;
|
|
3479
3647
|
try {
|
|
3480
|
-
stat =
|
|
3648
|
+
stat = fs6.lstatSync(filePath);
|
|
3481
3649
|
} catch (error) {
|
|
3482
3650
|
if (error.code === "ENOENT") {
|
|
3483
3651
|
return { path: filePath, kind: "missing" };
|
|
@@ -3506,7 +3674,7 @@ function readHookFile(filePath) {
|
|
|
3506
3674
|
return {
|
|
3507
3675
|
path: filePath,
|
|
3508
3676
|
kind: "file",
|
|
3509
|
-
contents:
|
|
3677
|
+
contents: fs6.readFileSync(filePath, "utf8")
|
|
3510
3678
|
};
|
|
3511
3679
|
} catch (error) {
|
|
3512
3680
|
return {
|
|
@@ -3518,17 +3686,17 @@ function readHookFile(filePath) {
|
|
|
3518
3686
|
}
|
|
3519
3687
|
function canonicalizePath(filePath) {
|
|
3520
3688
|
try {
|
|
3521
|
-
return
|
|
3689
|
+
return fs6.realpathSync(filePath);
|
|
3522
3690
|
} catch {
|
|
3523
3691
|
const missingSegments = [];
|
|
3524
3692
|
let ancestor = filePath;
|
|
3525
3693
|
while (true) {
|
|
3526
|
-
const parent =
|
|
3694
|
+
const parent = path11.dirname(ancestor);
|
|
3527
3695
|
if (parent === ancestor) return filePath;
|
|
3528
|
-
missingSegments.unshift(
|
|
3696
|
+
missingSegments.unshift(path11.basename(ancestor));
|
|
3529
3697
|
ancestor = parent;
|
|
3530
3698
|
try {
|
|
3531
|
-
return
|
|
3699
|
+
return path11.join(fs6.realpathSync(ancestor), ...missingSegments);
|
|
3532
3700
|
} catch {
|
|
3533
3701
|
}
|
|
3534
3702
|
}
|
|
@@ -3547,7 +3715,7 @@ function assertMutationPrecondition(mutation) {
|
|
|
3547
3715
|
}
|
|
3548
3716
|
function applyMutation(mutation) {
|
|
3549
3717
|
if (mutation.after === null) {
|
|
3550
|
-
|
|
3718
|
+
fs6.rmSync(mutation.path);
|
|
3551
3719
|
} else {
|
|
3552
3720
|
writeTextFileAtomic(mutation.path, mutation.after);
|
|
3553
3721
|
}
|
|
@@ -3560,7 +3728,7 @@ function rollbackMutation(mutation) {
|
|
|
3560
3728
|
};
|
|
3561
3729
|
assertMutationPrecondition(inverse);
|
|
3562
3730
|
if (inverse.after === null) {
|
|
3563
|
-
|
|
3731
|
+
fs6.rmSync(inverse.path);
|
|
3564
3732
|
} else {
|
|
3565
3733
|
writeTextFileAtomic(inverse.path, inverse.after);
|
|
3566
3734
|
}
|
|
@@ -3628,7 +3796,7 @@ function applyPlan(plan, warn, onFailure) {
|
|
|
3628
3796
|
};
|
|
3629
3797
|
}
|
|
3630
3798
|
function plannerContext(packageRoot, options) {
|
|
3631
|
-
const ownershipStatePath = options.ownershipStatePath ?? (options.homeDir ?
|
|
3799
|
+
const ownershipStatePath = options.ownershipStatePath ?? (options.homeDir ? path11.join(options.homeDir, ".fml", "codex-hook-install.json") : DEFAULT_CODEX_HOOK_OWNERSHIP_STATE_PATH);
|
|
3632
3800
|
return {
|
|
3633
3801
|
packageRoot,
|
|
3634
3802
|
homeDir: options.homeDir ?? os2.homedir(),
|
|
@@ -3659,10 +3827,10 @@ function removeFmlHooks(options = {}) {
|
|
|
3659
3827
|
}
|
|
3660
3828
|
|
|
3661
3829
|
// src/commands/marketplace-link.ts
|
|
3662
|
-
import
|
|
3830
|
+
import fs7 from "fs";
|
|
3663
3831
|
function existingPathKind(linkPath) {
|
|
3664
3832
|
try {
|
|
3665
|
-
return
|
|
3833
|
+
return fs7.lstatSync(linkPath).isSymbolicLink() ? "symlink" : "other";
|
|
3666
3834
|
} catch (error) {
|
|
3667
3835
|
if (error.code === "ENOENT") return "missing";
|
|
3668
3836
|
throw error;
|
|
@@ -3671,15 +3839,15 @@ function existingPathKind(linkPath) {
|
|
|
3671
3839
|
function replaceMarketplaceLink(linkPath, targetPath, type) {
|
|
3672
3840
|
const existing = existingPathKind(linkPath);
|
|
3673
3841
|
if (existing === "other") return "preserved";
|
|
3674
|
-
if (existing === "symlink")
|
|
3675
|
-
|
|
3842
|
+
if (existing === "symlink") fs7.unlinkSync(linkPath);
|
|
3843
|
+
fs7.symlinkSync(targetPath, linkPath, type);
|
|
3676
3844
|
return existing === "symlink" ? "replaced" : "created";
|
|
3677
3845
|
}
|
|
3678
3846
|
function removeMarketplaceLink(linkPath) {
|
|
3679
3847
|
const existing = existingPathKind(linkPath);
|
|
3680
3848
|
if (existing === "missing") return "missing";
|
|
3681
3849
|
if (existing === "other") return "preserved";
|
|
3682
|
-
|
|
3850
|
+
fs7.unlinkSync(linkPath);
|
|
3683
3851
|
return "removed";
|
|
3684
3852
|
}
|
|
3685
3853
|
|
|
@@ -3774,10 +3942,10 @@ function configRecord(value) {
|
|
|
3774
3942
|
}
|
|
3775
3943
|
|
|
3776
3944
|
// ../../packages/agent-targets/src/mcp/targets/claude-desktop.ts
|
|
3777
|
-
import
|
|
3945
|
+
import path13 from "path";
|
|
3778
3946
|
|
|
3779
3947
|
// ../../packages/agent-targets/src/mcp/capability.ts
|
|
3780
|
-
import
|
|
3948
|
+
import path12 from "path";
|
|
3781
3949
|
import { parse as parse4 } from "smol-toml";
|
|
3782
3950
|
|
|
3783
3951
|
// ../../packages/agent-targets/src/mcp/toml.ts
|
|
@@ -3883,7 +4051,7 @@ function changedConfigPlan(target2, current, before, data) {
|
|
|
3883
4051
|
function generatedServer(target2, context) {
|
|
3884
4052
|
return fmlMcpServerConfig(
|
|
3885
4053
|
target2.command,
|
|
3886
|
-
|
|
4054
|
+
path12.join(context.packageRoot, "bin", "mcp-server")
|
|
3887
4055
|
);
|
|
3888
4056
|
}
|
|
3889
4057
|
function planInstall4(target2, context) {
|
|
@@ -3963,7 +4131,7 @@ var CLAUDE_DESKTOP_DIRECT_MCP = createDirectMcpCapability({
|
|
|
3963
4131
|
configKey: "mcpServers",
|
|
3964
4132
|
configPath: ({ env, homeDir, platform }) => {
|
|
3965
4133
|
if (platform === "darwin") {
|
|
3966
|
-
return
|
|
4134
|
+
return path13.join(
|
|
3967
4135
|
homeDir,
|
|
3968
4136
|
"Library",
|
|
3969
4137
|
"Application Support",
|
|
@@ -3972,14 +4140,14 @@ var CLAUDE_DESKTOP_DIRECT_MCP = createDirectMcpCapability({
|
|
|
3972
4140
|
);
|
|
3973
4141
|
}
|
|
3974
4142
|
if (platform === "win32") {
|
|
3975
|
-
return
|
|
3976
|
-
env.APPDATA ??
|
|
4143
|
+
return path13.join(
|
|
4144
|
+
env.APPDATA ?? path13.join(homeDir, "AppData", "Roaming"),
|
|
3977
4145
|
"Claude",
|
|
3978
4146
|
"claude_desktop_config.json"
|
|
3979
4147
|
);
|
|
3980
4148
|
}
|
|
3981
|
-
return
|
|
3982
|
-
env.XDG_CONFIG_HOME ??
|
|
4149
|
+
return path13.join(
|
|
4150
|
+
env.XDG_CONFIG_HOME ?? path13.join(homeDir, ".config"),
|
|
3983
4151
|
"Claude",
|
|
3984
4152
|
"claude_desktop_config.json"
|
|
3985
4153
|
);
|
|
@@ -3988,7 +4156,7 @@ var CLAUDE_DESKTOP_DIRECT_MCP = createDirectMcpCapability({
|
|
|
3988
4156
|
});
|
|
3989
4157
|
|
|
3990
4158
|
// ../../packages/agent-targets/src/mcp/targets/codex.ts
|
|
3991
|
-
import
|
|
4159
|
+
import path14 from "path";
|
|
3992
4160
|
var TARGET6 = agentTarget("codex");
|
|
3993
4161
|
var CODEX_DIRECT_MCP = createDirectMcpCapability({
|
|
3994
4162
|
targetId: TARGET6.id,
|
|
@@ -3997,26 +4165,26 @@ var CODEX_DIRECT_MCP = createDirectMcpCapability({
|
|
|
3997
4165
|
configKey: "mcp_servers",
|
|
3998
4166
|
configPath: ({ env, homeDir }) => {
|
|
3999
4167
|
const configuredHome = env.CODEX_HOME;
|
|
4000
|
-
const codexHome = typeof configuredHome === "string" && configuredHome.length > 0 ?
|
|
4001
|
-
return
|
|
4168
|
+
const codexHome = typeof configuredHome === "string" && configuredHome.length > 0 ? path14.resolve(configuredHome) : path14.join(homeDir, ".codex");
|
|
4169
|
+
return path14.join(codexHome, "config.toml");
|
|
4002
4170
|
},
|
|
4003
4171
|
command: () => "node"
|
|
4004
4172
|
});
|
|
4005
4173
|
|
|
4006
4174
|
// ../../packages/agent-targets/src/mcp/targets/gemini.ts
|
|
4007
|
-
import
|
|
4175
|
+
import path15 from "path";
|
|
4008
4176
|
var TARGET7 = agentTarget("gemini");
|
|
4009
4177
|
var GEMINI_DIRECT_MCP = createDirectMcpCapability({
|
|
4010
4178
|
targetId: TARGET7.id,
|
|
4011
4179
|
displayName: TARGET7.displayName,
|
|
4012
4180
|
configFormat: "json",
|
|
4013
4181
|
configKey: "mcpServers",
|
|
4014
|
-
configPath: (context) =>
|
|
4182
|
+
configPath: (context) => path15.join(geminiConfigDirectory(context), "settings.json"),
|
|
4015
4183
|
command: () => "node"
|
|
4016
4184
|
});
|
|
4017
4185
|
|
|
4018
4186
|
// ../../packages/agent-targets/src/mcp/targets/grok.ts
|
|
4019
|
-
import
|
|
4187
|
+
import path16 from "path";
|
|
4020
4188
|
var TARGET8 = agentTarget("grok");
|
|
4021
4189
|
var GROK_DIRECT_MCP = createDirectMcpCapability({
|
|
4022
4190
|
targetId: TARGET8.id,
|
|
@@ -4025,8 +4193,8 @@ var GROK_DIRECT_MCP = createDirectMcpCapability({
|
|
|
4025
4193
|
configKey: "mcp_servers",
|
|
4026
4194
|
configPath: ({ env, homeDir }) => {
|
|
4027
4195
|
const configuredHome = env.GROK_HOME;
|
|
4028
|
-
const grokHome = typeof configuredHome === "string" && configuredHome.length > 0 ?
|
|
4029
|
-
return
|
|
4196
|
+
const grokHome = typeof configuredHome === "string" && configuredHome.length > 0 ? path16.resolve(configuredHome) : path16.join(homeDir, ".grok");
|
|
4197
|
+
return path16.join(grokHome, "config.toml");
|
|
4030
4198
|
},
|
|
4031
4199
|
command: () => "node"
|
|
4032
4200
|
});
|
|
@@ -4040,7 +4208,7 @@ var DIRECT_MCP_CAPABILITIES = Object.freeze([
|
|
|
4040
4208
|
]);
|
|
4041
4209
|
|
|
4042
4210
|
// src/commands/mcp-config.ts
|
|
4043
|
-
import
|
|
4211
|
+
import fs8 from "fs";
|
|
4044
4212
|
import os3 from "os";
|
|
4045
4213
|
function resolutionContext(options) {
|
|
4046
4214
|
return {
|
|
@@ -4053,7 +4221,7 @@ function resolutionContext(options) {
|
|
|
4053
4221
|
function readMcpFile(filePath) {
|
|
4054
4222
|
let stat;
|
|
4055
4223
|
try {
|
|
4056
|
-
stat =
|
|
4224
|
+
stat = fs8.lstatSync(filePath);
|
|
4057
4225
|
} catch (error) {
|
|
4058
4226
|
if (error.code === "ENOENT") {
|
|
4059
4227
|
return { path: filePath, kind: "missing" };
|
|
@@ -4082,7 +4250,7 @@ function readMcpFile(filePath) {
|
|
|
4082
4250
|
return {
|
|
4083
4251
|
path: filePath,
|
|
4084
4252
|
kind: "file",
|
|
4085
|
-
contents:
|
|
4253
|
+
contents: fs8.readFileSync(filePath, "utf8")
|
|
4086
4254
|
};
|
|
4087
4255
|
} catch (error) {
|
|
4088
4256
|
return {
|
|
@@ -4118,21 +4286,21 @@ function preflightMutation(mutation) {
|
|
|
4118
4286
|
}
|
|
4119
4287
|
function writeMutation(mutation) {
|
|
4120
4288
|
if (mutation.after === null) {
|
|
4121
|
-
|
|
4289
|
+
fs8.rmSync(mutation.path, { force: true });
|
|
4122
4290
|
return;
|
|
4123
4291
|
}
|
|
4124
4292
|
writeTextFileAtomic(mutation.path, mutation.after);
|
|
4125
4293
|
}
|
|
4126
4294
|
function rollbackMutation2(mutation) {
|
|
4127
4295
|
if (mutation.before === null) {
|
|
4128
|
-
|
|
4296
|
+
fs8.rmSync(mutation.path, { force: true });
|
|
4129
4297
|
return;
|
|
4130
4298
|
}
|
|
4131
4299
|
writeTextFileAtomic(mutation.path, mutation.before);
|
|
4132
4300
|
}
|
|
4133
4301
|
function warnSkippedPlan(plan, error, warn) {
|
|
4134
4302
|
warn(
|
|
4135
|
-
`
|
|
4303
|
+
`Warning: skipped ${plan.displayName} MCP config at ${plan.configPath}: ${error}`
|
|
4136
4304
|
);
|
|
4137
4305
|
}
|
|
4138
4306
|
function applyPlan2(plan, warn) {
|
|
@@ -4176,14 +4344,17 @@ function reportPlan(plan, warn, log) {
|
|
|
4176
4344
|
for (const note of plan.notes) log(note);
|
|
4177
4345
|
}
|
|
4178
4346
|
function configureFmlDirectMcp(pluginRoot, options = {}) {
|
|
4179
|
-
const
|
|
4347
|
+
const emitWarning = (message) => {
|
|
4348
|
+
(options.warn ?? console.warn)(message);
|
|
4349
|
+
options.onFailure?.(message);
|
|
4350
|
+
};
|
|
4180
4351
|
const log = options.log ?? console.log;
|
|
4181
4352
|
const context = plannerContext2(pluginRoot, options);
|
|
4182
4353
|
const changes = [];
|
|
4183
4354
|
for (const capability of DIRECT_MCP_CAPABILITIES) {
|
|
4184
4355
|
const plan = capability.planInstall(context);
|
|
4185
|
-
reportPlan(plan,
|
|
4186
|
-
if (!applyPlan2(plan,
|
|
4356
|
+
reportPlan(plan, emitWarning, log);
|
|
4357
|
+
if (!applyPlan2(plan, emitWarning)) continue;
|
|
4187
4358
|
changes.push({
|
|
4188
4359
|
targetId: plan.targetId,
|
|
4189
4360
|
displayName: plan.displayName,
|
|
@@ -4212,13 +4383,13 @@ function removeFmlDirectMcp(target2 = "all", options = {}) {
|
|
|
4212
4383
|
}
|
|
4213
4384
|
|
|
4214
4385
|
// src/commands/agent-skills.ts
|
|
4215
|
-
import { createHash as createHash2, randomUUID as
|
|
4216
|
-
import
|
|
4386
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
4387
|
+
import fs9 from "fs";
|
|
4217
4388
|
import os4 from "os";
|
|
4218
|
-
import
|
|
4389
|
+
import path21 from "path";
|
|
4219
4390
|
|
|
4220
4391
|
// ../../packages/agent-targets/src/install/surfaces/capability.ts
|
|
4221
|
-
import
|
|
4392
|
+
import path17 from "path";
|
|
4222
4393
|
var SOURCE_PATH_SEGMENTS = ["agent-assets", "fml", "SKILL.md"];
|
|
4223
4394
|
var DESTINATION_PATH_SEGMENTS = ["skills", "fml", "SKILL.md"];
|
|
4224
4395
|
function createAgentSkillCapability(definition) {
|
|
@@ -4231,8 +4402,8 @@ function createAgentSkillCapability(definition) {
|
|
|
4231
4402
|
resolve: (context) => Object.freeze({
|
|
4232
4403
|
targetId: definition.targetId,
|
|
4233
4404
|
displayName: definition.displayName,
|
|
4234
|
-
sourcePath:
|
|
4235
|
-
destinationPath:
|
|
4405
|
+
sourcePath: path17.join(context.packageRoot, ...SOURCE_PATH_SEGMENTS),
|
|
4406
|
+
destinationPath: path17.join(
|
|
4236
4407
|
definition.destinationRoot(context),
|
|
4237
4408
|
...DESTINATION_PATH_SEGMENTS
|
|
4238
4409
|
),
|
|
@@ -4255,7 +4426,7 @@ var CLAUDE_AGENT_SKILL = createAgentSkillCapability({
|
|
|
4255
4426
|
});
|
|
4256
4427
|
|
|
4257
4428
|
// ../../packages/agent-targets/src/install/surfaces/targets/codex.ts
|
|
4258
|
-
import
|
|
4429
|
+
import path18 from "path";
|
|
4259
4430
|
var TARGET10 = agentTarget("codex");
|
|
4260
4431
|
var CODEX_AGENT_SKILL = createAgentSkillCapability({
|
|
4261
4432
|
targetId: TARGET10.id,
|
|
@@ -4263,11 +4434,11 @@ var CODEX_AGENT_SKILL = createAgentSkillCapability({
|
|
|
4263
4434
|
invocation: "$fml",
|
|
4264
4435
|
deliveryMode: "instruction-read",
|
|
4265
4436
|
argumentMode: "user-input",
|
|
4266
|
-
destinationRoot: ({ homeDir }) =>
|
|
4437
|
+
destinationRoot: ({ homeDir }) => path18.join(homeDir, ".agents")
|
|
4267
4438
|
});
|
|
4268
4439
|
|
|
4269
4440
|
// ../../packages/agent-targets/src/install/surfaces/targets/gemini.ts
|
|
4270
|
-
import
|
|
4441
|
+
import path19 from "path";
|
|
4271
4442
|
var TARGET11 = agentTarget("gemini");
|
|
4272
4443
|
var GEMINI_AGENT_SKILL = createAgentSkillCapability({
|
|
4273
4444
|
targetId: TARGET11.id,
|
|
@@ -4277,13 +4448,13 @@ var GEMINI_AGENT_SKILL = createAgentSkillCapability({
|
|
|
4277
4448
|
argumentMode: "post-submit-prompt",
|
|
4278
4449
|
destinationRoot: ({ env, homeDir }) => {
|
|
4279
4450
|
const configuredHome = env.GEMINI_CLI_HOME?.trim();
|
|
4280
|
-
const homeRoot = configuredHome ?
|
|
4281
|
-
return
|
|
4451
|
+
const homeRoot = configuredHome ? path19.resolve(configuredHome) : homeDir;
|
|
4452
|
+
return path19.join(homeRoot, ".agents");
|
|
4282
4453
|
}
|
|
4283
4454
|
});
|
|
4284
4455
|
|
|
4285
4456
|
// ../../packages/agent-targets/src/install/surfaces/targets/grok.ts
|
|
4286
|
-
import
|
|
4457
|
+
import path20 from "path";
|
|
4287
4458
|
var TARGET12 = agentTarget("grok");
|
|
4288
4459
|
var GROK_AGENT_SKILL = createAgentSkillCapability({
|
|
4289
4460
|
targetId: TARGET12.id,
|
|
@@ -4291,7 +4462,7 @@ var GROK_AGENT_SKILL = createAgentSkillCapability({
|
|
|
4291
4462
|
invocation: "/fml",
|
|
4292
4463
|
deliveryMode: "prompt-expansion",
|
|
4293
4464
|
argumentMode: "template-or-append",
|
|
4294
|
-
destinationRoot: ({ homeDir }) =>
|
|
4465
|
+
destinationRoot: ({ homeDir }) => path20.join(homeDir, ".agents")
|
|
4295
4466
|
});
|
|
4296
4467
|
|
|
4297
4468
|
// ../../packages/agent-targets/src/install/surfaces/registry.ts
|
|
@@ -4305,7 +4476,7 @@ var AGENT_SKILL_CAPABILITIES = Object.freeze([
|
|
|
4305
4476
|
// src/commands/agent-skills.ts
|
|
4306
4477
|
var FML_AGENT_SKILL_MARKER = "<!-- fml-managed-agent-surface:v1 -->";
|
|
4307
4478
|
var AGENT_SKILL_STATE_SCHEMA_VERSION = 1;
|
|
4308
|
-
var DEFAULT_STATE_PATH =
|
|
4479
|
+
var DEFAULT_STATE_PATH = path21.join(FML_DATA_DIR, "agent-skills.json");
|
|
4309
4480
|
function emptyState() {
|
|
4310
4481
|
return Object.freeze({
|
|
4311
4482
|
schemaVersion: AGENT_SKILL_STATE_SCHEMA_VERSION,
|
|
@@ -4318,7 +4489,7 @@ function sha256(contents) {
|
|
|
4318
4489
|
function readSkillFile(filePath) {
|
|
4319
4490
|
let stat;
|
|
4320
4491
|
try {
|
|
4321
|
-
stat =
|
|
4492
|
+
stat = fs9.lstatSync(filePath);
|
|
4322
4493
|
} catch (error) {
|
|
4323
4494
|
if (error.code === "ENOENT") {
|
|
4324
4495
|
return { kind: "missing" };
|
|
@@ -4341,7 +4512,7 @@ function readSkillFile(filePath) {
|
|
|
4341
4512
|
};
|
|
4342
4513
|
}
|
|
4343
4514
|
try {
|
|
4344
|
-
const contents =
|
|
4515
|
+
const contents = fs9.readFileSync(filePath, "utf8");
|
|
4345
4516
|
return { kind: "file", contents, sha256: sha256(contents) };
|
|
4346
4517
|
} catch (error) {
|
|
4347
4518
|
return {
|
|
@@ -4410,7 +4581,7 @@ function parseState(source, statePath) {
|
|
|
4410
4581
|
}
|
|
4411
4582
|
entries.push(
|
|
4412
4583
|
Object.freeze({
|
|
4413
|
-
path:
|
|
4584
|
+
path: path21.resolve(entry.path),
|
|
4414
4585
|
sha256: entry.sha256,
|
|
4415
4586
|
targetIds: Object.freeze([...entry.targetIds])
|
|
4416
4587
|
})
|
|
@@ -4436,7 +4607,7 @@ function loadState(statePath) {
|
|
|
4436
4607
|
}
|
|
4437
4608
|
function serializeState(entries) {
|
|
4438
4609
|
const sorted = [...entries].map((entry) => ({
|
|
4439
|
-
path:
|
|
4610
|
+
path: path21.resolve(entry.path),
|
|
4440
4611
|
sha256: entry.sha256,
|
|
4441
4612
|
targetIds: [...entry.targetIds].sort()
|
|
4442
4613
|
})).sort((left, right) => left.path.localeCompare(right.path));
|
|
@@ -4475,7 +4646,7 @@ function resolvedGroups(pluginRoot, options) {
|
|
|
4475
4646
|
const groups = /* @__PURE__ */ new Map();
|
|
4476
4647
|
for (const capability of AGENT_SKILL_CAPABILITIES) {
|
|
4477
4648
|
const resolved = capability.resolve(context);
|
|
4478
|
-
const destinationPath =
|
|
4649
|
+
const destinationPath = path21.resolve(resolved.destinationPath);
|
|
4479
4650
|
const key = process.platform === "win32" ? destinationPath.toLowerCase() : destinationPath;
|
|
4480
4651
|
const current = groups.get(key) ?? [];
|
|
4481
4652
|
current.push({ ...resolved, destinationPath });
|
|
@@ -4512,10 +4683,10 @@ function resultFor(group, status, reason) {
|
|
|
4512
4683
|
});
|
|
4513
4684
|
}
|
|
4514
4685
|
function destinationDirectoryIssue(destinationPath, requireEmpty) {
|
|
4515
|
-
const skillDir =
|
|
4686
|
+
const skillDir = path21.dirname(destinationPath);
|
|
4516
4687
|
let stat;
|
|
4517
4688
|
try {
|
|
4518
|
-
stat =
|
|
4689
|
+
stat = fs9.lstatSync(skillDir);
|
|
4519
4690
|
} catch (error) {
|
|
4520
4691
|
if (error.code === "ENOENT") return null;
|
|
4521
4692
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -4523,7 +4694,7 @@ function destinationDirectoryIssue(destinationPath, requireEmpty) {
|
|
|
4523
4694
|
if (stat.isSymbolicLink()) return "skill directory is a symbolic link";
|
|
4524
4695
|
if (!stat.isDirectory()) return "skill directory is not a directory";
|
|
4525
4696
|
try {
|
|
4526
|
-
if (!requireEmpty ||
|
|
4697
|
+
if (!requireEmpty || fs9.readdirSync(skillDir).length === 0) return null;
|
|
4527
4698
|
return "skill directory contains unmanaged files";
|
|
4528
4699
|
} catch (error) {
|
|
4529
4700
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -4540,9 +4711,9 @@ function rollbackSkillWrite(destinationPath, before) {
|
|
|
4540
4711
|
writeTextFileAtomic(destinationPath, before.contents);
|
|
4541
4712
|
return;
|
|
4542
4713
|
}
|
|
4543
|
-
|
|
4714
|
+
fs9.rmSync(destinationPath, { force: true });
|
|
4544
4715
|
try {
|
|
4545
|
-
|
|
4716
|
+
fs9.rmdirSync(path21.dirname(destinationPath));
|
|
4546
4717
|
} catch {
|
|
4547
4718
|
}
|
|
4548
4719
|
}
|
|
@@ -4585,12 +4756,12 @@ function installManagedAgentSkills(pluginRoot, options = {}) {
|
|
|
4585
4756
|
const loaded = loadState(statePath);
|
|
4586
4757
|
let stateSource = loaded.source;
|
|
4587
4758
|
const entries = new Map(
|
|
4588
|
-
loaded.state.entries.map((entry) => [
|
|
4759
|
+
loaded.state.entries.map((entry) => [path21.resolve(entry.path), entry])
|
|
4589
4760
|
);
|
|
4590
4761
|
const results = [];
|
|
4591
4762
|
for (const group of resolvedGroups(pluginRoot, options)) {
|
|
4592
4763
|
const source = sourceSkill(group.sourcePath);
|
|
4593
|
-
const destinationPath =
|
|
4764
|
+
const destinationPath = path21.resolve(group.destinationPath);
|
|
4594
4765
|
const current = readSkillFile(destinationPath);
|
|
4595
4766
|
const previous = entries.get(destinationPath);
|
|
4596
4767
|
const nextEntry = stateEntryFor(
|
|
@@ -4675,11 +4846,11 @@ function removeSkillFile(args) {
|
|
|
4675
4846
|
`Agent skill changed before ${args.destinationPath} could be removed.`
|
|
4676
4847
|
);
|
|
4677
4848
|
}
|
|
4678
|
-
const backupPath =
|
|
4679
|
-
|
|
4680
|
-
`.${
|
|
4849
|
+
const backupPath = path21.join(
|
|
4850
|
+
path21.dirname(args.destinationPath),
|
|
4851
|
+
`.${path21.basename(args.destinationPath)}.${process.pid}.${randomUUID3()}.bak`
|
|
4681
4852
|
);
|
|
4682
|
-
|
|
4853
|
+
fs9.renameSync(args.destinationPath, backupPath);
|
|
4683
4854
|
let stateSource;
|
|
4684
4855
|
try {
|
|
4685
4856
|
stateSource = writeState(
|
|
@@ -4689,7 +4860,7 @@ function removeSkillFile(args) {
|
|
|
4689
4860
|
);
|
|
4690
4861
|
} catch (error) {
|
|
4691
4862
|
try {
|
|
4692
|
-
|
|
4863
|
+
fs9.renameSync(backupPath, args.destinationPath);
|
|
4693
4864
|
} catch (rollbackError) {
|
|
4694
4865
|
const message = error instanceof Error ? error.message : String(error);
|
|
4695
4866
|
const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
|
|
@@ -4701,12 +4872,12 @@ function removeSkillFile(args) {
|
|
|
4701
4872
|
}
|
|
4702
4873
|
let warning;
|
|
4703
4874
|
try {
|
|
4704
|
-
|
|
4875
|
+
fs9.rmSync(backupPath, { force: true });
|
|
4705
4876
|
} catch (error) {
|
|
4706
4877
|
warning = `agent skill removal was committed, but backup cleanup failed at ${backupPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
4707
4878
|
}
|
|
4708
4879
|
try {
|
|
4709
|
-
|
|
4880
|
+
fs9.rmdirSync(path21.dirname(args.destinationPath));
|
|
4710
4881
|
} catch {
|
|
4711
4882
|
}
|
|
4712
4883
|
return { stateSource, ...warning ? { warning } : {} };
|
|
@@ -4716,16 +4887,16 @@ function removeManagedAgentSkills(pluginRoot, options = {}) {
|
|
|
4716
4887
|
const loaded = loadState(statePath);
|
|
4717
4888
|
let stateSource = loaded.source;
|
|
4718
4889
|
const entries = new Map(
|
|
4719
|
-
loaded.state.entries.map((entry) => [
|
|
4890
|
+
loaded.state.entries.map((entry) => [path21.resolve(entry.path), entry])
|
|
4720
4891
|
);
|
|
4721
4892
|
const candidates = /* @__PURE__ */ new Map();
|
|
4722
4893
|
const currentGroups = resolvedGroups(pluginRoot, options);
|
|
4723
|
-
const packagedSourcePath = currentGroups[0]?.sourcePath ??
|
|
4894
|
+
const packagedSourcePath = currentGroups[0]?.sourcePath ?? path21.join(pluginRoot, "agent-assets", "fml", "SKILL.md");
|
|
4724
4895
|
for (const group of currentGroups) {
|
|
4725
|
-
candidates.set(
|
|
4896
|
+
candidates.set(path21.resolve(group.destinationPath), group);
|
|
4726
4897
|
}
|
|
4727
4898
|
for (const entry of entries.values()) {
|
|
4728
|
-
const destinationPath =
|
|
4899
|
+
const destinationPath = path21.resolve(entry.path);
|
|
4729
4900
|
if (candidates.has(destinationPath)) continue;
|
|
4730
4901
|
const targets = entry.targetIds.filter(isAgentSkillTargetId).map(targetForId);
|
|
4731
4902
|
if (targets.length === 0) continue;
|
|
@@ -4740,7 +4911,7 @@ function removeManagedAgentSkills(pluginRoot, options = {}) {
|
|
|
4740
4911
|
for (const group of [...candidates.values()].sort(
|
|
4741
4912
|
(left, right) => left.destinationPath.localeCompare(right.destinationPath)
|
|
4742
4913
|
)) {
|
|
4743
|
-
const destinationPath =
|
|
4914
|
+
const destinationPath = path21.resolve(group.destinationPath);
|
|
4744
4915
|
const current = readSkillFile(destinationPath);
|
|
4745
4916
|
const previous = entries.get(destinationPath);
|
|
4746
4917
|
if (current.kind === "missing") {
|
|
@@ -4798,13 +4969,13 @@ function removeManagedAgentSkills(pluginRoot, options = {}) {
|
|
|
4798
4969
|
}
|
|
4799
4970
|
|
|
4800
4971
|
// src/commands/install.ts
|
|
4801
|
-
function resolvePluginRoot(startDir =
|
|
4972
|
+
function resolvePluginRoot(startDir = path22.dirname(fileURLToPath(import.meta.url))) {
|
|
4802
4973
|
let directory = startDir;
|
|
4803
4974
|
while (true) {
|
|
4804
|
-
if (
|
|
4975
|
+
if (fs10.existsSync(path22.join(directory, "package.json")) && fs10.existsSync(path22.join(directory, "bin"))) {
|
|
4805
4976
|
return directory;
|
|
4806
4977
|
}
|
|
4807
|
-
const parent =
|
|
4978
|
+
const parent = path22.dirname(directory);
|
|
4808
4979
|
if (parent === directory) {
|
|
4809
4980
|
throw new Error(
|
|
4810
4981
|
`Could not locate the FML package root: no ancestor of ${startDir} contains both package.json and bin/. Reinstall @fml-inc/fml and retry.`
|
|
@@ -4819,7 +4990,7 @@ function getPluginRoot() {
|
|
|
4819
4990
|
function readJsonFileForUpdate(filePath) {
|
|
4820
4991
|
let source;
|
|
4821
4992
|
try {
|
|
4822
|
-
source =
|
|
4993
|
+
source = fs10.readFileSync(filePath, "utf8");
|
|
4823
4994
|
} catch (error) {
|
|
4824
4995
|
if (error.code === "ENOENT") {
|
|
4825
4996
|
return { ok: true, exists: false, data: {} };
|
|
@@ -4867,14 +5038,13 @@ async function handleInstall(opts = {}) {
|
|
|
4867
5038
|
homeDir: os5.homedir(),
|
|
4868
5039
|
packageRoot: pluginRoot
|
|
4869
5040
|
});
|
|
4870
|
-
console.log("
|
|
4871
|
-
console.log("[1/5] Creating FML directories...");
|
|
5041
|
+
console.log("Configuring FML integrations...");
|
|
4872
5042
|
for (const root of configuredManagedRoots()) {
|
|
4873
5043
|
const preparation = prepareManagedDirectory(root);
|
|
4874
5044
|
if (!preparation.managed) {
|
|
4875
5045
|
const recovery = process.platform === "win32" ? "use a dedicated FML directory in a native location; custom Windows roots require manual cleanup" : "use a dedicated FML directory or follow the inspected custom-root adoption steps in https://github.com/fml-inc/fml/blob/main/docs/plugin-cleanup.md#adopt-an-existing-custom-root";
|
|
4876
5046
|
console.warn(
|
|
4877
|
-
`
|
|
5047
|
+
`Warning: ${root.directory} could not be safely marked as FML-owned because it is non-empty, symlinked, or not owned by the current user; ${recovery} before using \`fml uninstall --purge\`.`
|
|
4878
5048
|
);
|
|
4879
5049
|
}
|
|
4880
5050
|
}
|
|
@@ -4884,12 +5054,9 @@ async function handleInstall(opts = {}) {
|
|
|
4884
5054
|
FML_LOG_DIR,
|
|
4885
5055
|
FML_OBSERVER_DIR
|
|
4886
5056
|
])) {
|
|
4887
|
-
|
|
4888
|
-
console.log(` ${directory}`);
|
|
5057
|
+
fs10.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
4889
5058
|
}
|
|
4890
|
-
initializeSyncSettings(
|
|
4891
|
-
console.log();
|
|
4892
|
-
console.log("[2/5] Installing agent-native /fml skills...");
|
|
5059
|
+
initializeSyncSettings(path22.join(FML_OBSERVER_DIR, "sync.json"));
|
|
4893
5060
|
let skillReady = false;
|
|
4894
5061
|
try {
|
|
4895
5062
|
const skillResults = installManagedAgentSkills(pluginRoot);
|
|
@@ -4897,52 +5064,71 @@ async function handleInstall(opts = {}) {
|
|
|
4897
5064
|
for (const result of skillResults) {
|
|
4898
5065
|
const targets = result.displayNames.join(", ");
|
|
4899
5066
|
if (result.status === "preserved") {
|
|
4900
|
-
console.
|
|
4901
|
-
`
|
|
4902
|
-
);
|
|
4903
|
-
} else {
|
|
4904
|
-
console.log(
|
|
4905
|
-
` ${targets}: ${result.destinationPath} (${result.status})`
|
|
5067
|
+
console.warn(
|
|
5068
|
+
`Warning: preserved ${result.destinationPath} for ${targets}: ${result.reason}`
|
|
4906
5069
|
);
|
|
4907
5070
|
}
|
|
4908
5071
|
}
|
|
5072
|
+
} catch (error) {
|
|
5073
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5074
|
+
console.warn(
|
|
5075
|
+
`Warning: agent-native skill setup could not be completed: ${message}`
|
|
5076
|
+
);
|
|
5077
|
+
}
|
|
5078
|
+
let piExtensionReady = false;
|
|
5079
|
+
try {
|
|
5080
|
+
const piPaths = PI_EXTENSION_INSTALLER.resolvePaths({
|
|
5081
|
+
env: process.env,
|
|
5082
|
+
homeDir: os5.homedir(),
|
|
5083
|
+
packageRoot: pluginRoot,
|
|
5084
|
+
platform: process.platform
|
|
5085
|
+
});
|
|
5086
|
+
const piResult = PI_EXTENSION_INSTALLER.install(piPaths);
|
|
5087
|
+
piExtensionReady = piResult.status !== "preserved";
|
|
5088
|
+
if (piResult.status === "preserved") {
|
|
5089
|
+
console.log(
|
|
5090
|
+
` warn: preserved ${piResult.destinationPath} for Pi: ${piResult.reason}`
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
if (piResult.legacyRemoved) {
|
|
5094
|
+
console.log(" Removed the legacy Panopticon Pi extension");
|
|
5095
|
+
} else if (piResult.legacyPreserved) {
|
|
5096
|
+
console.log(
|
|
5097
|
+
` warn: preserved unrecognized Pi extension ${piPaths.legacyDestinationPath}; inspect and remove it if it belongs to Panopticon`
|
|
5098
|
+
);
|
|
5099
|
+
}
|
|
4909
5100
|
} catch (error) {
|
|
4910
5101
|
const message = error instanceof Error ? error.message : String(error);
|
|
4911
5102
|
console.log(
|
|
4912
|
-
` warn:
|
|
5103
|
+
` warn: Pi extension setup could not be completed: ${message}`
|
|
4913
5104
|
);
|
|
4914
5105
|
}
|
|
4915
|
-
|
|
4916
|
-
console.log("[3/5] Writing plugin manifest...");
|
|
4917
|
-
const packageJson = readJsonFile(path21.join(pluginRoot, "package.json"));
|
|
5106
|
+
const packageJson = readJsonFile(path22.join(pluginRoot, "package.json"));
|
|
4918
5107
|
const generatedManifest = readJsonFile(paths.pluginManifestPath);
|
|
4919
5108
|
const version = typeof generatedManifest?.version === "string" ? generatedManifest.version : typeof packageJson?.version === "string" ? packageJson.version : "0.0.0-dev";
|
|
4920
5109
|
writeJsonFile(
|
|
4921
5110
|
paths.pluginManifestPath,
|
|
4922
5111
|
CLAUDE_PLUGIN_INSTALLER.pluginManifest(version)
|
|
4923
5112
|
);
|
|
4924
|
-
console.log(` Version: ${version}
|
|
4925
|
-
`);
|
|
4926
|
-
console.log("[4/5] Registering the FML plugin...");
|
|
4927
5113
|
const claudeBin = resolveBin("claude");
|
|
4928
5114
|
const marketplaceResult = readJsonFileForUpdate(
|
|
4929
5115
|
paths.marketplaceManifestPath
|
|
4930
5116
|
);
|
|
4931
5117
|
let marketplaceReady = false;
|
|
4932
5118
|
if (!marketplaceResult.ok) {
|
|
4933
|
-
console.
|
|
4934
|
-
`
|
|
5119
|
+
console.warn(
|
|
5120
|
+
`Warning: preserved unreadable marketplace config ${paths.marketplaceManifestPath}: ${marketplaceResult.error}`
|
|
4935
5121
|
);
|
|
4936
5122
|
} else {
|
|
4937
|
-
|
|
5123
|
+
fs10.mkdirSync(paths.marketplaceDir, { recursive: true, mode: 448 });
|
|
4938
5124
|
const linkUpdate = replaceMarketplaceLink(
|
|
4939
5125
|
paths.marketplaceLinkPath,
|
|
4940
5126
|
pluginRoot,
|
|
4941
5127
|
marketplaceSymlinkType()
|
|
4942
5128
|
);
|
|
4943
5129
|
if (linkUpdate === "preserved") {
|
|
4944
|
-
console.
|
|
4945
|
-
`
|
|
5130
|
+
console.warn(
|
|
5131
|
+
`Warning: preserved non-symlink marketplace path ${paths.marketplaceLinkPath}; remove it manually to enable the FML Claude plugin`
|
|
4946
5132
|
);
|
|
4947
5133
|
} else {
|
|
4948
5134
|
const marketplace = CLAUDE_PLUGIN_INSTALLER.addMarketplaceEntry(
|
|
@@ -4955,8 +5141,8 @@ async function handleInstall(opts = {}) {
|
|
|
4955
5141
|
const settingsResult = readJsonFileForUpdate(paths.settingsPath);
|
|
4956
5142
|
let settingsReady = false;
|
|
4957
5143
|
if (!settingsResult.ok) {
|
|
4958
|
-
console.
|
|
4959
|
-
`
|
|
5144
|
+
console.warn(
|
|
5145
|
+
`Warning: preserved unreadable Claude settings ${paths.settingsPath}: ${settingsResult.error}`
|
|
4960
5146
|
);
|
|
4961
5147
|
} else if (marketplaceReady) {
|
|
4962
5148
|
const settings = CLAUDE_PLUGIN_INSTALLER.enableInSettings(
|
|
@@ -4966,21 +5152,23 @@ async function handleInstall(opts = {}) {
|
|
|
4966
5152
|
writeJsonFile(paths.settingsPath, settings);
|
|
4967
5153
|
settingsReady = true;
|
|
4968
5154
|
}
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
5155
|
+
let mcpReady = true;
|
|
5156
|
+
let claudePluginReady = true;
|
|
5157
|
+
configureFmlDirectMcp(pluginRoot, {
|
|
5158
|
+
warn: (message) => console.warn(message.replace(/^warn:/u, "Warning:")),
|
|
5159
|
+
onFailure: () => {
|
|
5160
|
+
mcpReady = false;
|
|
5161
|
+
}
|
|
5162
|
+
});
|
|
4972
5163
|
let hooksReady = true;
|
|
4973
|
-
|
|
5164
|
+
configureFmlHooks(pluginRoot, {
|
|
4974
5165
|
onFailure: () => {
|
|
4975
5166
|
hooksReady = false;
|
|
4976
5167
|
}
|
|
4977
5168
|
});
|
|
4978
|
-
for (const target2 of hookTargets) {
|
|
4979
|
-
console.log(` Hooks: ${target2.displayName} -> fml`);
|
|
4980
|
-
}
|
|
4981
5169
|
if (!marketplaceReady || !settingsReady) {
|
|
4982
|
-
console.
|
|
4983
|
-
"
|
|
5170
|
+
console.warn(
|
|
5171
|
+
"Warning: skipped Claude plugin registration until its existing config is repaired"
|
|
4984
5172
|
);
|
|
4985
5173
|
} else if (claudeBin) {
|
|
4986
5174
|
try {
|
|
@@ -4995,16 +5183,14 @@ async function handleInstall(opts = {}) {
|
|
|
4995
5183
|
timeout: command.timeoutMs
|
|
4996
5184
|
});
|
|
4997
5185
|
}
|
|
4998
|
-
console.log(" Plugin registered via Claude Code CLI");
|
|
4999
5186
|
} catch (error) {
|
|
5187
|
+
claudePluginReady = false;
|
|
5000
5188
|
const message = error instanceof Error ? error.message : String(error);
|
|
5001
|
-
console.
|
|
5189
|
+
console.warn(`Warning: Claude plugin registration failed: ${message}`);
|
|
5002
5190
|
}
|
|
5003
5191
|
} else {
|
|
5004
|
-
console.
|
|
5192
|
+
console.warn("Warning: claude CLI not found; plugin files are ready");
|
|
5005
5193
|
}
|
|
5006
|
-
console.log();
|
|
5007
|
-
console.log("[5/5] Initializing the FML environment...");
|
|
5008
5194
|
if (!envConfigExists()) {
|
|
5009
5195
|
writeEnvConfig({
|
|
5010
5196
|
schemaVersion: 1,
|
|
@@ -5013,13 +5199,10 @@ async function handleInstall(opts = {}) {
|
|
|
5013
5199
|
[DEFAULT_TARGET_NAME]: { convexUrl: DEFAULT_PROD_URL }
|
|
5014
5200
|
}
|
|
5015
5201
|
});
|
|
5016
|
-
console.log(` ${DEFAULT_TARGET_NAME}: ${DEFAULT_PROD_URL}`);
|
|
5017
|
-
} else {
|
|
5018
|
-
console.log(" Preserved existing environment selection");
|
|
5019
5202
|
}
|
|
5020
|
-
if (!skillReady || !marketplaceReady || !settingsReady || !hooksReady) {
|
|
5203
|
+
if (!skillReady || !piExtensionReady || !marketplaceReady || !settingsReady || !hooksReady) {
|
|
5021
5204
|
throw new Error(
|
|
5022
|
-
"FML install is incomplete: repair the preserved agent skill, hook configuration, Claude plugin path, or configuration reported above, then run `fml install` again."
|
|
5205
|
+
"FML install is incomplete: repair the preserved agent skill, Pi extension, hook configuration, Claude plugin path, or configuration reported above, then run `fml install` again."
|
|
5023
5206
|
);
|
|
5024
5207
|
}
|
|
5025
5208
|
try {
|
|
@@ -5030,29 +5213,28 @@ async function handleInstall(opts = {}) {
|
|
|
5030
5213
|
`FML install is incomplete: hook capture remains paused because its state could not be cleared: ${message}`
|
|
5031
5214
|
);
|
|
5032
5215
|
}
|
|
5033
|
-
console.log("");
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5216
|
+
console.log("\u2713 Agent skills configured");
|
|
5217
|
+
if (mcpReady && claudePluginReady) {
|
|
5218
|
+
console.log("\u2713 Coding-agent integrations configured");
|
|
5219
|
+
console.log("FML integrations configured.");
|
|
5220
|
+
} else {
|
|
5221
|
+
console.warn(
|
|
5222
|
+
"Warning: Coding-agent integrations were not fully configured. See warnings above."
|
|
5223
|
+
);
|
|
5224
|
+
console.log("FML integration configuration finished with warnings.");
|
|
5225
|
+
}
|
|
5041
5226
|
}
|
|
5042
5227
|
|
|
5043
|
-
// src/commands/
|
|
5044
|
-
import
|
|
5045
|
-
import os7 from "os";
|
|
5046
|
-
import path27 from "path";
|
|
5047
|
-
import readline from "readline/promises";
|
|
5228
|
+
// src/commands/sync.ts
|
|
5229
|
+
import { statSync } from "fs";
|
|
5048
5230
|
|
|
5049
5231
|
// src/commands/otel-config.ts
|
|
5050
|
-
import
|
|
5232
|
+
import fs12 from "fs";
|
|
5051
5233
|
import os6 from "os";
|
|
5052
|
-
import
|
|
5234
|
+
import path27 from "path";
|
|
5053
5235
|
|
|
5054
5236
|
// ../../packages/agent-targets/src/install/otel/claude.ts
|
|
5055
|
-
import
|
|
5237
|
+
import path23 from "path";
|
|
5056
5238
|
|
|
5057
5239
|
// ../../packages/agent-targets/src/install/otel/shared.ts
|
|
5058
5240
|
import { isDeepStrictEqual as isDeepStrictEqual7 } from "util";
|
|
@@ -5189,9 +5371,9 @@ var MANAGED_MARKER = "FML_OTEL_MANAGED";
|
|
|
5189
5371
|
var CLAUDE_OTEL_SOURCE_LIMITATION = "Claude Code may truncate inline raw API bodies and tool content at 60 KiB and redact extended-thinking content before export";
|
|
5190
5372
|
function configPath(context) {
|
|
5191
5373
|
if (context.configPathOverride) {
|
|
5192
|
-
return
|
|
5374
|
+
return path23.resolve(context.configPathOverride);
|
|
5193
5375
|
}
|
|
5194
|
-
return
|
|
5376
|
+
return path23.join(
|
|
5195
5377
|
claudeConfigDirectory({ env: context.env, homeDir: context.homeDir }),
|
|
5196
5378
|
"settings.json"
|
|
5197
5379
|
);
|
|
@@ -5362,7 +5544,7 @@ var CLAUDE_OTEL_INSTALLER = Object.freeze({
|
|
|
5362
5544
|
|
|
5363
5545
|
// ../../packages/agent-targets/src/install/otel/codex.ts
|
|
5364
5546
|
import { isDeepStrictEqual as isDeepStrictEqual8 } from "util";
|
|
5365
|
-
import
|
|
5547
|
+
import path24 from "path";
|
|
5366
5548
|
import { parse as parse5 } from "smol-toml";
|
|
5367
5549
|
var TARGET14 = agentTarget("codex");
|
|
5368
5550
|
var TARGET_ID6 = TARGET14.id;
|
|
@@ -5371,13 +5553,13 @@ var AUTHENTICATION2 = "bearer-header";
|
|
|
5371
5553
|
var CODEX_OTEL_SOURCE_LIMITATION = "Codex OTel has no documented raw API body or full tool-content export switch; tool-result content is an output snippet";
|
|
5372
5554
|
function configPath2(context) {
|
|
5373
5555
|
if (context.configPathOverride) {
|
|
5374
|
-
return
|
|
5556
|
+
return path24.resolve(context.configPathOverride);
|
|
5375
5557
|
}
|
|
5376
5558
|
const configuredHome = context.env.CODEX_HOME;
|
|
5377
|
-
const codexHome =
|
|
5378
|
-
typeof configuredHome === "string" && configuredHome.trim().length > 0 ? configuredHome :
|
|
5559
|
+
const codexHome = path24.resolve(
|
|
5560
|
+
typeof configuredHome === "string" && configuredHome.trim().length > 0 ? configuredHome : path24.join(context.homeDir, ".codex")
|
|
5379
5561
|
);
|
|
5380
|
-
return
|
|
5562
|
+
return path24.join(codexHome, "config.toml");
|
|
5381
5563
|
}
|
|
5382
5564
|
function readToml(snapshot) {
|
|
5383
5565
|
const failure = snapshotError2(snapshot);
|
|
@@ -5597,7 +5779,7 @@ var CODEX_OTEL_INSTALLER = Object.freeze({
|
|
|
5597
5779
|
});
|
|
5598
5780
|
|
|
5599
5781
|
// ../../packages/agent-targets/src/install/otel/gemini.ts
|
|
5600
|
-
import
|
|
5782
|
+
import path25 from "path";
|
|
5601
5783
|
var TARGET15 = agentTarget("gemini");
|
|
5602
5784
|
var TARGET_ID7 = TARGET15.id;
|
|
5603
5785
|
var DISPLAY_NAME7 = TARGET15.displayName;
|
|
@@ -5605,9 +5787,9 @@ var AUTHENTICATION3 = "path-token";
|
|
|
5605
5787
|
var GEMINI_OTEL_SOURCE_LIMITATION = "Gemini CLI has no documented OTLP header setting; FML uses an observer-local tokenized loopback path";
|
|
5606
5788
|
function configPath3(context) {
|
|
5607
5789
|
if (context.configPathOverride) {
|
|
5608
|
-
return
|
|
5790
|
+
return path25.resolve(context.configPathOverride);
|
|
5609
5791
|
}
|
|
5610
|
-
return
|
|
5792
|
+
return path25.join(geminiConfigDirectory(context), "settings.json");
|
|
5611
5793
|
}
|
|
5612
5794
|
function managedTelemetry(context) {
|
|
5613
5795
|
const destination = resolveDestination(context.observer);
|
|
@@ -5761,8 +5943,8 @@ var OTEL_INSTALLER_CAPABILITIES = Object.freeze([
|
|
|
5761
5943
|
]);
|
|
5762
5944
|
|
|
5763
5945
|
// src/commands/otel-config-state.ts
|
|
5764
|
-
import
|
|
5765
|
-
import
|
|
5946
|
+
import fs11 from "fs";
|
|
5947
|
+
import path26 from "path";
|
|
5766
5948
|
var OTEL_CONFIG_STATE_SCHEMA_VERSION = 1;
|
|
5767
5949
|
function emptyState2() {
|
|
5768
5950
|
return Object.freeze({
|
|
@@ -5776,12 +5958,12 @@ function invalidState2(statePath, reason) {
|
|
|
5776
5958
|
);
|
|
5777
5959
|
}
|
|
5778
5960
|
function enforceOwnerOnlyMode(statePath) {
|
|
5779
|
-
if (process.platform !== "win32")
|
|
5961
|
+
if (process.platform !== "win32") fs11.chmodSync(statePath, 384);
|
|
5780
5962
|
}
|
|
5781
5963
|
function readStateFile(statePath) {
|
|
5782
5964
|
let stat;
|
|
5783
5965
|
try {
|
|
5784
|
-
stat =
|
|
5966
|
+
stat = fs11.lstatSync(statePath);
|
|
5785
5967
|
} catch (error) {
|
|
5786
5968
|
if (error.code === "ENOENT") {
|
|
5787
5969
|
return { kind: "missing" };
|
|
@@ -5799,7 +5981,7 @@ function readStateFile(statePath) {
|
|
|
5799
5981
|
}
|
|
5800
5982
|
try {
|
|
5801
5983
|
enforceOwnerOnlyMode(statePath);
|
|
5802
|
-
return { kind: "file", contents:
|
|
5984
|
+
return { kind: "file", contents: fs11.readFileSync(statePath, "utf8") };
|
|
5803
5985
|
} catch (error) {
|
|
5804
5986
|
return {
|
|
5805
5987
|
kind: "invalid",
|
|
@@ -5808,7 +5990,7 @@ function readStateFile(statePath) {
|
|
|
5808
5990
|
}
|
|
5809
5991
|
}
|
|
5810
5992
|
function otelConfigStateEntryKey(targetId, configPath4) {
|
|
5811
|
-
const resolved =
|
|
5993
|
+
const resolved = path26.resolve(configPath4);
|
|
5812
5994
|
const pathKey = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
5813
5995
|
return `${targetId}\0${pathKey}`;
|
|
5814
5996
|
}
|
|
@@ -5844,7 +6026,7 @@ function parseState2(source, statePath) {
|
|
|
5844
6026
|
if (typeof observerRecord.baseUrl !== "string" || observerRecord.baseUrl.length === 0 || typeof observerRecord.authToken !== "string" || observerRecord.authToken.length === 0) {
|
|
5845
6027
|
throw invalidState2(statePath, "contains an invalid observer destination");
|
|
5846
6028
|
}
|
|
5847
|
-
const resolvedPath =
|
|
6029
|
+
const resolvedPath = path26.resolve(entry.path);
|
|
5848
6030
|
const key = otelConfigStateEntryKey(entry.targetId, resolvedPath);
|
|
5849
6031
|
if (keys.has(key)) {
|
|
5850
6032
|
throw invalidState2(statePath, "contains duplicate entries");
|
|
@@ -5882,7 +6064,7 @@ function loadFmlOtelConfigState(statePath) {
|
|
|
5882
6064
|
function serializeState2(entries) {
|
|
5883
6065
|
const sorted = [...entries].map((entry) => ({
|
|
5884
6066
|
targetId: entry.targetId,
|
|
5885
|
-
path:
|
|
6067
|
+
path: path26.resolve(entry.path),
|
|
5886
6068
|
observer: {
|
|
5887
6069
|
baseUrl: entry.observer.baseUrl,
|
|
5888
6070
|
authToken: entry.observer.authToken
|
|
@@ -5919,7 +6101,7 @@ function isKnownOtelTargetId(targetId) {
|
|
|
5919
6101
|
}
|
|
5920
6102
|
|
|
5921
6103
|
// src/commands/otel-config.ts
|
|
5922
|
-
var DEFAULT_OTEL_CONFIG_STATE_PATH =
|
|
6104
|
+
var DEFAULT_OTEL_CONFIG_STATE_PATH = path27.join(
|
|
5923
6105
|
FML_DATA_DIR,
|
|
5924
6106
|
"otel-config.json"
|
|
5925
6107
|
);
|
|
@@ -5928,7 +6110,7 @@ function readLocalObserverOtelDestination(options) {
|
|
|
5928
6110
|
if (!authToken) return null;
|
|
5929
6111
|
try {
|
|
5930
6112
|
const parsed = JSON.parse(
|
|
5931
|
-
|
|
6113
|
+
fs12.readFileSync(options.endpointFile, "utf8")
|
|
5932
6114
|
);
|
|
5933
6115
|
const endpoint = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5934
6116
|
const host = endpoint?.host;
|
|
@@ -5945,7 +6127,7 @@ function readLocalObserverOtelDestination(options) {
|
|
|
5945
6127
|
function readOtelFile(filePath) {
|
|
5946
6128
|
let stat;
|
|
5947
6129
|
try {
|
|
5948
|
-
stat =
|
|
6130
|
+
stat = fs12.lstatSync(filePath);
|
|
5949
6131
|
} catch (error) {
|
|
5950
6132
|
if (error.code === "ENOENT") {
|
|
5951
6133
|
return { path: filePath, kind: "missing" };
|
|
@@ -5974,7 +6156,7 @@ function readOtelFile(filePath) {
|
|
|
5974
6156
|
return {
|
|
5975
6157
|
path: filePath,
|
|
5976
6158
|
kind: "file",
|
|
5977
|
-
contents:
|
|
6159
|
+
contents: fs12.readFileSync(filePath, "utf8")
|
|
5978
6160
|
};
|
|
5979
6161
|
} catch (error) {
|
|
5980
6162
|
return {
|
|
@@ -5996,7 +6178,7 @@ function assertMutationPrecondition2(mutation) {
|
|
|
5996
6178
|
throw new Error(`configuration changed while planning ${mutation.path}`);
|
|
5997
6179
|
}
|
|
5998
6180
|
function applyMutation2(mutation) {
|
|
5999
|
-
if (mutation.after === null)
|
|
6181
|
+
if (mutation.after === null) fs12.rmSync(mutation.path);
|
|
6000
6182
|
else writeTextFileAtomic(mutation.path, mutation.after);
|
|
6001
6183
|
}
|
|
6002
6184
|
function enforceOwnerOnlyConfigPermissions(filePath) {
|
|
@@ -6009,7 +6191,7 @@ function enforceOwnerOnlyConfigPermissions(filePath) {
|
|
|
6009
6191
|
);
|
|
6010
6192
|
}
|
|
6011
6193
|
try {
|
|
6012
|
-
|
|
6194
|
+
fs12.chmodSync(filePath, 384);
|
|
6013
6195
|
} catch (error) {
|
|
6014
6196
|
const detail = error instanceof Error ? error.message : String(error);
|
|
6015
6197
|
throw new Error(
|
|
@@ -6040,7 +6222,7 @@ function applyPlan3(plan, warn, commitOwnership) {
|
|
|
6040
6222
|
} catch (error) {
|
|
6041
6223
|
const detail = error instanceof Error ? error.message : String(error);
|
|
6042
6224
|
warn(
|
|
6043
|
-
`
|
|
6225
|
+
`Warning: skipped ${plan.displayName} OpenTelemetry at ${plan.configPath}: ${detail}`
|
|
6044
6226
|
);
|
|
6045
6227
|
return null;
|
|
6046
6228
|
}
|
|
@@ -6049,7 +6231,7 @@ function applyPlan3(plan, warn, commitOwnership) {
|
|
|
6049
6231
|
} catch (error) {
|
|
6050
6232
|
const detail = error instanceof Error ? error.message : String(error);
|
|
6051
6233
|
warn(
|
|
6052
|
-
`
|
|
6234
|
+
`Warning: could not record ${plan.displayName} OpenTelemetry ownership at ${plan.configPath}: ${detail}`
|
|
6053
6235
|
);
|
|
6054
6236
|
}
|
|
6055
6237
|
}
|
|
@@ -6058,7 +6240,7 @@ function applyPlan3(plan, warn, commitOwnership) {
|
|
|
6058
6240
|
const uniquePaths2 = new Set(plan.mutations.map((mutation) => mutation.path));
|
|
6059
6241
|
if (uniquePaths2.size !== plan.mutations.length) {
|
|
6060
6242
|
warn(
|
|
6061
|
-
`
|
|
6243
|
+
`Warning: skipped ${plan.displayName} OpenTelemetry at ${plan.configPath}: plan contains duplicate paths`
|
|
6062
6244
|
);
|
|
6063
6245
|
return null;
|
|
6064
6246
|
}
|
|
@@ -6089,7 +6271,7 @@ function applyPlan3(plan, warn, commitOwnership) {
|
|
|
6089
6271
|
const detail = error instanceof Error ? error.message : String(error);
|
|
6090
6272
|
const rollbackDetail = rollbackErrors.length === 0 ? "" : `; rollback failed: ${rollbackErrors.join("; ")}`;
|
|
6091
6273
|
warn(
|
|
6092
|
-
`
|
|
6274
|
+
`Warning: skipped ${plan.displayName} OpenTelemetry at ${plan.configPath}: ${detail}${rollbackDetail}`
|
|
6093
6275
|
);
|
|
6094
6276
|
return null;
|
|
6095
6277
|
}
|
|
@@ -6104,7 +6286,7 @@ function plannerContext3(observer, options, configPathOverride, configOwnershipR
|
|
|
6104
6286
|
observer,
|
|
6105
6287
|
homeDir: options.homeDir ?? os6.homedir(),
|
|
6106
6288
|
env: options.env ?? process.env,
|
|
6107
|
-
...configPathOverride ? { configPathOverride:
|
|
6289
|
+
...configPathOverride ? { configPathOverride: path27.resolve(configPathOverride) } : {},
|
|
6108
6290
|
...configOwnershipRecorded ? { configOwnershipRecorded: true } : {},
|
|
6109
6291
|
readFile: readOtelFile
|
|
6110
6292
|
};
|
|
@@ -6124,7 +6306,7 @@ function loadOtelConfigState(options, warn) {
|
|
|
6124
6306
|
)
|
|
6125
6307
|
};
|
|
6126
6308
|
} catch (error) {
|
|
6127
|
-
warn(`
|
|
6309
|
+
warn(`Warning: ${error instanceof Error ? error.message : String(error)}`);
|
|
6128
6310
|
return null;
|
|
6129
6311
|
}
|
|
6130
6312
|
}
|
|
@@ -6147,7 +6329,7 @@ function commitStateEntry(state, key, next) {
|
|
|
6147
6329
|
function stateEntry(plan, observer) {
|
|
6148
6330
|
return Object.freeze({
|
|
6149
6331
|
targetId: plan.targetId,
|
|
6150
|
-
path:
|
|
6332
|
+
path: path27.resolve(plan.configPath),
|
|
6151
6333
|
observer: Object.freeze({
|
|
6152
6334
|
baseUrl: observer.baseUrl,
|
|
6153
6335
|
authToken: observer.authToken
|
|
@@ -6171,7 +6353,7 @@ function planSafely(capability, operation, context, warn) {
|
|
|
6171
6353
|
return operation === "install" ? capability.planInstall(context) : capability.planUninstall(context);
|
|
6172
6354
|
} catch (error) {
|
|
6173
6355
|
warn(
|
|
6174
|
-
`
|
|
6356
|
+
`Warning: skipped ${capability.displayName} OpenTelemetry: ${error instanceof Error ? error.message : String(error)}`
|
|
6175
6357
|
);
|
|
6176
6358
|
return null;
|
|
6177
6359
|
}
|
|
@@ -6181,7 +6363,7 @@ function pruneMissingRecordedEntry(state, key, entry, warn) {
|
|
|
6181
6363
|
commitStateEntry(state, key, null);
|
|
6182
6364
|
} catch (error) {
|
|
6183
6365
|
warn(
|
|
6184
|
-
`
|
|
6366
|
+
`Warning: could not prune missing ${entry.targetId} OpenTelemetry ownership at ${entry.path}: ${error instanceof Error ? error.message : String(error)}`
|
|
6185
6367
|
);
|
|
6186
6368
|
}
|
|
6187
6369
|
}
|
|
@@ -6283,13 +6465,13 @@ function removeFmlOtel(observer, options = {}) {
|
|
|
6283
6465
|
}
|
|
6284
6466
|
if (candidate.recorded && candidate.plan.mutations.length === 0 && candidate.plan.warnings.length === 0 && state) {
|
|
6285
6467
|
warn(
|
|
6286
|
-
`
|
|
6468
|
+
`Warning: preserved ${candidate.plan.displayName} OpenTelemetry at ${candidate.plan.configPath}: managed fields were modified or removed after installation`
|
|
6287
6469
|
);
|
|
6288
6470
|
try {
|
|
6289
6471
|
commitStateEntry(state, key, null);
|
|
6290
6472
|
} catch (error) {
|
|
6291
6473
|
warn(
|
|
6292
|
-
`
|
|
6474
|
+
`Warning: could not relinquish ${candidate.plan.displayName} OpenTelemetry ownership at ${candidate.plan.configPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
6293
6475
|
);
|
|
6294
6476
|
}
|
|
6295
6477
|
}
|
|
@@ -6297,437 +6479,407 @@ function removeFmlOtel(observer, options = {}) {
|
|
|
6297
6479
|
return changes;
|
|
6298
6480
|
}
|
|
6299
6481
|
|
|
6300
|
-
// src/commands/
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
try {
|
|
6304
|
-
setHookCaptureDisabled(true);
|
|
6305
|
-
return true;
|
|
6306
|
-
} catch (error) {
|
|
6307
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
6308
|
-
console.warn(
|
|
6309
|
-
`Could not persist the hook-capture pause at ${FML_HOOK_CAPTURE_DISABLED_FILE}: ${message}`
|
|
6310
|
-
);
|
|
6311
|
-
return false;
|
|
6312
|
-
}
|
|
6482
|
+
// src/commands/sync.ts
|
|
6483
|
+
function rawSync() {
|
|
6484
|
+
return createCliRawSync();
|
|
6313
6485
|
}
|
|
6314
|
-
function
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
return
|
|
6319
|
-
} catch {
|
|
6320
|
-
return [logicalPath];
|
|
6486
|
+
function formatBytes(bytes) {
|
|
6487
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
6488
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
6489
|
+
if (bytes < 1024 * 1024 * 1024) {
|
|
6490
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
6321
6491
|
}
|
|
6492
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
6322
6493
|
}
|
|
6323
|
-
function
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6494
|
+
function formatDuration(durationMs) {
|
|
6495
|
+
if (durationMs < 1e3) return `${Math.round(durationMs)} ms`;
|
|
6496
|
+
return `${(durationMs / 1e3).toFixed(2)} s`;
|
|
6497
|
+
}
|
|
6498
|
+
function printRepositoryResolution(metrics) {
|
|
6499
|
+
if (metrics.attempted === 0 && metrics.budgetSkipped === 0) return;
|
|
6500
|
+
console.log(
|
|
6501
|
+
` Repo checks: ${metrics.attempted} (${metrics.resolved} resolved, ${metrics.unresolved} unresolved, ${metrics.cacheHits} cached, ${metrics.budgetSkipped} skipped by budget)`
|
|
6502
|
+
);
|
|
6503
|
+
console.log(
|
|
6504
|
+
` Repo check time: ${formatDuration(metrics.elapsedMs)} total, ${formatDuration(metrics.slowestMs)} slowest`
|
|
6505
|
+
);
|
|
6506
|
+
}
|
|
6507
|
+
function writeJson(value) {
|
|
6508
|
+
console.log(JSON.stringify(value, null, 2));
|
|
6509
|
+
}
|
|
6510
|
+
function warnRetainedLegacyDatabase(retained) {
|
|
6511
|
+
if (retained.pendingEvents !== null && retained.pendingHookEvents !== null && retained.pendingOtlpEnvelopes !== null) {
|
|
6512
|
+
console.warn(
|
|
6513
|
+
`Warning: Retained legacy observer database at ${retained.databaseFile} with ${retained.pendingEvents} pending rows (${retained.pendingHookEvents} hook, ${retained.pendingOtlpEnvelopes} OTLP); FML will not sync those rows automatically.`
|
|
6336
6514
|
);
|
|
6337
|
-
|
|
6338
|
-
fs12.rmSync(directory, { force: true });
|
|
6339
|
-
return true;
|
|
6340
|
-
}
|
|
6341
|
-
return false;
|
|
6342
|
-
}
|
|
6343
|
-
const directoryPaths = [resolvedDirectory];
|
|
6344
|
-
try {
|
|
6345
|
-
const canonicalDirectory = fs12.realpathSync(directory);
|
|
6346
|
-
if (!pathsAreSame(canonicalDirectory, resolvedDirectory)) {
|
|
6347
|
-
directoryPaths.push(canonicalDirectory);
|
|
6348
|
-
}
|
|
6349
|
-
} catch {
|
|
6350
|
-
}
|
|
6351
|
-
const containsPreservedPath = directoryPaths.some(
|
|
6352
|
-
(directoryPath) => preservedPaths.some(
|
|
6353
|
-
(preservedPath) => pathContains(directoryPath, preservedPath)
|
|
6354
|
-
)
|
|
6355
|
-
);
|
|
6356
|
-
if (!containsPreservedPath) {
|
|
6357
|
-
fs12.rmSync(directory, { recursive: true, force: true });
|
|
6358
|
-
return true;
|
|
6515
|
+
return;
|
|
6359
6516
|
}
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
(preservedPath) => pathsAreSame(directoryPath, path27.resolve(preservedPath))
|
|
6363
|
-
)
|
|
6517
|
+
console.warn(
|
|
6518
|
+
`Warning: Retained legacy observer database at ${retained.databaseFile}; pending row count unavailable. FML will not sync that database automatically.`
|
|
6364
6519
|
);
|
|
6365
|
-
if (isPreservedPath) return true;
|
|
6366
|
-
if (!directoryStats.isDirectory()) return false;
|
|
6367
|
-
let entries;
|
|
6368
|
-
try {
|
|
6369
|
-
entries = fs12.readdirSync(directory);
|
|
6370
|
-
} catch (error) {
|
|
6371
|
-
if (error.code === "ENOENT") return true;
|
|
6372
|
-
throw error;
|
|
6373
|
-
}
|
|
6374
|
-
let complete = true;
|
|
6375
|
-
for (const entry of entries) {
|
|
6376
|
-
const entryPath = path27.join(directory, entry);
|
|
6377
|
-
if (!purgeDirectoryPreservingPaths(entryPath, preservedPaths, false)) {
|
|
6378
|
-
complete = false;
|
|
6379
|
-
}
|
|
6380
|
-
}
|
|
6381
|
-
return complete;
|
|
6382
6520
|
}
|
|
6383
|
-
function
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
preservedFiles.flatMap(
|
|
6388
|
-
(preservedFile) => resolvePreservedPaths(preservedFile)
|
|
6389
|
-
),
|
|
6390
|
-
true
|
|
6521
|
+
function printStatus(status) {
|
|
6522
|
+
const { queue } = status;
|
|
6523
|
+
console.log(
|
|
6524
|
+
`Raw artifact sync: ${status.settings.enabled ? "enabled" : "disabled"}`
|
|
6391
6525
|
);
|
|
6392
|
-
}
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
}
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
fs12.writeFileSync(marker, FML_MANAGED_DIRECTORY_MARKER_CONTENT, {
|
|
6402
|
-
encoding: "utf8",
|
|
6403
|
-
flag: "wx",
|
|
6404
|
-
mode: 384
|
|
6405
|
-
});
|
|
6406
|
-
} catch (markerError) {
|
|
6407
|
-
if (markerError.code === "EEXIST" && hasManagedDirectoryMarker(directory)) {
|
|
6408
|
-
throw error;
|
|
6409
|
-
}
|
|
6410
|
-
const removalMessage = error instanceof Error ? error.message : String(error);
|
|
6411
|
-
const markerMessage = markerError instanceof Error ? markerError.message : String(markerError);
|
|
6412
|
-
throw new Error(
|
|
6413
|
-
`could not remove ${directory} (${removalMessage}) or restore its FML ownership marker (${markerMessage})`
|
|
6414
|
-
);
|
|
6415
|
-
}
|
|
6416
|
-
throw error;
|
|
6417
|
-
}
|
|
6418
|
-
}
|
|
6419
|
-
function cleanInstalledPluginScopes(paths) {
|
|
6420
|
-
const installedPlugins = readJsonFile(paths.installedPluginsPath);
|
|
6421
|
-
if (!installedPlugins) return;
|
|
6422
|
-
const update = CLAUDE_PLUGIN_INSTALLER.removeInstalledPlugin(
|
|
6423
|
-
installedPlugins,
|
|
6424
|
-
CLAUDE_PLUGIN_INSTALLER.pluginId
|
|
6526
|
+
console.log(` State: ${FML_OBSERVER_DIR}`);
|
|
6527
|
+
console.log(` Tracked: ${queue.tracked}`);
|
|
6528
|
+
console.log(` Pending: ${queue.pending}`);
|
|
6529
|
+
console.log(` Raw work: ${queue.rawPending}`);
|
|
6530
|
+
console.log(` Metadata: ${queue.metadataPending}`);
|
|
6531
|
+
console.log(` Uploaded: ${queue.uploaded}`);
|
|
6532
|
+
console.log(` Failed: ${queue.failed}`);
|
|
6533
|
+
console.log(
|
|
6534
|
+
` Watermark: ${formatBytes(queue.uploadedBytes)} / ${formatBytes(queue.totalBytes)}`
|
|
6425
6535
|
);
|
|
6426
|
-
|
|
6427
|
-
for (const settingsPath2 of CLAUDE_PLUGIN_INSTALLER.projectSettingsPaths(
|
|
6428
|
-
installedPlugins,
|
|
6429
|
-
CLAUDE_PLUGIN_INSTALLER.pluginId
|
|
6430
|
-
)) {
|
|
6431
|
-
if (removeEnabledPluginFromSettings(
|
|
6432
|
-
settingsPath2,
|
|
6433
|
-
CLAUDE_PLUGIN_INSTALLER.pluginId
|
|
6434
|
-
)) {
|
|
6435
|
-
console.log(` Cleaned ${settingsPath2}`);
|
|
6436
|
-
}
|
|
6437
|
-
}
|
|
6438
|
-
writeJsonFile(paths.installedPluginsPath, update.installedPlugins);
|
|
6439
|
-
console.log(" Cleaned installed_plugins.json");
|
|
6440
|
-
}
|
|
6441
|
-
function purgeWarning(roots) {
|
|
6442
|
-
return `${PURGE_WARNING}
|
|
6443
|
-
Purge roots:
|
|
6444
|
-
${roots.map((root) => ` - ${root.directory}`).join("\n")}`;
|
|
6536
|
+
console.log(` Remaining: ${formatBytes(queue.remainingBytes)}`);
|
|
6445
6537
|
}
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
if (
|
|
6449
|
-
|
|
6450
|
-
return true;
|
|
6451
|
-
}
|
|
6452
|
-
const input = opts.input ?? process.stdin;
|
|
6453
|
-
if (input.isTTY !== true) {
|
|
6454
|
-
console.error(
|
|
6455
|
-
`${warning}
|
|
6456
|
-
Re-run with --yes to confirm in a non-interactive session.`
|
|
6457
|
-
);
|
|
6458
|
-
return false;
|
|
6459
|
-
}
|
|
6460
|
-
const rl = readline.createInterface({
|
|
6461
|
-
input,
|
|
6462
|
-
output: opts.output ?? process.stdout
|
|
6463
|
-
});
|
|
6464
|
-
try {
|
|
6465
|
-
const closed = new Promise((resolve) => {
|
|
6466
|
-
rl.once("close", () => resolve(null));
|
|
6467
|
-
});
|
|
6468
|
-
const answer = await Promise.race([
|
|
6469
|
-
rl.question(`${warning}
|
|
6470
|
-
Type y to continue: `).catch(() => null),
|
|
6471
|
-
closed
|
|
6472
|
-
]);
|
|
6473
|
-
if (answer !== null && answer.trim().toLowerCase() === "y") return true;
|
|
6474
|
-
} finally {
|
|
6475
|
-
rl.close();
|
|
6476
|
-
}
|
|
6477
|
-
console.error("Uninstall canceled; nothing was removed.");
|
|
6478
|
-
return false;
|
|
6538
|
+
function syncExitCode(result) {
|
|
6539
|
+
if (result.outcome === "failed" || result.outcome === "exhausted") return 1;
|
|
6540
|
+
if (result.outcome === "busy") return 1;
|
|
6541
|
+
return 0;
|
|
6479
6542
|
}
|
|
6480
|
-
function
|
|
6481
|
-
const
|
|
6482
|
-
if (
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6543
|
+
async function handleSyncRun(opts = {}) {
|
|
6544
|
+
const sync2 = rawSync();
|
|
6545
|
+
if (!sync2.getSettings().enabled) {
|
|
6546
|
+
const result2 = {
|
|
6547
|
+
ok: true,
|
|
6548
|
+
outcome: "disabled",
|
|
6549
|
+
status: sync2.status()
|
|
6550
|
+
};
|
|
6551
|
+
if (opts.json) writeJson(result2);
|
|
6552
|
+
else printStatus(result2.status);
|
|
6553
|
+
return;
|
|
6486
6554
|
}
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
if (!await confirmPurge(opts, purgeRoots)) {
|
|
6498
|
-
process.exitCode = 1;
|
|
6499
|
-
return;
|
|
6555
|
+
if (opts.dryRun) {
|
|
6556
|
+
const discovery = sync2.discover();
|
|
6557
|
+
const bySource = {};
|
|
6558
|
+
let totalBytes = 0;
|
|
6559
|
+
for (const candidate of discovery.candidates) {
|
|
6560
|
+
bySource[candidate.source] = (bySource[candidate.source] ?? 0) + 1;
|
|
6561
|
+
try {
|
|
6562
|
+
totalBytes += statSync(candidate.sourcePath).size;
|
|
6563
|
+
} catch {
|
|
6564
|
+
}
|
|
6500
6565
|
}
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6566
|
+
const result2 = {
|
|
6567
|
+
ok: true,
|
|
6568
|
+
dryRun: true,
|
|
6569
|
+
targetUrl: getSiteUrl(),
|
|
6570
|
+
discovered: {
|
|
6571
|
+
count: discovery.candidates.length,
|
|
6572
|
+
totalBytes,
|
|
6573
|
+
bySource
|
|
6574
|
+
},
|
|
6575
|
+
repositoryResolution: discovery.repositoryResolution,
|
|
6576
|
+
oversized: discovery.oversized,
|
|
6577
|
+
status: sync2.status()
|
|
6578
|
+
};
|
|
6579
|
+
if (opts.json) writeJson(result2);
|
|
6580
|
+
else {
|
|
6581
|
+
console.log("FML raw artifact sync dry-run");
|
|
6582
|
+
console.log(` Target: ${result2.targetUrl}`);
|
|
6583
|
+
console.log(` Discovered: ${result2.discovered.count}`);
|
|
6584
|
+
console.log(` Bytes: ${formatBytes(result2.discovered.totalBytes)}`);
|
|
6585
|
+
for (const [source, count] of Object.entries(bySource).sort()) {
|
|
6586
|
+
console.log(` ${source}: ${count}`);
|
|
6587
|
+
}
|
|
6588
|
+
printRepositoryResolution(discovery.repositoryResolution);
|
|
6589
|
+
console.log(` Oversized: ${discovery.oversized.length}`);
|
|
6590
|
+
if (discovery.compressedArtifactsUnsupportedReason) {
|
|
6591
|
+
console.log(
|
|
6592
|
+
` Compressed: skipped \u2014 ${discovery.compressedArtifactsUnsupportedReason}`
|
|
6593
|
+
);
|
|
6504
6594
|
}
|
|
6505
6595
|
}
|
|
6506
|
-
if (refuseUnsafePurge(purgeRoots)) return;
|
|
6507
|
-
}
|
|
6508
|
-
console.log("Uninstalling fml...\n");
|
|
6509
|
-
const otelDestination = readLocalObserverOtelDestination({
|
|
6510
|
-
endpointFile: FML_OBSERVER_ENDPOINT_FILE,
|
|
6511
|
-
authTokenFile: FML_OBSERVER_AUTH_TOKEN_FILE
|
|
6512
|
-
});
|
|
6513
|
-
let observer;
|
|
6514
|
-
try {
|
|
6515
|
-
observer = await stopObserver();
|
|
6516
|
-
} catch (error) {
|
|
6517
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
6518
|
-
console.error(
|
|
6519
|
-
"Could not safely stop the FML observer; uninstall was aborted. Repair the reported files and rerun `fml uninstall`."
|
|
6520
|
-
);
|
|
6521
|
-
process.exitCode = 1;
|
|
6522
6596
|
return;
|
|
6523
6597
|
}
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
);
|
|
6598
|
+
const attempt = await runRawSyncOnce();
|
|
6599
|
+
if (attempt.kind === "unauthenticated") {
|
|
6600
|
+
console.error("Raw artifact sync requires an FML login.");
|
|
6601
|
+
console.error("Run `fml login` or set FML_TOKEN.");
|
|
6528
6602
|
process.exitCode = 1;
|
|
6529
6603
|
return;
|
|
6530
6604
|
}
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
});
|
|
6537
|
-
console.log("[1/7] Unregistering the Claude Code plugin...");
|
|
6538
|
-
const claudeBin = resolveBin("claude");
|
|
6539
|
-
if (claudeBin) {
|
|
6540
|
-
try {
|
|
6541
|
-
const command = CLAUDE_PLUGIN_INSTALLER.uninstallCommand;
|
|
6542
|
-
execBinSync(claudeBin, [...command.args], {
|
|
6543
|
-
timeout: command.timeoutMs
|
|
6544
|
-
});
|
|
6545
|
-
} catch {
|
|
6605
|
+
if (attempt.kind === "disabled") {
|
|
6606
|
+
if (opts.json) {
|
|
6607
|
+
writeJson({ ok: true, outcome: "disabled", status: attempt.status });
|
|
6608
|
+
} else {
|
|
6609
|
+
printStatus(attempt.status);
|
|
6546
6610
|
}
|
|
6611
|
+
return;
|
|
6547
6612
|
}
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
);
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6613
|
+
const { result, targetUrl } = attempt;
|
|
6614
|
+
if (opts.json) {
|
|
6615
|
+
writeJson({ ok: syncExitCode(result) === 0, targetUrl, result });
|
|
6616
|
+
} else {
|
|
6617
|
+
console.log("FML raw artifact sync");
|
|
6618
|
+
console.log(` Target: ${targetUrl}`);
|
|
6619
|
+
console.log(` Outcome: ${result.outcome}`);
|
|
6620
|
+
console.log(` Sync passes: ${result.syncPasses}`);
|
|
6621
|
+
console.log(` Discovered: ${result.discovery.candidates.length}`);
|
|
6622
|
+
if (result.discovery.compressedArtifactsUnsupportedReason) {
|
|
6623
|
+
console.log(
|
|
6624
|
+
` Compressed: skipped \u2014 ${result.discovery.compressedArtifactsUnsupportedReason}`
|
|
6625
|
+
);
|
|
6626
|
+
}
|
|
6627
|
+
printRepositoryResolution(result.discovery.repositoryResolution);
|
|
6628
|
+
console.log(` Uploaded files: ${result.upload.uploaded}`);
|
|
6629
|
+
console.log(` Uploaded chunks: ${result.upload.uploadedChunks}`);
|
|
6630
|
+
console.log(
|
|
6631
|
+
` Uploaded bytes: ${formatBytes(result.upload.uploadedBytes)}`
|
|
6559
6632
|
);
|
|
6633
|
+
console.log(` Failed: ${result.upload.failed}`);
|
|
6634
|
+
console.log(` Exhausted: ${result.upload.exhausted}`);
|
|
6635
|
+
if (result.upload.retryAtMs !== void 0) {
|
|
6636
|
+
console.log(
|
|
6637
|
+
` Retry at: ${new Date(result.upload.retryAtMs).toISOString()}`
|
|
6638
|
+
);
|
|
6639
|
+
}
|
|
6640
|
+
console.log(` Drained: ${result.drained ? "yes" : "no"}`);
|
|
6641
|
+
if (result.outcome === "pending") {
|
|
6642
|
+
console.log(
|
|
6643
|
+
" Next: More data remains; run `fml sync` again or `fml start` for continuous syncing."
|
|
6644
|
+
);
|
|
6645
|
+
} else if (result.outcome === "backing_off") {
|
|
6646
|
+
const nextRetryAtMs = result.upload.nextRetryAtMs;
|
|
6647
|
+
console.log(
|
|
6648
|
+
` Next: Remaining items are backing off after upload errors${nextRetryAtMs !== void 0 ? `; next retry due ${new Date(nextRetryAtMs).toISOString()}` : ""}. \`fml start\` retries automatically.`
|
|
6649
|
+
);
|
|
6650
|
+
}
|
|
6651
|
+
console.log("");
|
|
6652
|
+
printStatus(result.status);
|
|
6560
6653
|
}
|
|
6561
|
-
|
|
6562
|
-
|
|
6654
|
+
process.exitCode = syncExitCode(result);
|
|
6655
|
+
}
|
|
6656
|
+
function setRawSyncEnabled(enabled) {
|
|
6657
|
+
return rawSync().setEnabled(enabled);
|
|
6658
|
+
}
|
|
6659
|
+
async function handleSyncEnable(opts = {}) {
|
|
6660
|
+
const sync2 = rawSync();
|
|
6661
|
+
const settings = sync2.setEnabled(true);
|
|
6662
|
+
const isolationWarning = observerOnlyOverrideWarning();
|
|
6663
|
+
if (isolationWarning) console.warn(isolationWarning);
|
|
6664
|
+
let retainedLegacyDatabase;
|
|
6563
6665
|
try {
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6666
|
+
const observer = await startObserver({
|
|
6667
|
+
onLegacyDatabaseRetained: (retained) => {
|
|
6668
|
+
retainedLegacyDatabase = retained;
|
|
6669
|
+
if (!opts.json) warnRetainedLegacyDatabase(retained);
|
|
6670
|
+
}
|
|
6671
|
+
});
|
|
6672
|
+
setHookCaptureDisabled(false);
|
|
6673
|
+
if (observer.status === "already_running") {
|
|
6674
|
+
await wakeObserverSync();
|
|
6675
|
+
}
|
|
6676
|
+
const port = observer.runtime.health?.port;
|
|
6677
|
+
const authToken = port ? readObserverAuthToken(FML_OBSERVER_AUTH_TOKEN_FILE) : null;
|
|
6678
|
+
if (port && authToken) {
|
|
6679
|
+
configureFmlOtel({
|
|
6680
|
+
baseUrl: `http://127.0.0.1:${port}`,
|
|
6681
|
+
authToken
|
|
6682
|
+
});
|
|
6683
|
+
} else if (!opts.json) {
|
|
6684
|
+
console.warn(
|
|
6685
|
+
"Warning: Could not configure native OpenTelemetry: the healthy observer endpoint or local token is unavailable."
|
|
6686
|
+
);
|
|
6687
|
+
}
|
|
6688
|
+
if (opts.json) {
|
|
6689
|
+
writeJson({
|
|
6690
|
+
ok: true,
|
|
6691
|
+
sync: settings,
|
|
6692
|
+
observer,
|
|
6693
|
+
...observer.retainedLegacyDatabase ? { retainedLegacyDatabase: observer.retainedLegacyDatabase } : {}
|
|
6694
|
+
});
|
|
6695
|
+
} else {
|
|
6696
|
+
const pid = observer.runtime.health?.pid;
|
|
6697
|
+
if (observer.status === "started") {
|
|
6698
|
+
console.log(
|
|
6699
|
+
pid ? `FML observer started (PID ${pid}).` : "FML observer started."
|
|
6573
6700
|
);
|
|
6701
|
+
console.log("Agent activity will sync automatically.");
|
|
6574
6702
|
} else {
|
|
6575
6703
|
console.log(
|
|
6576
|
-
`
|
|
6704
|
+
pid ? `FML observer is already running (PID ${pid}).` : "FML observer is already running."
|
|
6577
6705
|
);
|
|
6706
|
+
console.log("Agent activity sync is on.");
|
|
6578
6707
|
}
|
|
6579
6708
|
}
|
|
6580
6709
|
} catch (error) {
|
|
6581
6710
|
const message = error instanceof Error ? error.message : String(error);
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
);
|
|
6591
|
-
}
|
|
6592
|
-
const marketplace = readJsonFile(paths.marketplaceManifestPath);
|
|
6593
|
-
if (marketplace && Array.isArray(marketplace.plugins)) {
|
|
6594
|
-
writeJsonFile(
|
|
6595
|
-
paths.marketplaceManifestPath,
|
|
6596
|
-
CLAUDE_PLUGIN_INSTALLER.removeMarketplaceEntry(
|
|
6597
|
-
marketplace,
|
|
6598
|
-
CLAUDE_PLUGIN_INSTALLER.pluginName
|
|
6599
|
-
)
|
|
6600
|
-
);
|
|
6601
|
-
}
|
|
6602
|
-
fs12.rmSync(paths.pluginCacheDir, { recursive: true, force: true });
|
|
6603
|
-
console.log(" Marketplace and plugin cache removed");
|
|
6604
|
-
console.log("[4/7] Removing FML hook registrations...");
|
|
6605
|
-
let hookRemovalComplete = true;
|
|
6606
|
-
for (const target2 of removeFmlHooks({
|
|
6607
|
-
onFailure: () => {
|
|
6608
|
-
hookRemovalComplete = false;
|
|
6609
|
-
}
|
|
6610
|
-
})) {
|
|
6611
|
-
console.log(` Removed FML hooks from ${target2.displayName}`);
|
|
6612
|
-
}
|
|
6613
|
-
if (!hookRemovalComplete) {
|
|
6711
|
+
if (opts.json) {
|
|
6712
|
+
writeJson({
|
|
6713
|
+
ok: false,
|
|
6714
|
+
sync: settings,
|
|
6715
|
+
error: message,
|
|
6716
|
+
...retainedLegacyDatabase ? { retainedLegacyDatabase } : {}
|
|
6717
|
+
});
|
|
6718
|
+
} else console.error(message);
|
|
6614
6719
|
process.exitCode = 1;
|
|
6615
|
-
console.warn(
|
|
6616
|
-
" Some FML hook registrations were preserved; repair the paths reported above and rerun `fml uninstall`."
|
|
6617
|
-
);
|
|
6618
6720
|
}
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6721
|
+
}
|
|
6722
|
+
async function handleSyncDisable(opts = {}) {
|
|
6723
|
+
const sync2 = rawSync();
|
|
6724
|
+
const settings = sync2.setEnabled(false);
|
|
6725
|
+
const observerWoken = await wakeObserverSync();
|
|
6726
|
+
if (opts.json) {
|
|
6727
|
+
writeJson({ ok: true, sync: settings, observerWoken });
|
|
6728
|
+
} else {
|
|
6729
|
+
console.log("Agent activity sync is paused.");
|
|
6730
|
+
const observer = await readObserverRuntimeStatus();
|
|
6731
|
+
if (observer.running) {
|
|
6732
|
+
console.log("FML observer remains available for local integrations");
|
|
6733
|
+
}
|
|
6622
6734
|
}
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6735
|
+
}
|
|
6736
|
+
async function handleSyncStatus(opts = {}) {
|
|
6737
|
+
const status = rawSync().status();
|
|
6738
|
+
const observer = await readObserverRuntimeStatus();
|
|
6739
|
+
if (opts.json) writeJson({ ok: true, status, observer });
|
|
6740
|
+
else {
|
|
6741
|
+
printStatus(status);
|
|
6742
|
+
const state = observer.stateStatus.state;
|
|
6743
|
+
console.log(
|
|
6744
|
+
` Observer: ${observer.healthy ? "running" : observer.running ? "unhealthy" : "stopped"}`
|
|
6626
6745
|
);
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
}
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
purgeComplete = false;
|
|
6637
|
-
} else {
|
|
6638
|
-
for (const root of purgeRoots) {
|
|
6639
|
-
if (refuseUnsafePurge([root])) {
|
|
6640
|
-
purgeComplete = false;
|
|
6641
|
-
continue;
|
|
6642
|
-
}
|
|
6643
|
-
const containsObserverDirectory = pathContains(
|
|
6644
|
-
root.directory,
|
|
6645
|
-
FML_OBSERVER_DIR
|
|
6746
|
+
if (state) {
|
|
6747
|
+
console.log(` PID: ${state.pid}`);
|
|
6748
|
+
console.log(` Phase: ${state.phase}`);
|
|
6749
|
+
if (state.lastOutcome) {
|
|
6750
|
+
console.log(` Last run: ${state.lastOutcome}`);
|
|
6751
|
+
}
|
|
6752
|
+
if (state.nextAttemptAtMs) {
|
|
6753
|
+
console.log(
|
|
6754
|
+
` Next run: ${new Date(state.nextAttemptAtMs).toISOString()}`
|
|
6646
6755
|
);
|
|
6647
|
-
const customManagedRoot = !root.nativeDefault;
|
|
6648
|
-
try {
|
|
6649
|
-
const purged = purgeDirectoryPreservingFiles(root.directory, [
|
|
6650
|
-
...customManagedRoot ? [managedDirectoryMarker(root.directory)] : [],
|
|
6651
|
-
...containsObserverDirectory && hookCapturePaused ? [FML_HOOK_CAPTURE_DISABLED_FILE] : []
|
|
6652
|
-
]);
|
|
6653
|
-
if (!purged) {
|
|
6654
|
-
purgeComplete = false;
|
|
6655
|
-
console.warn(
|
|
6656
|
-
` Preserved ${root.directory}: a symbolic link prevents a complete safe purge`
|
|
6657
|
-
);
|
|
6658
|
-
continue;
|
|
6659
|
-
}
|
|
6660
|
-
if (customManagedRoot && !containsObserverDirectory) {
|
|
6661
|
-
finishCustomRootPurge(root.directory);
|
|
6662
|
-
}
|
|
6663
|
-
console.log(` Purged ${root.directory}`);
|
|
6664
|
-
} catch (error) {
|
|
6665
|
-
purgeComplete = false;
|
|
6666
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
6667
|
-
console.warn(
|
|
6668
|
-
` Preserved ${root.directory}: the safe purge was incomplete: ${message}`
|
|
6669
|
-
);
|
|
6670
|
-
}
|
|
6671
6756
|
}
|
|
6672
6757
|
}
|
|
6673
|
-
|
|
6758
|
+
console.log(` Log: ${observer.logFile}`);
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
async function handleSyncReset(opts = {}) {
|
|
6762
|
+
const result = rawSync().reset();
|
|
6763
|
+
if (opts.json) writeJson({ ok: !result.skipped, reset: result });
|
|
6764
|
+
else if (result.skipped) {
|
|
6765
|
+
console.error(
|
|
6766
|
+
"Raw artifact reset skipped: another sync owns the queue lock"
|
|
6767
|
+
);
|
|
6674
6768
|
} else {
|
|
6675
6769
|
console.log(
|
|
6676
|
-
|
|
6770
|
+
`Reset raw upload watermarks for ${result.reset} artifact${result.reset === 1 ? "" : "s"}.`
|
|
6677
6771
|
);
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
console.
|
|
6682
|
-
"
|
|
6772
|
+
if (result.removed > 0) {
|
|
6773
|
+
console.log(`Removed ${result.removed} missing artifact entries.`);
|
|
6774
|
+
}
|
|
6775
|
+
console.log(
|
|
6776
|
+
"The next enabled sync will replay each artifact from byte zero."
|
|
6683
6777
|
);
|
|
6684
6778
|
}
|
|
6685
|
-
if (
|
|
6779
|
+
if (result.skipped) {
|
|
6686
6780
|
process.exitCode = 1;
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
);
|
|
6781
|
+
} else {
|
|
6782
|
+
await wakeObserverSync();
|
|
6690
6783
|
}
|
|
6691
|
-
console.log("\nDone! FML has been uninstalled.");
|
|
6692
6784
|
}
|
|
6693
6785
|
|
|
6694
|
-
// src/commands/
|
|
6695
|
-
async function
|
|
6696
|
-
|
|
6697
|
-
console.log(`Current: ${currentVersion}`);
|
|
6698
|
-
console.log("Updating via pnpm...\n");
|
|
6699
|
-
try {
|
|
6700
|
-
const pnpm = resolveBin("pnpm");
|
|
6701
|
-
if (!pnpm) throw new Error("pnpm was not found on PATH");
|
|
6702
|
-
const env = { ...process.env };
|
|
6703
|
-
delete env.npm_config_registry;
|
|
6704
|
-
execBinSync(pnpm, ["add", "--global", "@fml-inc/fml@latest"], {
|
|
6705
|
-
timeout: 12e4,
|
|
6706
|
-
env
|
|
6707
|
-
});
|
|
6708
|
-
console.log("\nUpdated. Run `fml install` to reconfigure.");
|
|
6709
|
-
} catch (err) {
|
|
6710
|
-
Sentry.captureException(err);
|
|
6711
|
-
const msg = err instanceof Error ? err.stderr?.toString() ?? err.message : String(err);
|
|
6712
|
-
console.error("Update failed:", msg);
|
|
6713
|
-
process.exit(1);
|
|
6714
|
-
}
|
|
6786
|
+
// src/commands/daemon.ts
|
|
6787
|
+
async function handleFmlStart() {
|
|
6788
|
+
await handleSyncEnable();
|
|
6715
6789
|
}
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6790
|
+
async function stopFml(opts = {}) {
|
|
6791
|
+
setRawSyncEnabled(false);
|
|
6792
|
+
const result = await stopObserver({ force: opts.force });
|
|
6793
|
+
let stopped = result.status === "stopped" || result.status === "killed" || result.status === "not_running" || result.status === "stale_state";
|
|
6794
|
+
if (stopped) {
|
|
6795
|
+
try {
|
|
6796
|
+
setHookCaptureDisabled(true);
|
|
6797
|
+
} catch (error) {
|
|
6798
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6799
|
+
console.error(
|
|
6800
|
+
`FML observer stopped, but the hook-capture pause could not be persisted: ${message}`
|
|
6801
|
+
);
|
|
6802
|
+
process.exitCode = 1;
|
|
6803
|
+
stopped = false;
|
|
6804
|
+
}
|
|
6725
6805
|
}
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6806
|
+
switch (result.status) {
|
|
6807
|
+
case "stopped":
|
|
6808
|
+
console.log(`FML observer stopped (PID ${result.pid}).`);
|
|
6809
|
+
console.log("Agent activity sync is paused.");
|
|
6810
|
+
break;
|
|
6811
|
+
case "killed":
|
|
6812
|
+
console.log(`FML observer stopped (PID ${result.pid}).`);
|
|
6813
|
+
console.log("Agent activity sync is paused.");
|
|
6814
|
+
break;
|
|
6815
|
+
case "not_running":
|
|
6816
|
+
console.log("FML observer is already stopped.");
|
|
6817
|
+
console.log("Agent activity sync is paused.");
|
|
6818
|
+
break;
|
|
6819
|
+
case "stale_state":
|
|
6820
|
+
console.log(
|
|
6821
|
+
"FML observer is stopped. Old process state will be cleaned on the next start."
|
|
6822
|
+
);
|
|
6823
|
+
console.log("Agent activity sync is paused.");
|
|
6824
|
+
break;
|
|
6825
|
+
case "permission_denied":
|
|
6826
|
+
console.error(
|
|
6827
|
+
`Could not stop FML observer PID ${result.pid}: permission denied`
|
|
6828
|
+
);
|
|
6829
|
+
process.exitCode = 1;
|
|
6830
|
+
break;
|
|
6831
|
+
case "foreign_listener":
|
|
6832
|
+
console.error("Refused to stop a different local observer instance");
|
|
6833
|
+
process.exitCode = 1;
|
|
6834
|
+
break;
|
|
6835
|
+
case "ambiguous_live_process":
|
|
6836
|
+
console.error(
|
|
6837
|
+
`Refused to treat unverifiable live PID ${result.pid} as a stopped FML observer`
|
|
6838
|
+
);
|
|
6839
|
+
process.exitCode = 1;
|
|
6840
|
+
break;
|
|
6841
|
+
case "unresponsive":
|
|
6842
|
+
console.error(
|
|
6843
|
+
`Refused to signal unverifiable FML observer PID ${result.pid}; retry with \`fml stop --force\` to escalate`
|
|
6844
|
+
);
|
|
6845
|
+
process.exitCode = 1;
|
|
6846
|
+
break;
|
|
6847
|
+
case "signal_sent":
|
|
6848
|
+
console.error(`FML observer PID ${result.pid} did not stop`);
|
|
6849
|
+
process.exitCode = 1;
|
|
6850
|
+
break;
|
|
6851
|
+
case "force_stop_unverified":
|
|
6852
|
+
console.error(
|
|
6853
|
+
`Could not corroborate that PID ${result.pid} is the FML observer; refusing to signal it. If the observer is not running, remove ${result.stateFile}, ${result.lockFile}, ${result.databaseStateFile}, and ${result.databaseLockFile} manually.`
|
|
6854
|
+
);
|
|
6855
|
+
process.exitCode = 1;
|
|
6856
|
+
break;
|
|
6857
|
+
}
|
|
6858
|
+
return stopped;
|
|
6859
|
+
}
|
|
6860
|
+
async function handleFmlStop(opts = {}) {
|
|
6861
|
+
await stopFml(opts);
|
|
6862
|
+
}
|
|
6863
|
+
async function handleFmlRestart() {
|
|
6864
|
+
if (await stopFml()) {
|
|
6865
|
+
await handleFmlStart();
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
|
|
6869
|
+
// src/auth/oauth.ts
|
|
6870
|
+
import crypto from "crypto";
|
|
6871
|
+
import http from "http";
|
|
6872
|
+
async function fetchWorkosClientId(siteUrl) {
|
|
6873
|
+
const response = await fetch(`${siteUrl}/api/auth/config`);
|
|
6874
|
+
const data = await response.json();
|
|
6875
|
+
if (!response.ok || data.ok === false || !data.workosClientId) {
|
|
6876
|
+
throw new Error(data.error ?? `HTTP ${response.status}`);
|
|
6877
|
+
}
|
|
6878
|
+
return data.workosClientId;
|
|
6879
|
+
}
|
|
6880
|
+
async function canOpenBrowser() {
|
|
6881
|
+
if (process.env.FML_DEVICE_FLOW === "1") return false;
|
|
6882
|
+
if (process.env.FML_NO_BROWSER === "1") return false;
|
|
6731
6883
|
if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
6732
6884
|
return false;
|
|
6733
6885
|
}
|
|
@@ -6735,8 +6887,8 @@ async function canOpenBrowser() {
|
|
|
6735
6887
|
if (process.env.CODESPACES) return false;
|
|
6736
6888
|
if (process.env.REMOTE_CONTAINERS) return false;
|
|
6737
6889
|
try {
|
|
6738
|
-
const
|
|
6739
|
-
if (
|
|
6890
|
+
const fs14 = await import("fs");
|
|
6891
|
+
if (fs14.existsSync("/.dockerenv")) return false;
|
|
6740
6892
|
} catch {
|
|
6741
6893
|
}
|
|
6742
6894
|
return true;
|
|
@@ -7194,8 +7346,9 @@ async function runServiceTokenLogin(refreshToken) {
|
|
|
7194
7346
|
}
|
|
7195
7347
|
await selectOrg(environment);
|
|
7196
7348
|
console.log("Logged in with FML service token.");
|
|
7197
|
-
console.log(
|
|
7198
|
-
|
|
7349
|
+
console.log(
|
|
7350
|
+
"Queries and agent activity sync will use the stored service token."
|
|
7351
|
+
);
|
|
7199
7352
|
}
|
|
7200
7353
|
async function handleServiceTokenLogin(refreshToken) {
|
|
7201
7354
|
try {
|
|
@@ -7212,16 +7365,12 @@ async function handleServiceTokenLogin(refreshToken) {
|
|
|
7212
7365
|
process.exit(1);
|
|
7213
7366
|
}
|
|
7214
7367
|
}
|
|
7215
|
-
async function
|
|
7216
|
-
if (opts?.serviceToken) {
|
|
7217
|
-
await handleServiceTokenLogin();
|
|
7218
|
-
return;
|
|
7219
|
-
}
|
|
7368
|
+
async function runInteractiveLogin(opts = {}) {
|
|
7220
7369
|
const environment = captureLoginEnvironment();
|
|
7221
7370
|
const envName = environment.envName;
|
|
7222
7371
|
const existingToken = await getValidToken({ env: envName });
|
|
7223
7372
|
if (existingToken) {
|
|
7224
|
-
const { readTokens: readTokens2 } = await import("./token-store-
|
|
7373
|
+
const { readTokens: readTokens2 } = await import("./token-store-BT5THIAJ.js");
|
|
7225
7374
|
const stored = readTokens2(envName);
|
|
7226
7375
|
if (stored?.tokenType === "service") {
|
|
7227
7376
|
const label = stored.user.id === SERVICE_TOKEN_LOGIN_USER_ID ? "a service token" : `${stored.user.name} (${stored.user.email})`;
|
|
@@ -7233,23 +7382,27 @@ async function handleLogin(opts) {
|
|
|
7233
7382
|
}
|
|
7234
7383
|
await linkGitHubIdentity(environment);
|
|
7235
7384
|
await selectOrg(environment);
|
|
7236
|
-
|
|
7237
|
-
process.exit(0);
|
|
7385
|
+
return;
|
|
7238
7386
|
}
|
|
7239
7387
|
console.log(`Signing in to FML (${envName})...`);
|
|
7388
|
+
const useDeviceFlow = opts.device || !await canOpenBrowser();
|
|
7389
|
+
let result;
|
|
7390
|
+
if (useDeviceFlow) {
|
|
7391
|
+
result = await deviceLogin(environment);
|
|
7392
|
+
} else {
|
|
7393
|
+
result = await login(environment);
|
|
7394
|
+
}
|
|
7395
|
+
console.log(`Logged in as ${result.name} (${result.email})`);
|
|
7396
|
+
await linkGitHubIdentity(environment);
|
|
7397
|
+
await selectOrg(environment);
|
|
7398
|
+
}
|
|
7399
|
+
async function handleLogin(opts) {
|
|
7400
|
+
if (opts?.serviceToken) {
|
|
7401
|
+
await handleServiceTokenLogin();
|
|
7402
|
+
return;
|
|
7403
|
+
}
|
|
7240
7404
|
try {
|
|
7241
|
-
|
|
7242
|
-
let result;
|
|
7243
|
-
if (useDeviceFlow) {
|
|
7244
|
-
result = await deviceLogin(environment);
|
|
7245
|
-
} else {
|
|
7246
|
-
result = await login(environment);
|
|
7247
|
-
}
|
|
7248
|
-
console.log(`
|
|
7249
|
-
Logged in as ${result.name} (${result.email})`);
|
|
7250
|
-
await linkGitHubIdentity(environment);
|
|
7251
|
-
await selectOrg(environment);
|
|
7252
|
-
console.log("You're all set! Restart Claude Code to use FML tools.");
|
|
7405
|
+
await runInteractiveLogin({ device: opts?.device });
|
|
7253
7406
|
process.exit(0);
|
|
7254
7407
|
} catch (err) {
|
|
7255
7408
|
Sentry.captureException(err);
|
|
@@ -7259,742 +7412,923 @@ Logged in as ${result.name} (${result.email})`);
|
|
|
7259
7412
|
}
|
|
7260
7413
|
}
|
|
7261
7414
|
|
|
7262
|
-
// src/commands/
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7415
|
+
// src/commands/setup.ts
|
|
7416
|
+
var defaultDependencies = {
|
|
7417
|
+
install: () => handleInstall(),
|
|
7418
|
+
getActiveToken: () => getValidToken({ env: getActiveEnv().name }),
|
|
7419
|
+
login: () => runInteractiveLogin(),
|
|
7420
|
+
stop: () => handleFmlStop(),
|
|
7421
|
+
start: () => handleFmlStart()
|
|
7422
|
+
};
|
|
7423
|
+
async function runStep(name, operation) {
|
|
7424
|
+
const priorExitCode = process.exitCode;
|
|
7425
|
+
await operation();
|
|
7426
|
+
if (process.exitCode && process.exitCode !== priorExitCode) {
|
|
7427
|
+
throw new Error(`FML setup could not complete ${name}.`);
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
async function handleSetup(dependencies = defaultDependencies) {
|
|
7431
|
+
if (process.exitCode) {
|
|
7432
|
+
throw new Error("FML setup cannot run after an earlier command failure.");
|
|
7433
|
+
}
|
|
7434
|
+
console.log("Setting up FML...");
|
|
7435
|
+
await runStep("integration installation", dependencies.install);
|
|
7436
|
+
const token = await dependencies.getActiveToken();
|
|
7437
|
+
if (token) {
|
|
7438
|
+
console.log("Already signed in.");
|
|
7439
|
+
} else {
|
|
7440
|
+
await runStep("login", dependencies.login);
|
|
7441
|
+
}
|
|
7442
|
+
await runStep("observer stop", dependencies.stop);
|
|
7443
|
+
await runStep("observer start", dependencies.start);
|
|
7444
|
+
console.log("FML is ready.");
|
|
7445
|
+
console.log("Start a new agent session to load FML tools.");
|
|
7446
|
+
console.log("Run `fml doctor` to verify setup.");
|
|
7267
7447
|
}
|
|
7268
7448
|
|
|
7269
|
-
// src/commands/
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7449
|
+
// src/commands/uninstall.ts
|
|
7450
|
+
import fs13 from "fs";
|
|
7451
|
+
import os7 from "os";
|
|
7452
|
+
import path28 from "path";
|
|
7453
|
+
import readline from "readline/promises";
|
|
7454
|
+
var PURGE_WARNING = "This deletes FML credentials for ALL environments, logs, sync state, and every pending best-effort OTLP envelope.";
|
|
7455
|
+
function tryDisableHookCapture() {
|
|
7456
|
+
try {
|
|
7457
|
+
setHookCaptureDisabled(true);
|
|
7458
|
+
return true;
|
|
7459
|
+
} catch (error) {
|
|
7460
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7461
|
+
console.warn(
|
|
7462
|
+
`Warning: Could not persist the hook-capture pause at ${FML_HOOK_CAPTURE_DISABLED_FILE}: ${message}`
|
|
7463
|
+
);
|
|
7464
|
+
return false;
|
|
7276
7465
|
}
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7466
|
+
}
|
|
7467
|
+
function resolvePreservedPaths(preservedFile) {
|
|
7468
|
+
const logicalPath = path28.resolve(preservedFile);
|
|
7469
|
+
try {
|
|
7470
|
+
const canonicalPath = fs13.realpathSync(preservedFile);
|
|
7471
|
+
return pathsAreSame(canonicalPath, logicalPath) ? [logicalPath] : [logicalPath, canonicalPath];
|
|
7472
|
+
} catch {
|
|
7473
|
+
return [logicalPath];
|
|
7285
7474
|
}
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7475
|
+
}
|
|
7476
|
+
function purgeDirectoryPreservingPaths(directory, preservedPaths, configuredRoot = false) {
|
|
7477
|
+
const resolvedDirectory = path28.resolve(directory);
|
|
7478
|
+
let directoryStats;
|
|
7479
|
+
try {
|
|
7480
|
+
directoryStats = fs13.lstatSync(directory);
|
|
7481
|
+
} catch (error) {
|
|
7482
|
+
if (error.code === "ENOENT") return true;
|
|
7483
|
+
throw error;
|
|
7484
|
+
}
|
|
7485
|
+
if (directoryStats.isSymbolicLink()) {
|
|
7486
|
+
if (configuredRoot) return false;
|
|
7487
|
+
const containsLogicalMarker = preservedPaths.some(
|
|
7488
|
+
(preservedPath) => pathContains(resolvedDirectory, preservedPath)
|
|
7489
|
+
);
|
|
7490
|
+
if (!containsLogicalMarker) {
|
|
7491
|
+
fs13.rmSync(directory, { force: true });
|
|
7492
|
+
return true;
|
|
7294
7493
|
}
|
|
7295
|
-
|
|
7296
|
-
|
|
7494
|
+
return false;
|
|
7495
|
+
}
|
|
7496
|
+
const directoryPaths = [resolvedDirectory];
|
|
7497
|
+
try {
|
|
7498
|
+
const canonicalDirectory = fs13.realpathSync(directory);
|
|
7499
|
+
if (!pathsAreSame(canonicalDirectory, resolvedDirectory)) {
|
|
7500
|
+
directoryPaths.push(canonicalDirectory);
|
|
7297
7501
|
}
|
|
7298
|
-
|
|
7502
|
+
} catch {
|
|
7299
7503
|
}
|
|
7300
|
-
const
|
|
7301
|
-
(
|
|
7504
|
+
const containsPreservedPath = directoryPaths.some(
|
|
7505
|
+
(directoryPath) => preservedPaths.some(
|
|
7506
|
+
(preservedPath) => pathContains(directoryPath, preservedPath)
|
|
7507
|
+
)
|
|
7302
7508
|
);
|
|
7303
|
-
if (!
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
);
|
|
7307
|
-
process.exit(1);
|
|
7509
|
+
if (!containsPreservedPath) {
|
|
7510
|
+
fs13.rmSync(directory, { recursive: true, force: true });
|
|
7511
|
+
return true;
|
|
7308
7512
|
}
|
|
7309
|
-
const
|
|
7310
|
-
|
|
7311
|
-
|
|
7513
|
+
const isPreservedPath = directoryPaths.some(
|
|
7514
|
+
(directoryPath) => preservedPaths.some(
|
|
7515
|
+
(preservedPath) => pathsAreSame(directoryPath, path28.resolve(preservedPath))
|
|
7516
|
+
)
|
|
7517
|
+
);
|
|
7518
|
+
if (isPreservedPath) return true;
|
|
7519
|
+
if (!directoryStats.isDirectory()) return false;
|
|
7520
|
+
let entries;
|
|
7521
|
+
try {
|
|
7522
|
+
entries = fs13.readdirSync(directory);
|
|
7523
|
+
} catch (error) {
|
|
7524
|
+
if (error.code === "ENOENT") return true;
|
|
7525
|
+
throw error;
|
|
7526
|
+
}
|
|
7527
|
+
let complete = true;
|
|
7528
|
+
for (const entry of entries) {
|
|
7529
|
+
const entryPath = path28.join(directory, entry);
|
|
7530
|
+
if (!purgeDirectoryPreservingPaths(entryPath, preservedPaths, false)) {
|
|
7531
|
+
complete = false;
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
return complete;
|
|
7312
7535
|
}
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7536
|
+
function purgeDirectoryPreservingFiles(directory, preservedFiles) {
|
|
7537
|
+
if (hasSymlinkedParentBelowTrustedAnchor(directory)) return false;
|
|
7538
|
+
return purgeDirectoryPreservingPaths(
|
|
7539
|
+
directory,
|
|
7540
|
+
preservedFiles.flatMap(
|
|
7541
|
+
(preservedFile) => resolvePreservedPaths(preservedFile)
|
|
7542
|
+
),
|
|
7543
|
+
true
|
|
7544
|
+
);
|
|
7319
7545
|
}
|
|
7320
|
-
|
|
7321
|
-
const
|
|
7322
|
-
|
|
7323
|
-
if (!api) {
|
|
7324
|
-
console.error("Not authenticated. Run `fml login` first.");
|
|
7325
|
-
process.exit(1);
|
|
7326
|
-
}
|
|
7327
|
-
let slug;
|
|
7546
|
+
function finishCustomRootPurge(directory) {
|
|
7547
|
+
const marker = managedDirectoryMarker(directory);
|
|
7548
|
+
fs13.rmSync(marker, { force: true });
|
|
7328
7549
|
try {
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7550
|
+
fs13.rmdirSync(directory);
|
|
7551
|
+
} catch (error) {
|
|
7552
|
+
if (error.code === "ENOENT") return;
|
|
7553
|
+
try {
|
|
7554
|
+
fs13.writeFileSync(marker, FML_MANAGED_DIRECTORY_MARKER_CONTENT, {
|
|
7555
|
+
encoding: "utf8",
|
|
7556
|
+
flag: "wx",
|
|
7557
|
+
mode: 384
|
|
7558
|
+
});
|
|
7559
|
+
} catch (markerError) {
|
|
7560
|
+
if (markerError.code === "EEXIST" && hasManagedDirectoryMarker(directory)) {
|
|
7561
|
+
throw error;
|
|
7562
|
+
}
|
|
7563
|
+
const removalMessage = error instanceof Error ? error.message : String(error);
|
|
7564
|
+
const markerMessage = markerError instanceof Error ? markerError.message : String(markerError);
|
|
7565
|
+
throw new Error(
|
|
7566
|
+
`could not remove ${directory} (${removalMessage}) or restore its FML ownership marker (${markerMessage})`
|
|
7567
|
+
);
|
|
7333
7568
|
}
|
|
7334
|
-
|
|
7335
|
-
const org = selected && orgs.find((candidate) => {
|
|
7336
|
-
const candidateSlug = candidate.slug ?? candidate.name;
|
|
7337
|
-
return candidateSlug === selected || candidate.name === selected;
|
|
7338
|
-
}) || orgs[0];
|
|
7339
|
-
slug = org.slug ?? org.name;
|
|
7340
|
-
} catch {
|
|
7341
|
-
console.error("Could not fetch organizations. Opening base URL.");
|
|
7342
|
-
slug = "";
|
|
7343
|
-
}
|
|
7344
|
-
const baseUrl = appBaseUrl();
|
|
7345
|
-
const url = slug ? `${baseUrl}/${slug}` : baseUrl;
|
|
7346
|
-
if (opts.json) {
|
|
7347
|
-
console.log(JSON.stringify({ url }));
|
|
7348
|
-
} else {
|
|
7349
|
-
const openBrowser = (await import("open")).default;
|
|
7350
|
-
await openBrowser(url);
|
|
7569
|
+
throw error;
|
|
7351
7570
|
}
|
|
7352
7571
|
}
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
7360
|
-
}
|
|
7361
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
7362
|
-
}
|
|
7363
|
-
function hookSpoolStatus(value) {
|
|
7364
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7365
|
-
return void 0;
|
|
7366
|
-
}
|
|
7367
|
-
const record = value;
|
|
7368
|
-
const nonNegativeInteger = (field) => {
|
|
7369
|
-
const candidate = record[field];
|
|
7370
|
-
return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : void 0;
|
|
7371
|
-
};
|
|
7372
|
-
const logicalBytes = nonNegativeInteger("logicalBytes");
|
|
7373
|
-
const evictedRecords = nonNegativeInteger("evictedRecords");
|
|
7374
|
-
const evictedBytes = nonNegativeInteger("evictedBytes");
|
|
7375
|
-
const evictedPendingRecords = nonNegativeInteger("evictedPendingRecords");
|
|
7376
|
-
const evictedQuarantinedRecords = nonNegativeInteger(
|
|
7377
|
-
"evictedQuarantinedRecords"
|
|
7572
|
+
function cleanInstalledPluginScopes(paths) {
|
|
7573
|
+
const installedPlugins = readJsonFile(paths.installedPluginsPath);
|
|
7574
|
+
if (!installedPlugins) return;
|
|
7575
|
+
const update = CLAUDE_PLUGIN_INSTALLER.removeInstalledPlugin(
|
|
7576
|
+
installedPlugins,
|
|
7577
|
+
CLAUDE_PLUGIN_INSTALLER.pluginId
|
|
7378
7578
|
);
|
|
7379
|
-
|
|
7380
|
-
const
|
|
7381
|
-
|
|
7382
|
-
|
|
7579
|
+
if (!update.changed) return;
|
|
7580
|
+
for (const settingsPath2 of CLAUDE_PLUGIN_INSTALLER.projectSettingsPaths(
|
|
7581
|
+
installedPlugins,
|
|
7582
|
+
CLAUDE_PLUGIN_INSTALLER.pluginId
|
|
7583
|
+
)) {
|
|
7584
|
+
removeEnabledPluginFromSettings(
|
|
7585
|
+
settingsPath2,
|
|
7586
|
+
CLAUDE_PLUGIN_INSTALLER.pluginId
|
|
7587
|
+
);
|
|
7383
7588
|
}
|
|
7384
|
-
|
|
7385
|
-
const lastEvictedAtMs = lastEvictedAtMsValue !== void 0 && lastEvictedAtMsValue <= 864e13 ? lastEvictedAtMsValue : void 0;
|
|
7386
|
-
return {
|
|
7387
|
-
logicalBytes,
|
|
7388
|
-
maxBytes,
|
|
7389
|
-
...quarantineMaxAgeMs === null ? { quarantineMaxAgeMs: null } : typeof quarantineMaxAgeMs === "number" && Number.isSafeInteger(quarantineMaxAgeMs) && quarantineMaxAgeMs > 0 ? { quarantineMaxAgeMs } : {},
|
|
7390
|
-
evictedRecords,
|
|
7391
|
-
evictedBytes,
|
|
7392
|
-
evictedPendingRecords,
|
|
7393
|
-
evictedQuarantinedRecords,
|
|
7394
|
-
...lastEvictedAtMs === void 0 ? {} : { lastEvictedAtMs }
|
|
7395
|
-
};
|
|
7589
|
+
writeJsonFile(paths.installedPluginsPath, update.installedPlugins);
|
|
7396
7590
|
}
|
|
7397
|
-
function
|
|
7398
|
-
|
|
7399
|
-
|
|
7591
|
+
function purgeWarning(roots) {
|
|
7592
|
+
return `${PURGE_WARNING}
|
|
7593
|
+
Purge roots:
|
|
7594
|
+
${roots.map((root) => ` - ${root.directory}`).join("\n")}`;
|
|
7595
|
+
}
|
|
7596
|
+
async function confirmPurge(opts, roots) {
|
|
7597
|
+
const warning = purgeWarning(roots);
|
|
7598
|
+
if (opts.assumeYes) {
|
|
7599
|
+
console.log(warning);
|
|
7600
|
+
return true;
|
|
7400
7601
|
}
|
|
7401
|
-
const
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
const maxBytes = nonNegativeInteger("maxBytes");
|
|
7409
|
-
const droppedRecords = nonNegativeInteger("droppedRecords");
|
|
7410
|
-
const droppedLogicalBytes = nonNegativeInteger("droppedLogicalBytes");
|
|
7411
|
-
const droppedBodyBytes = nonNegativeInteger("droppedBodyBytes");
|
|
7412
|
-
if (record.unit !== "logical_bytes" || logicalBytes === void 0 || bodyBytes === void 0 || maxBytes === void 0 || maxBytes < 1 || droppedRecords === void 0 || droppedLogicalBytes === void 0 || droppedBodyBytes === void 0 || bodyBytes > logicalBytes || droppedBodyBytes > droppedLogicalBytes) {
|
|
7413
|
-
return void 0;
|
|
7602
|
+
const input = opts.input ?? process.stdin;
|
|
7603
|
+
if (input.isTTY !== true) {
|
|
7604
|
+
console.error(
|
|
7605
|
+
`${warning}
|
|
7606
|
+
Re-run with --yes to confirm in a non-interactive session.`
|
|
7607
|
+
);
|
|
7608
|
+
return false;
|
|
7414
7609
|
}
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
if (typeof record.phase !== "string" || typeof record.pending !== "number" || !Number.isSafeInteger(record.pending) || record.pending < 0) {
|
|
7432
|
-
return null;
|
|
7610
|
+
const rl = readline.createInterface({
|
|
7611
|
+
input,
|
|
7612
|
+
output: opts.output ?? process.stdout
|
|
7613
|
+
});
|
|
7614
|
+
try {
|
|
7615
|
+
const closed = new Promise((resolve) => {
|
|
7616
|
+
rl.once("close", () => resolve(null));
|
|
7617
|
+
});
|
|
7618
|
+
const answer = await Promise.race([
|
|
7619
|
+
rl.question(`${warning}
|
|
7620
|
+
Type y to continue: `).catch(() => null),
|
|
7621
|
+
closed
|
|
7622
|
+
]);
|
|
7623
|
+
if (answer !== null && answer.trim().toLowerCase() === "y") return true;
|
|
7624
|
+
} finally {
|
|
7625
|
+
rl.close();
|
|
7433
7626
|
}
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
pending: record.pending,
|
|
7437
|
-
...typeof record.quarantined === "number" && Number.isSafeInteger(record.quarantined) && record.quarantined >= 0 ? { quarantined: record.quarantined } : {},
|
|
7438
|
-
...typeof record.lastError === "string" ? { lastError: record.lastError } : {},
|
|
7439
|
-
...typeof record.lastWarning === "string" ? { lastWarning: record.lastWarning } : {},
|
|
7440
|
-
...typeof record.nextAttemptAtMs === "number" && Number.isSafeInteger(record.nextAttemptAtMs) && record.nextAttemptAtMs >= 0 && Number.isFinite(new Date(record.nextAttemptAtMs).getTime()) ? { nextAttemptAtMs: record.nextAttemptAtMs } : {}
|
|
7441
|
-
};
|
|
7627
|
+
console.error("Uninstall canceled; nothing was removed.");
|
|
7628
|
+
return false;
|
|
7442
7629
|
}
|
|
7443
|
-
function
|
|
7444
|
-
const
|
|
7445
|
-
if (
|
|
7446
|
-
|
|
7630
|
+
function refuseUnsafePurge(roots) {
|
|
7631
|
+
const unsafeRoots = unsafePurgeRoots(roots);
|
|
7632
|
+
if (unsafeRoots.length === 0) return false;
|
|
7633
|
+
console.error("Refusing to purge unsafe FML directories:");
|
|
7634
|
+
for (const root of unsafeRoots) {
|
|
7635
|
+
console.error(` - ${root.directory}: ${root.reason}`);
|
|
7447
7636
|
}
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
return
|
|
7453
|
-
...status,
|
|
7454
|
-
...typeof record.rejected === "number" && Number.isSafeInteger(record.rejected) && record.rejected >= 0 ? { rejected: record.rejected } : {},
|
|
7455
|
-
...typeof record.lastRejectedReason === "string" ? { lastRejectedReason: record.lastRejectedReason } : {},
|
|
7456
|
-
...typeof record.lastRejectedTarget === "string" ? { lastRejectedTarget: record.lastRejectedTarget } : {},
|
|
7457
|
-
...spool === void 0 ? {} : { spool }
|
|
7458
|
-
};
|
|
7637
|
+
console.error(
|
|
7638
|
+
"Inspect and remove those roots manually, or reconfigure FML to fresh dedicated empty data, state, log, and observer directories and run `fml install` before retrying."
|
|
7639
|
+
);
|
|
7640
|
+
process.exitCode = 1;
|
|
7641
|
+
return true;
|
|
7459
7642
|
}
|
|
7460
|
-
function
|
|
7461
|
-
const
|
|
7462
|
-
if (
|
|
7463
|
-
return
|
|
7643
|
+
async function handleUninstall(opts) {
|
|
7644
|
+
const purgeRoots = opts.purge ? configuredManagedRoots() : [];
|
|
7645
|
+
if (opts.purge) {
|
|
7646
|
+
if (refuseUnsafePurge(purgeRoots)) return;
|
|
7647
|
+
if (!await confirmPurge(opts, purgeRoots)) {
|
|
7648
|
+
process.exitCode = 1;
|
|
7649
|
+
return;
|
|
7650
|
+
}
|
|
7651
|
+
for (const root of purgeRoots) {
|
|
7652
|
+
if (!fs13.existsSync(root.directory) && pathContains(root.directory, FML_OBSERVER_DIR)) {
|
|
7653
|
+
prepareManagedDirectory(root);
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
if (refuseUnsafePurge(purgeRoots)) return;
|
|
7464
7657
|
}
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7658
|
+
console.log("Removing FML integrations...\n");
|
|
7659
|
+
const otelDestination = readLocalObserverOtelDestination({
|
|
7660
|
+
endpointFile: FML_OBSERVER_ENDPOINT_FILE,
|
|
7661
|
+
authTokenFile: FML_OBSERVER_AUTH_TOKEN_FILE
|
|
7662
|
+
});
|
|
7663
|
+
let observer;
|
|
7664
|
+
try {
|
|
7665
|
+
observer = await stopObserver();
|
|
7666
|
+
} catch (error) {
|
|
7667
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
7668
|
+
console.error(
|
|
7669
|
+
"Could not safely stop the FML observer; uninstall was aborted. Repair the reported files and rerun `fml uninstall`."
|
|
7670
|
+
);
|
|
7671
|
+
process.exitCode = 1;
|
|
7672
|
+
return;
|
|
7673
|
+
}
|
|
7674
|
+
if (observer.status === "permission_denied" || observer.status === "foreign_listener" || observer.status === "ambiguous_live_process" || observer.status === "unresponsive" || observer.status === "signal_sent") {
|
|
7675
|
+
console.error(
|
|
7676
|
+
`Could not safely stop FML observer PID ${observer.pid}; uninstall was aborted.`
|
|
7677
|
+
);
|
|
7678
|
+
process.exitCode = 1;
|
|
7679
|
+
return;
|
|
7680
|
+
}
|
|
7681
|
+
let hookCapturePaused = tryDisableHookCapture();
|
|
7682
|
+
createRawArtifactSync({ stateDir: FML_OBSERVER_DIR }).setEnabled(false);
|
|
7683
|
+
const paths = CLAUDE_PLUGIN_INSTALLER.resolvePaths({
|
|
7684
|
+
env: process.env,
|
|
7685
|
+
homeDir: os7.homedir()
|
|
7686
|
+
});
|
|
7687
|
+
const claudeBin = resolveBin("claude");
|
|
7688
|
+
if (claudeBin) {
|
|
7689
|
+
try {
|
|
7690
|
+
const command = CLAUDE_PLUGIN_INSTALLER.uninstallCommand;
|
|
7691
|
+
execBinSync(claudeBin, [...command.args], {
|
|
7692
|
+
timeout: command.timeoutMs
|
|
7693
|
+
});
|
|
7694
|
+
} catch {
|
|
7489
7695
|
}
|
|
7490
|
-
|
|
7491
|
-
|
|
7696
|
+
}
|
|
7697
|
+
cleanInstalledPluginScopes(paths);
|
|
7698
|
+
const settings = readJsonFile(paths.settingsPath);
|
|
7699
|
+
if (settings) {
|
|
7700
|
+
const marketplace2 = readJsonFile(paths.marketplaceManifestPath);
|
|
7701
|
+
const keepLocalMarketplace = CLAUDE_PLUGIN_INSTALLER.hasOtherLocalMarketplacePlugins(
|
|
7702
|
+
settings,
|
|
7703
|
+
marketplace2
|
|
7492
7704
|
);
|
|
7493
|
-
|
|
7494
|
-
|
|
7705
|
+
writeJsonFile(
|
|
7706
|
+
paths.settingsPath,
|
|
7707
|
+
CLAUDE_PLUGIN_INSTALLER.disableInSettings(settings, keepLocalMarketplace)
|
|
7495
7708
|
);
|
|
7496
7709
|
}
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
console.log(` Uploaded: ${syncStatus.queue.uploaded}`);
|
|
7507
|
-
console.log(` Failed: ${syncStatus.queue.failed}`);
|
|
7508
|
-
console.log(
|
|
7509
|
-
` Watermark: ${formatBytes(syncStatus.queue.uploadedBytes)} / ${formatBytes(syncStatus.queue.totalBytes)}`
|
|
7510
|
-
);
|
|
7511
|
-
console.log(` Remaining: ${formatBytes(syncStatus.queue.remainingBytes)}`);
|
|
7512
|
-
const observer = await readObserverRuntimeStatus();
|
|
7513
|
-
const protectedStatus = observer.healthy ? await readObserverProtectedStatus() : null;
|
|
7514
|
-
const observerState = observer.stateStatus.state;
|
|
7515
|
-
console.log("\nLocal observer:");
|
|
7516
|
-
console.log(
|
|
7517
|
-
` Status: ${observer.healthy ? "running" : observer.running ? "unhealthy" : "stopped"}`
|
|
7518
|
-
);
|
|
7519
|
-
if (observerState) {
|
|
7520
|
-
console.log(` PID: ${observerState.pid}`);
|
|
7521
|
-
console.log(` Phase: ${observerState.phase}`);
|
|
7522
|
-
if (observerState.lastOutcome) {
|
|
7523
|
-
console.log(` Last: ${observerState.lastOutcome}`);
|
|
7710
|
+
let skillsRemoved = true;
|
|
7711
|
+
try {
|
|
7712
|
+
for (const result of removeManagedAgentSkills(resolvePackageRoot())) {
|
|
7713
|
+
if (result.status !== "preserved" && !result.reason) continue;
|
|
7714
|
+
skillsRemoved = false;
|
|
7715
|
+
const targets = result.displayNames.join(", ");
|
|
7716
|
+
console.warn(
|
|
7717
|
+
`Warning: Preserved ${result.destinationPath} for ${targets}: ${result.reason ?? "cleanup could not be verified"}`
|
|
7718
|
+
);
|
|
7524
7719
|
}
|
|
7720
|
+
} catch (error) {
|
|
7721
|
+
skillsRemoved = false;
|
|
7722
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7723
|
+
console.warn(
|
|
7724
|
+
`Warning: Agent skill cleanup could not be completed: ${message}`
|
|
7725
|
+
);
|
|
7525
7726
|
}
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
if (
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
console.log(
|
|
7542
|
-
` Last rejection: ${hooks.lastRejectedReason}${hooks.lastRejectedTarget ? ` (${hooks.lastRejectedTarget})` : ""}`
|
|
7543
|
-
);
|
|
7544
|
-
}
|
|
7727
|
+
let integrationsRemoved = true;
|
|
7728
|
+
try {
|
|
7729
|
+
const piPaths = PI_EXTENSION_INSTALLER.resolvePaths({
|
|
7730
|
+
env: process.env,
|
|
7731
|
+
homeDir: os7.homedir(),
|
|
7732
|
+
packageRoot: resolvePackageRoot(),
|
|
7733
|
+
platform: process.platform
|
|
7734
|
+
});
|
|
7735
|
+
const piResult = PI_EXTENSION_INSTALLER.uninstall(piPaths);
|
|
7736
|
+
if (piResult.status === "preserved") {
|
|
7737
|
+
integrationsRemoved = false;
|
|
7738
|
+
process.exitCode = 1;
|
|
7739
|
+
console.warn(
|
|
7740
|
+
`Warning: Preserved ${piResult.destinationPath} for Pi: ${piResult.reason}`
|
|
7741
|
+
);
|
|
7545
7742
|
}
|
|
7546
|
-
if (
|
|
7547
|
-
console.log(
|
|
7548
|
-
|
|
7743
|
+
if (piResult.legacyRemoved) {
|
|
7744
|
+
console.log("Removed the legacy Panopticon Pi extension");
|
|
7745
|
+
} else if (piResult.legacyPreserved) {
|
|
7746
|
+
integrationsRemoved = false;
|
|
7747
|
+
process.exitCode = 1;
|
|
7748
|
+
console.warn(
|
|
7749
|
+
`Warning: Preserved unrecognized Pi extension ${piPaths.legacyDestinationPath}; inspect and remove it if it belongs to Panopticon`
|
|
7549
7750
|
);
|
|
7550
|
-
if (hooks.spool.quarantineMaxAgeMs !== void 0) {
|
|
7551
|
-
console.log(
|
|
7552
|
-
` Quarantine retention: ${hooks.spool.quarantineMaxAgeMs === null ? "unbounded" : `${Math.round(hooks.spool.quarantineMaxAgeMs / 864e5)} day(s)`}`
|
|
7553
|
-
);
|
|
7554
|
-
}
|
|
7555
|
-
if (hooks.spool.evictedRecords > 0) {
|
|
7556
|
-
console.log(
|
|
7557
|
-
` Evicted: ${hooks.spool.evictedRecords} record(s), ${formatBytes(hooks.spool.evictedBytes)} (${hooks.spool.evictedQuarantinedRecords} quarantined, ${hooks.spool.evictedPendingRecords} pending)`
|
|
7558
|
-
);
|
|
7559
|
-
if (hooks.spool.lastEvictedAtMs !== void 0) {
|
|
7560
|
-
console.log(
|
|
7561
|
-
` Last eviction: ${new Date(hooks.spool.lastEvictedAtMs).toISOString()}`
|
|
7562
|
-
);
|
|
7563
|
-
}
|
|
7564
|
-
}
|
|
7565
7751
|
}
|
|
7566
|
-
|
|
7752
|
+
} catch (error) {
|
|
7753
|
+
integrationsRemoved = false;
|
|
7754
|
+
process.exitCode = 1;
|
|
7755
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7756
|
+
console.warn(
|
|
7757
|
+
`Warning: Pi extension cleanup could not be completed: ${message}`
|
|
7758
|
+
);
|
|
7567
7759
|
}
|
|
7568
|
-
if (
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
` Inbox: ${hookInbox.pendingEntries} pending (${formatBytes(hookInbox.pendingBytes)})`
|
|
7760
|
+
if (removeMarketplaceLink(paths.marketplaceLinkPath) === "preserved") {
|
|
7761
|
+
integrationsRemoved = false;
|
|
7762
|
+
console.warn(
|
|
7763
|
+
`Warning: Preserved non-symlink marketplace path ${paths.marketplaceLinkPath}`
|
|
7573
7764
|
);
|
|
7574
|
-
|
|
7575
|
-
|
|
7765
|
+
}
|
|
7766
|
+
const marketplace = readJsonFile(paths.marketplaceManifestPath);
|
|
7767
|
+
if (marketplace && Array.isArray(marketplace.plugins)) {
|
|
7768
|
+
writeJsonFile(
|
|
7769
|
+
paths.marketplaceManifestPath,
|
|
7770
|
+
CLAUDE_PLUGIN_INSTALLER.removeMarketplaceEntry(
|
|
7771
|
+
marketplace,
|
|
7772
|
+
CLAUDE_PLUGIN_INSTALLER.pluginName
|
|
7773
|
+
)
|
|
7576
7774
|
);
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
` Inbox retired: ${hookInbox.retiredEntries} record(s), ${formatBytes(hookInbox.retiredBytes)}`
|
|
7775
|
+
}
|
|
7776
|
+
fs13.rmSync(paths.pluginCacheDir, { recursive: true, force: true });
|
|
7777
|
+
const reportIntegrationWarning = (message) => {
|
|
7778
|
+
integrationsRemoved = false;
|
|
7779
|
+
console.warn(message.replace(/^warn:/u, "Warning:"));
|
|
7780
|
+
};
|
|
7781
|
+
let hookRemovalComplete = true;
|
|
7782
|
+
removeFmlHooks({
|
|
7783
|
+
onFailure: (target2) => {
|
|
7784
|
+
hookRemovalComplete = false;
|
|
7785
|
+
reportIntegrationWarning(
|
|
7786
|
+
`Warning: Preserved FML hook registration at ${target2.configPath}: ${target2.reason}`
|
|
7590
7787
|
);
|
|
7591
7788
|
}
|
|
7789
|
+
});
|
|
7790
|
+
if (!hookRemovalComplete) {
|
|
7791
|
+
integrationsRemoved = false;
|
|
7792
|
+
process.exitCode = 1;
|
|
7793
|
+
console.warn(
|
|
7794
|
+
"Warning: Some FML hook registrations were preserved; repair the paths reported above and rerun `fml uninstall`."
|
|
7795
|
+
);
|
|
7592
7796
|
}
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
console.
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7797
|
+
removeFmlOtel(otelDestination, { warn: reportIntegrationWarning });
|
|
7798
|
+
if (!otelDestination) {
|
|
7799
|
+
integrationsRemoved = false;
|
|
7800
|
+
console.warn(
|
|
7801
|
+
"Warning: The local observer endpoint or token is unavailable; only recorded OpenTelemetry profiles could be cleaned"
|
|
7802
|
+
);
|
|
7803
|
+
}
|
|
7804
|
+
removeFmlDirectMcp("all", {
|
|
7805
|
+
log: () => void 0,
|
|
7806
|
+
warn: reportIntegrationWarning
|
|
7807
|
+
});
|
|
7808
|
+
let purgeComplete = true;
|
|
7809
|
+
if (opts.purge) {
|
|
7810
|
+
if (refuseUnsafePurge(purgeRoots)) {
|
|
7811
|
+
purgeComplete = false;
|
|
7812
|
+
} else {
|
|
7813
|
+
for (const root of purgeRoots) {
|
|
7814
|
+
if (refuseUnsafePurge([root])) {
|
|
7815
|
+
purgeComplete = false;
|
|
7816
|
+
continue;
|
|
7817
|
+
}
|
|
7818
|
+
const containsObserverDirectory = pathContains(
|
|
7819
|
+
root.directory,
|
|
7820
|
+
FML_OBSERVER_DIR
|
|
7613
7821
|
);
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7822
|
+
const customManagedRoot = !root.nativeDefault;
|
|
7823
|
+
try {
|
|
7824
|
+
const purged = purgeDirectoryPreservingFiles(root.directory, [
|
|
7825
|
+
...customManagedRoot ? [managedDirectoryMarker(root.directory)] : [],
|
|
7826
|
+
...containsObserverDirectory && hookCapturePaused ? [FML_HOOK_CAPTURE_DISABLED_FILE] : []
|
|
7827
|
+
]);
|
|
7828
|
+
if (!purged) {
|
|
7829
|
+
purgeComplete = false;
|
|
7830
|
+
console.warn(
|
|
7831
|
+
`Warning: Preserved ${root.directory}: a symbolic link prevents a complete safe purge`
|
|
7832
|
+
);
|
|
7833
|
+
continue;
|
|
7834
|
+
}
|
|
7835
|
+
if (customManagedRoot && !containsObserverDirectory) {
|
|
7836
|
+
finishCustomRootPurge(root.directory);
|
|
7837
|
+
}
|
|
7838
|
+
} catch (error) {
|
|
7839
|
+
purgeComplete = false;
|
|
7840
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7841
|
+
console.warn(
|
|
7842
|
+
`Warning: Preserved ${root.directory}: the safe purge was incomplete: ${message}`
|
|
7617
7843
|
);
|
|
7618
7844
|
}
|
|
7619
7845
|
}
|
|
7620
7846
|
}
|
|
7621
|
-
if (
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7847
|
+
if (!hookCapturePaused) hookCapturePaused = tryDisableHookCapture();
|
|
7848
|
+
}
|
|
7849
|
+
if (!hookCapturePaused) {
|
|
7850
|
+
process.exitCode = 1;
|
|
7851
|
+
integrationsRemoved = false;
|
|
7852
|
+
console.warn(
|
|
7853
|
+
"Warning: FML cleanup is incomplete; cached agent processes may keep invoking hooks until they exit."
|
|
7854
|
+
);
|
|
7855
|
+
}
|
|
7856
|
+
if (opts.purge && !purgeComplete) {
|
|
7857
|
+
process.exitCode = 1;
|
|
7858
|
+
console.warn(
|
|
7859
|
+
"Warning: Some local data remains because a purge target could not be removed safely. Repair the reported path and rerun `fml uninstall --purge`."
|
|
7860
|
+
);
|
|
7861
|
+
}
|
|
7862
|
+
if (skillsRemoved) {
|
|
7863
|
+
console.log("\u2713 Agent skills removed");
|
|
7864
|
+
} else {
|
|
7865
|
+
console.warn(
|
|
7866
|
+
"Warning: Agent skills were not fully removed. See warnings above."
|
|
7867
|
+
);
|
|
7868
|
+
}
|
|
7869
|
+
if (integrationsRemoved) {
|
|
7870
|
+
console.log("\u2713 Coding-agent integrations removed");
|
|
7871
|
+
} else {
|
|
7872
|
+
console.warn(
|
|
7873
|
+
"Warning: Coding-agent integrations were not fully removed. See warnings above."
|
|
7874
|
+
);
|
|
7875
|
+
}
|
|
7876
|
+
if (!opts.purge) {
|
|
7877
|
+
console.log("\u2713 Local data preserved");
|
|
7878
|
+
} else if (purgeComplete && hookCapturePaused) {
|
|
7879
|
+
console.log("\u2713 Local data deleted");
|
|
7880
|
+
} else {
|
|
7881
|
+
console.warn(
|
|
7882
|
+
"Warning: Local data was not fully deleted. See warnings above."
|
|
7883
|
+
);
|
|
7884
|
+
}
|
|
7885
|
+
const removalComplete = skillsRemoved && integrationsRemoved && (!opts.purge || purgeComplete && hookCapturePaused);
|
|
7886
|
+
if (removalComplete) {
|
|
7887
|
+
console.log("\nFML integrations removed.");
|
|
7888
|
+
} else {
|
|
7889
|
+
console.warn(
|
|
7890
|
+
"FML integration removal is incomplete; review the warnings above."
|
|
7891
|
+
);
|
|
7628
7892
|
}
|
|
7629
7893
|
}
|
|
7630
7894
|
|
|
7631
|
-
// src/commands/
|
|
7632
|
-
|
|
7633
|
-
function
|
|
7634
|
-
return
|
|
7635
|
-
}
|
|
7636
|
-
function
|
|
7637
|
-
|
|
7638
|
-
if (
|
|
7639
|
-
|
|
7640
|
-
|
|
7895
|
+
// src/commands/update.ts
|
|
7896
|
+
var SEMVER_PATTERN = /^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
7897
|
+
function getCurrentVersion() {
|
|
7898
|
+
return true ? "0.8.0-rc.3+246af775" : "unknown";
|
|
7899
|
+
}
|
|
7900
|
+
function resolveUpdateChannel(version) {
|
|
7901
|
+
const match = SEMVER_PATTERN.exec(version);
|
|
7902
|
+
if (!match) return "latest";
|
|
7903
|
+
const prerelease = match[1];
|
|
7904
|
+
if (!prerelease) return "latest";
|
|
7905
|
+
return prerelease.split(".").every((identifier) => {
|
|
7906
|
+
return !(identifier.length > 1 && identifier.startsWith("0") && /^[0-9]+$/u.test(identifier));
|
|
7907
|
+
}) ? "next" : "latest";
|
|
7908
|
+
}
|
|
7909
|
+
function resolveUpdateTarget(version) {
|
|
7910
|
+
return `@fml-inc/fml@${resolveUpdateChannel(version)}`;
|
|
7911
|
+
}
|
|
7912
|
+
function createUpdateEnvironment(env) {
|
|
7913
|
+
const updateEnv = { ...env };
|
|
7914
|
+
for (const key of Object.keys(updateEnv)) {
|
|
7915
|
+
if (key.toLowerCase() === "npm_config_registry") {
|
|
7916
|
+
delete updateEnv[key];
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
return updateEnv;
|
|
7920
|
+
}
|
|
7921
|
+
function formatUpdateError(err) {
|
|
7922
|
+
if (!(err instanceof Error)) return String(err);
|
|
7923
|
+
const stderr = err.stderr;
|
|
7924
|
+
if (typeof stderr === "string") return stderr;
|
|
7925
|
+
if (Buffer.isBuffer(stderr)) return stderr.toString();
|
|
7926
|
+
return err.message;
|
|
7927
|
+
}
|
|
7928
|
+
async function handleUpdate(overrides = {}) {
|
|
7929
|
+
const dependencies = {
|
|
7930
|
+
currentVersion: getCurrentVersion(),
|
|
7931
|
+
env: process.env,
|
|
7932
|
+
resolveBin,
|
|
7933
|
+
execBinSync,
|
|
7934
|
+
captureException: Sentry.captureException,
|
|
7935
|
+
log: console.log,
|
|
7936
|
+
error: console.error,
|
|
7937
|
+
exit: process.exit,
|
|
7938
|
+
...overrides
|
|
7939
|
+
};
|
|
7940
|
+
const channel = resolveUpdateChannel(dependencies.currentVersion);
|
|
7941
|
+
const target2 = resolveUpdateTarget(dependencies.currentVersion);
|
|
7942
|
+
dependencies.log(`Current version: ${dependencies.currentVersion}`);
|
|
7943
|
+
dependencies.log(`Updating FML from the ${channel} channel...
|
|
7944
|
+
`);
|
|
7945
|
+
try {
|
|
7946
|
+
const npm = dependencies.resolveBin("npm");
|
|
7947
|
+
if (!npm) throw new Error("npm was not found on PATH");
|
|
7948
|
+
dependencies.execBinSync(npm, ["install", "--global", target2], {
|
|
7949
|
+
timeout: 12e4,
|
|
7950
|
+
env: createUpdateEnvironment(dependencies.env)
|
|
7951
|
+
});
|
|
7952
|
+
dependencies.log(`
|
|
7953
|
+
FML update complete (${channel} channel).`);
|
|
7954
|
+
dependencies.log("Run `fml setup` to refresh integrations.");
|
|
7955
|
+
} catch (err) {
|
|
7956
|
+
dependencies.captureException(err);
|
|
7957
|
+
dependencies.error("Update failed:", formatUpdateError(err));
|
|
7958
|
+
dependencies.exit(1);
|
|
7641
7959
|
}
|
|
7642
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
7643
|
-
}
|
|
7644
|
-
function formatDuration(durationMs) {
|
|
7645
|
-
if (durationMs < 1e3) return `${Math.round(durationMs)} ms`;
|
|
7646
|
-
return `${(durationMs / 1e3).toFixed(2)} s`;
|
|
7647
7960
|
}
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
);
|
|
7961
|
+
|
|
7962
|
+
// src/commands/logout.ts
|
|
7963
|
+
async function handleLogout() {
|
|
7964
|
+
const { name } = getActiveEnv();
|
|
7965
|
+
await clearStoredCredentials(name);
|
|
7653
7966
|
console.log(
|
|
7654
|
-
`
|
|
7967
|
+
`Logged out. Stored credentials for the ${name} environment were cleared.`
|
|
7655
7968
|
);
|
|
7656
7969
|
}
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
);
|
|
7665
|
-
return;
|
|
7970
|
+
|
|
7971
|
+
// src/commands/org.ts
|
|
7972
|
+
async function handleOrg(slug) {
|
|
7973
|
+
const environment = getActiveEnv();
|
|
7974
|
+
const token = await getValidToken({ env: environment.name });
|
|
7975
|
+
if (!token) {
|
|
7976
|
+
console.error("Not logged in. Run `fml login` first.");
|
|
7977
|
+
process.exit(1);
|
|
7666
7978
|
}
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
}
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
);
|
|
7676
|
-
console.log(` State: ${FML_OBSERVER_DIR}`);
|
|
7677
|
-
console.log(` Tracked: ${queue.tracked}`);
|
|
7678
|
-
console.log(` Pending: ${queue.pending}`);
|
|
7679
|
-
console.log(` Raw work: ${queue.rawPending}`);
|
|
7680
|
-
console.log(` Metadata: ${queue.metadataPending}`);
|
|
7681
|
-
console.log(` Uploaded: ${queue.uploaded}`);
|
|
7682
|
-
console.log(` Failed: ${queue.failed}`);
|
|
7683
|
-
console.log(
|
|
7684
|
-
` Watermark: ${formatBytes2(queue.uploadedBytes)} / ${formatBytes2(queue.totalBytes)}`
|
|
7685
|
-
);
|
|
7686
|
-
console.log(` Remaining: ${formatBytes2(queue.remainingBytes)}`);
|
|
7687
|
-
}
|
|
7688
|
-
function syncExitCode(result) {
|
|
7689
|
-
if (result.outcome === "failed" || result.outcome === "exhausted") return 1;
|
|
7690
|
-
if (result.outcome === "busy") return 1;
|
|
7691
|
-
return 0;
|
|
7692
|
-
}
|
|
7693
|
-
async function handleSyncRun(opts = {}) {
|
|
7694
|
-
const sync2 = rawSync();
|
|
7695
|
-
if (!sync2.getSettings().enabled) {
|
|
7696
|
-
const result2 = {
|
|
7697
|
-
ok: true,
|
|
7698
|
-
outcome: "disabled",
|
|
7699
|
-
status: sync2.status()
|
|
7700
|
-
};
|
|
7701
|
-
if (opts.json) writeJson(result2);
|
|
7702
|
-
else printStatus(result2.status);
|
|
7703
|
-
return;
|
|
7979
|
+
const api = createFmlClient(token, {
|
|
7980
|
+
envName: environment.name,
|
|
7981
|
+
siteUrl: getSiteUrlForConvexUrl(environment.convexUrl)
|
|
7982
|
+
});
|
|
7983
|
+
const orgs = await api.queryOrgs();
|
|
7984
|
+
if (orgs.length === 0) {
|
|
7985
|
+
console.error("No organizations found.");
|
|
7986
|
+
process.exit(1);
|
|
7704
7987
|
}
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
for (const
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
} catch {
|
|
7714
|
-
}
|
|
7988
|
+
const current = getSelectedOrg(environment.name);
|
|
7989
|
+
const currentId = getSelectedOrgId(environment.name);
|
|
7990
|
+
if (!slug) {
|
|
7991
|
+
console.log("Organizations:\n");
|
|
7992
|
+
for (const org of orgs) {
|
|
7993
|
+
const orgSlug = org.slug ?? org.name;
|
|
7994
|
+
const marker = org._id === currentId || !currentId && orgSlug === current ? " (selected)" : "";
|
|
7995
|
+
console.log(` ${orgSlug}${marker}`);
|
|
7715
7996
|
}
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
dryRun: true,
|
|
7719
|
-
targetUrl: getSiteUrl(),
|
|
7720
|
-
discovered: {
|
|
7721
|
-
count: discovery.candidates.length,
|
|
7722
|
-
totalBytes,
|
|
7723
|
-
bySource
|
|
7724
|
-
},
|
|
7725
|
-
repositoryResolution: discovery.repositoryResolution,
|
|
7726
|
-
oversized: discovery.oversized,
|
|
7727
|
-
status: sync2.status()
|
|
7728
|
-
};
|
|
7729
|
-
if (opts.json) writeJson(result2);
|
|
7730
|
-
else {
|
|
7731
|
-
console.log("FML raw artifact sync dry-run");
|
|
7732
|
-
console.log(` Target: ${result2.targetUrl}`);
|
|
7733
|
-
console.log(` Discovered: ${result2.discovered.count}`);
|
|
7734
|
-
console.log(` Bytes: ${formatBytes2(result2.discovered.totalBytes)}`);
|
|
7735
|
-
for (const [source, count] of Object.entries(bySource).sort()) {
|
|
7736
|
-
console.log(` ${source}: ${count}`);
|
|
7737
|
-
}
|
|
7738
|
-
printRepositoryResolution(discovery.repositoryResolution);
|
|
7739
|
-
console.log(` Oversized: ${discovery.oversized.length}`);
|
|
7740
|
-
if (discovery.compressedArtifactsUnsupportedReason) {
|
|
7741
|
-
console.log(
|
|
7742
|
-
` Compressed: skipped \u2014 ${discovery.compressedArtifactsUnsupportedReason}`
|
|
7743
|
-
);
|
|
7744
|
-
}
|
|
7997
|
+
if (!currentId && !current) {
|
|
7998
|
+
console.log("\nNo org selected. Run `fml org <slug>` to select one.");
|
|
7745
7999
|
}
|
|
7746
8000
|
return;
|
|
7747
8001
|
}
|
|
7748
|
-
const
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
8002
|
+
const match = orgs.find(
|
|
8003
|
+
(o) => (o.slug ?? o.name) === slug || o.name === slug
|
|
8004
|
+
);
|
|
8005
|
+
if (!match) {
|
|
8006
|
+
console.error(
|
|
8007
|
+
`Organization "${slug}" not found. Available: ${orgs.map((o) => o.slug ?? o.name).join(", ")}`
|
|
8008
|
+
);
|
|
8009
|
+
process.exit(1);
|
|
7754
8010
|
}
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
8011
|
+
const matchSlug = match.slug ?? match.name;
|
|
8012
|
+
await setSelectedOrg(matchSlug, match._id, environment.name);
|
|
8013
|
+
console.log(`Selected org: ${match.name} (${matchSlug})`);
|
|
8014
|
+
}
|
|
8015
|
+
|
|
8016
|
+
// src/commands/open.ts
|
|
8017
|
+
var DEFAULT_APP_BASE_URL = "https://app.fml.inc";
|
|
8018
|
+
function appBaseUrl() {
|
|
8019
|
+
const configured = process.env.FML_APP_URL?.trim();
|
|
8020
|
+
return (configured || DEFAULT_APP_BASE_URL).replace(/\/+$/, "");
|
|
8021
|
+
}
|
|
8022
|
+
async function handleOpen(opts) {
|
|
8023
|
+
const environment = getActiveEnv();
|
|
8024
|
+
const api = await getAuthenticatedClient(environment);
|
|
8025
|
+
if (!api) {
|
|
8026
|
+
console.error("Not signed in. Run `fml login` first.");
|
|
8027
|
+
process.exit(1);
|
|
8028
|
+
}
|
|
8029
|
+
let slug;
|
|
8030
|
+
try {
|
|
8031
|
+
const orgs = await api.queryOrgs();
|
|
8032
|
+
if (orgs.length === 0) {
|
|
8033
|
+
console.error("No organizations are available for this account.");
|
|
8034
|
+
process.exit(1);
|
|
7760
8035
|
}
|
|
7761
|
-
|
|
8036
|
+
const selected = getSelectedOrg(environment.name);
|
|
8037
|
+
const org = selected && orgs.find((candidate) => {
|
|
8038
|
+
const candidateSlug = candidate.slug ?? candidate.name;
|
|
8039
|
+
return candidateSlug === selected || candidate.name === selected;
|
|
8040
|
+
}) || orgs[0];
|
|
8041
|
+
slug = org.slug ?? org.name;
|
|
8042
|
+
} catch {
|
|
8043
|
+
console.error(
|
|
8044
|
+
"Could not load your organizations. Opening the FML dashboard."
|
|
8045
|
+
);
|
|
8046
|
+
slug = "";
|
|
7762
8047
|
}
|
|
7763
|
-
const
|
|
8048
|
+
const baseUrl = appBaseUrl();
|
|
8049
|
+
const url = slug ? `${baseUrl}/${slug}` : baseUrl;
|
|
7764
8050
|
if (opts.json) {
|
|
7765
|
-
|
|
8051
|
+
console.log(JSON.stringify({ url }));
|
|
7766
8052
|
} else {
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
8053
|
+
const openBrowser = (await import("open")).default;
|
|
8054
|
+
await openBrowser(url);
|
|
8055
|
+
}
|
|
8056
|
+
}
|
|
8057
|
+
|
|
8058
|
+
// src/commands/status.ts
|
|
8059
|
+
function formatBytes2(bytes) {
|
|
8060
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
8061
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
8062
|
+
if (bytes < 1024 * 1024 * 1024) {
|
|
8063
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
8064
|
+
}
|
|
8065
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
8066
|
+
}
|
|
8067
|
+
function hookSpoolStatus(value) {
|
|
8068
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8069
|
+
return void 0;
|
|
8070
|
+
}
|
|
8071
|
+
const record = value;
|
|
8072
|
+
const nonNegativeInteger = (field) => {
|
|
8073
|
+
const candidate = record[field];
|
|
8074
|
+
return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : void 0;
|
|
8075
|
+
};
|
|
8076
|
+
const logicalBytes = nonNegativeInteger("logicalBytes");
|
|
8077
|
+
const evictedRecords = nonNegativeInteger("evictedRecords");
|
|
8078
|
+
const evictedBytes = nonNegativeInteger("evictedBytes");
|
|
8079
|
+
const evictedPendingRecords = nonNegativeInteger("evictedPendingRecords");
|
|
8080
|
+
const evictedQuarantinedRecords = nonNegativeInteger(
|
|
8081
|
+
"evictedQuarantinedRecords"
|
|
8082
|
+
);
|
|
8083
|
+
const maxBytes = record.maxBytes;
|
|
8084
|
+
const quarantineMaxAgeMs = record.quarantineMaxAgeMs;
|
|
8085
|
+
if (record.unit !== "logical_bytes" || logicalBytes === void 0 || evictedRecords === void 0 || evictedBytes === void 0 || evictedPendingRecords === void 0 || evictedQuarantinedRecords === void 0 || maxBytes !== null && (typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1)) {
|
|
8086
|
+
return void 0;
|
|
8087
|
+
}
|
|
8088
|
+
const lastEvictedAtMsValue = nonNegativeInteger("lastEvictedAtMs");
|
|
8089
|
+
const lastEvictedAtMs = lastEvictedAtMsValue !== void 0 && lastEvictedAtMsValue <= 864e13 ? lastEvictedAtMsValue : void 0;
|
|
8090
|
+
return {
|
|
8091
|
+
logicalBytes,
|
|
8092
|
+
maxBytes,
|
|
8093
|
+
...quarantineMaxAgeMs === null ? { quarantineMaxAgeMs: null } : typeof quarantineMaxAgeMs === "number" && Number.isSafeInteger(quarantineMaxAgeMs) && quarantineMaxAgeMs > 0 ? { quarantineMaxAgeMs } : {},
|
|
8094
|
+
evictedRecords,
|
|
8095
|
+
evictedBytes,
|
|
8096
|
+
evictedPendingRecords,
|
|
8097
|
+
evictedQuarantinedRecords,
|
|
8098
|
+
...lastEvictedAtMs === void 0 ? {} : { lastEvictedAtMs }
|
|
8099
|
+
};
|
|
8100
|
+
}
|
|
8101
|
+
function otlpSpoolStatus(value) {
|
|
8102
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8103
|
+
return void 0;
|
|
8104
|
+
}
|
|
8105
|
+
const record = value;
|
|
8106
|
+
const nonNegativeInteger = (field) => {
|
|
8107
|
+
const candidate = record[field];
|
|
8108
|
+
return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : void 0;
|
|
8109
|
+
};
|
|
8110
|
+
const logicalBytes = nonNegativeInteger("logicalBytes");
|
|
8111
|
+
const bodyBytes = nonNegativeInteger("bodyBytes");
|
|
8112
|
+
const maxBytes = nonNegativeInteger("maxBytes");
|
|
8113
|
+
const droppedRecords = nonNegativeInteger("droppedRecords");
|
|
8114
|
+
const droppedLogicalBytes = nonNegativeInteger("droppedLogicalBytes");
|
|
8115
|
+
const droppedBodyBytes = nonNegativeInteger("droppedBodyBytes");
|
|
8116
|
+
if (record.unit !== "logical_bytes" || logicalBytes === void 0 || bodyBytes === void 0 || maxBytes === void 0 || maxBytes < 1 || droppedRecords === void 0 || droppedLogicalBytes === void 0 || droppedBodyBytes === void 0 || bodyBytes > logicalBytes || droppedBodyBytes > droppedLogicalBytes) {
|
|
8117
|
+
return void 0;
|
|
8118
|
+
}
|
|
8119
|
+
return {
|
|
8120
|
+
logicalBytes,
|
|
8121
|
+
bodyBytes,
|
|
8122
|
+
maxBytes,
|
|
8123
|
+
droppedRecords,
|
|
8124
|
+
droppedLogicalBytes,
|
|
8125
|
+
droppedBodyBytes,
|
|
8126
|
+
...typeof record.lastDroppedAtMs === "number" && Number.isSafeInteger(record.lastDroppedAtMs) && record.lastDroppedAtMs >= 0 && Number.isFinite(new Date(record.lastDroppedAtMs).getTime()) ? { lastDroppedAtMs: record.lastDroppedAtMs } : {},
|
|
8127
|
+
...typeof record.lastDropReason === "string" ? { lastDropReason: record.lastDropReason } : {}
|
|
8128
|
+
};
|
|
8129
|
+
}
|
|
8130
|
+
function observerQueueStatus(value, key) {
|
|
8131
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
8132
|
+
const queue = value[key];
|
|
8133
|
+
if (!queue || typeof queue !== "object" || Array.isArray(queue)) return null;
|
|
8134
|
+
const record = queue;
|
|
8135
|
+
if (typeof record.phase !== "string" || typeof record.pending !== "number" || !Number.isSafeInteger(record.pending) || record.pending < 0) {
|
|
8136
|
+
return null;
|
|
8137
|
+
}
|
|
8138
|
+
return {
|
|
8139
|
+
phase: record.phase,
|
|
8140
|
+
pending: record.pending,
|
|
8141
|
+
...typeof record.quarantined === "number" && Number.isSafeInteger(record.quarantined) && record.quarantined >= 0 ? { quarantined: record.quarantined } : {},
|
|
8142
|
+
...typeof record.lastError === "string" ? { lastError: record.lastError } : {},
|
|
8143
|
+
...typeof record.lastWarning === "string" ? { lastWarning: record.lastWarning } : {},
|
|
8144
|
+
...typeof record.nextAttemptAtMs === "number" && Number.isSafeInteger(record.nextAttemptAtMs) && record.nextAttemptAtMs >= 0 && Number.isFinite(new Date(record.nextAttemptAtMs).getTime()) ? { nextAttemptAtMs: record.nextAttemptAtMs } : {}
|
|
8145
|
+
};
|
|
8146
|
+
}
|
|
8147
|
+
function hookStatus(value) {
|
|
8148
|
+
const status = observerQueueStatus(value, "hooks");
|
|
8149
|
+
if (!status || !value || typeof value !== "object" || Array.isArray(value)) {
|
|
8150
|
+
return null;
|
|
8151
|
+
}
|
|
8152
|
+
const hooks = value.hooks;
|
|
8153
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return null;
|
|
8154
|
+
const record = hooks;
|
|
8155
|
+
const spool = hookSpoolStatus(record.spool);
|
|
8156
|
+
return {
|
|
8157
|
+
...status,
|
|
8158
|
+
...typeof record.rejected === "number" && Number.isSafeInteger(record.rejected) && record.rejected >= 0 ? { rejected: record.rejected } : {},
|
|
8159
|
+
...typeof record.lastRejectedReason === "string" ? { lastRejectedReason: record.lastRejectedReason } : {},
|
|
8160
|
+
...typeof record.lastRejectedTarget === "string" ? { lastRejectedTarget: record.lastRejectedTarget } : {},
|
|
8161
|
+
...spool === void 0 ? {} : { spool }
|
|
8162
|
+
};
|
|
8163
|
+
}
|
|
8164
|
+
function otlpStatus(value) {
|
|
8165
|
+
const status = observerQueueStatus(value, "otlp");
|
|
8166
|
+
if (!status || !value || typeof value !== "object" || Array.isArray(value)) {
|
|
8167
|
+
return null;
|
|
8168
|
+
}
|
|
8169
|
+
const otlp = value.otlp;
|
|
8170
|
+
if (!otlp || typeof otlp !== "object" || Array.isArray(otlp)) return null;
|
|
8171
|
+
const spool = otlpSpoolStatus(otlp.spool);
|
|
8172
|
+
return {
|
|
8173
|
+
...status,
|
|
8174
|
+
...spool === void 0 ? {} : { spool }
|
|
8175
|
+
};
|
|
8176
|
+
}
|
|
8177
|
+
async function handleStatus() {
|
|
8178
|
+
const { name, convexUrl } = getActiveEnv();
|
|
8179
|
+
console.log(`Environment: ${name}`);
|
|
8180
|
+
console.log(` Convex: ${convexUrl ?? "not configured"}
|
|
8181
|
+
`);
|
|
8182
|
+
const tokens = readTokens(name);
|
|
8183
|
+
if (!tokens) {
|
|
8184
|
+
console.log("Authentication:");
|
|
8185
|
+
console.log(" Not authenticated. Run `fml login` to sign in.");
|
|
8186
|
+
} else {
|
|
8187
|
+
const tokenValid = await getValidToken({ env: name });
|
|
8188
|
+
console.log("Authentication:");
|
|
8189
|
+
if (tokens.tokenType === "service" && tokens.user.id === SERVICE_TOKEN_LOGIN_USER_ID) {
|
|
8190
|
+
console.log(" Auth: service token");
|
|
8191
|
+
} else {
|
|
8192
|
+
console.log(` User: ${tokens.user.name} (${tokens.user.email})`);
|
|
7776
8193
|
}
|
|
7777
|
-
printRepositoryResolution(result.discovery.repositoryResolution);
|
|
7778
|
-
console.log(` Uploaded files: ${result.upload.uploaded}`);
|
|
7779
|
-
console.log(` Uploaded chunks: ${result.upload.uploadedChunks}`);
|
|
7780
8194
|
console.log(
|
|
7781
|
-
`
|
|
8195
|
+
` Token: ${tokenValid ? "valid" : "expired (needs refresh/re-login)"}`
|
|
7782
8196
|
);
|
|
7783
|
-
console.log(
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
8197
|
+
console.log(
|
|
8198
|
+
` Org: ${tokens.orgSlug ?? "(none \u2014 run `fml org <slug>`)"}`
|
|
8199
|
+
);
|
|
8200
|
+
}
|
|
8201
|
+
const syncStatus = createRawArtifactSync({
|
|
8202
|
+
stateDir: FML_OBSERVER_DIR
|
|
8203
|
+
}).status();
|
|
8204
|
+
console.log("\nRaw artifact sync:");
|
|
8205
|
+
console.log(` Enabled: ${syncStatus.settings.enabled ? "yes" : "no"}`);
|
|
8206
|
+
console.log(` Tracked: ${syncStatus.queue.tracked}`);
|
|
8207
|
+
console.log(` Pending: ${syncStatus.queue.pending}`);
|
|
8208
|
+
console.log(` Raw work: ${syncStatus.queue.rawPending}`);
|
|
8209
|
+
console.log(` Metadata: ${syncStatus.queue.metadataPending}`);
|
|
8210
|
+
console.log(` Uploaded: ${syncStatus.queue.uploaded}`);
|
|
8211
|
+
console.log(` Failed: ${syncStatus.queue.failed}`);
|
|
8212
|
+
console.log(
|
|
8213
|
+
` Watermark: ${formatBytes2(syncStatus.queue.uploadedBytes)} / ${formatBytes2(syncStatus.queue.totalBytes)}`
|
|
8214
|
+
);
|
|
8215
|
+
console.log(` Remaining: ${formatBytes2(syncStatus.queue.remainingBytes)}`);
|
|
8216
|
+
const observer = await readObserverRuntimeStatus();
|
|
8217
|
+
const protectedStatus = observer.healthy ? await readObserverProtectedStatus() : null;
|
|
8218
|
+
const observerState = observer.stateStatus.state;
|
|
8219
|
+
console.log("\nLocal observer:");
|
|
8220
|
+
console.log(
|
|
8221
|
+
` Status: ${observer.healthy ? "running" : observer.running ? "unhealthy" : "stopped"}`
|
|
8222
|
+
);
|
|
8223
|
+
if (observerState) {
|
|
8224
|
+
console.log(` PID: ${observerState.pid}`);
|
|
8225
|
+
console.log(` Phase: ${observerState.phase}`);
|
|
8226
|
+
if (observerState.lastOutcome) {
|
|
8227
|
+
console.log(` Last: ${observerState.lastOutcome}`);
|
|
7800
8228
|
}
|
|
7801
|
-
console.log("");
|
|
7802
|
-
printStatus(result.status);
|
|
7803
8229
|
}
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
try {
|
|
7816
|
-
const observer = await startObserver({
|
|
7817
|
-
onLegacyDatabaseRetained: (retained) => {
|
|
7818
|
-
retainedLegacyDatabase = retained;
|
|
7819
|
-
if (!opts.json) warnRetainedLegacyDatabase(retained);
|
|
7820
|
-
}
|
|
7821
|
-
});
|
|
7822
|
-
setHookCaptureDisabled(false);
|
|
7823
|
-
if (observer.status === "already_running") {
|
|
7824
|
-
await wakeObserverSync();
|
|
8230
|
+
console.log(` Log: ${observer.logFile}`);
|
|
8231
|
+
const hooks = hookStatus(protectedStatus);
|
|
8232
|
+
const hookInbox = readHookInboxStatus();
|
|
8233
|
+
console.log("\nHook capture:");
|
|
8234
|
+
if (!hooks) {
|
|
8235
|
+
console.log(` Status: ${observer.healthy ? "unavailable" : "stopped"}`);
|
|
8236
|
+
} else {
|
|
8237
|
+
console.log(` Status: ${hooks.phase}`);
|
|
8238
|
+
console.log(` Pending: ${hooks.pending}`);
|
|
8239
|
+
if (hooks.quarantined && hooks.quarantined > 0) {
|
|
8240
|
+
console.log(` Quarantined: ${hooks.quarantined}`);
|
|
7825
8241
|
}
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
})) {
|
|
7833
|
-
if (!opts.json) {
|
|
7834
|
-
console.log(`OpenTelemetry: ${target2.displayName} -> FML observer`);
|
|
7835
|
-
}
|
|
8242
|
+
if (hooks.rejected && hooks.rejected > 0) {
|
|
8243
|
+
console.log(` Rejected: ${hooks.rejected}`);
|
|
8244
|
+
if (hooks.lastRejectedReason) {
|
|
8245
|
+
console.log(
|
|
8246
|
+
` Last rejection: ${hooks.lastRejectedReason}${hooks.lastRejectedTarget ? ` (${hooks.lastRejectedTarget})` : ""}`
|
|
8247
|
+
);
|
|
7836
8248
|
}
|
|
7837
|
-
} else if (!opts.json) {
|
|
7838
|
-
console.warn(
|
|
7839
|
-
"Could not configure native OpenTelemetry: the healthy observer endpoint or local token is unavailable."
|
|
7840
|
-
);
|
|
7841
8249
|
}
|
|
7842
|
-
if (
|
|
7843
|
-
writeJson({
|
|
7844
|
-
ok: true,
|
|
7845
|
-
sync: settings,
|
|
7846
|
-
observer,
|
|
7847
|
-
...observer.retainedLegacyDatabase ? { retainedLegacyDatabase: observer.retainedLegacyDatabase } : {}
|
|
7848
|
-
});
|
|
7849
|
-
} else {
|
|
7850
|
-
console.log("FML raw artifact sync is enabled");
|
|
7851
|
-
const pid = observer.runtime.health?.pid;
|
|
8250
|
+
if (hooks.spool) {
|
|
7852
8251
|
console.log(
|
|
7853
|
-
|
|
8252
|
+
` Spool: ${formatBytes2(hooks.spool.logicalBytes)} / ${hooks.spool.maxBytes === null ? "unbounded" : formatBytes2(hooks.spool.maxBytes)} (logical content)`
|
|
7854
8253
|
);
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
writeJson({
|
|
7860
|
-
ok: false,
|
|
7861
|
-
sync: settings,
|
|
7862
|
-
error: message,
|
|
7863
|
-
...retainedLegacyDatabase ? { retainedLegacyDatabase } : {}
|
|
7864
|
-
});
|
|
7865
|
-
} else console.error(message);
|
|
7866
|
-
process.exitCode = 1;
|
|
7867
|
-
}
|
|
7868
|
-
}
|
|
7869
|
-
async function handleSyncDisable(opts = {}) {
|
|
7870
|
-
const sync2 = rawSync();
|
|
7871
|
-
const settings = sync2.setEnabled(false);
|
|
7872
|
-
const observerWoken = await wakeObserverSync();
|
|
7873
|
-
if (opts.json) {
|
|
7874
|
-
writeJson({ ok: true, sync: settings, observerWoken });
|
|
7875
|
-
} else {
|
|
7876
|
-
console.log("FML raw artifact sync is disabled");
|
|
7877
|
-
const observer = await readObserverRuntimeStatus();
|
|
7878
|
-
if (observer.running) {
|
|
7879
|
-
console.log("FML observer remains available for local integrations");
|
|
7880
|
-
}
|
|
7881
|
-
}
|
|
7882
|
-
}
|
|
7883
|
-
async function handleSyncStatus(opts = {}) {
|
|
7884
|
-
const status = rawSync().status();
|
|
7885
|
-
const observer = await readObserverRuntimeStatus();
|
|
7886
|
-
if (opts.json) writeJson({ ok: true, status, observer });
|
|
7887
|
-
else {
|
|
7888
|
-
printStatus(status);
|
|
7889
|
-
const state = observer.stateStatus.state;
|
|
7890
|
-
console.log(
|
|
7891
|
-
` Observer: ${observer.healthy ? "running" : observer.running ? "unhealthy" : "stopped"}`
|
|
7892
|
-
);
|
|
7893
|
-
if (state) {
|
|
7894
|
-
console.log(` PID: ${state.pid}`);
|
|
7895
|
-
console.log(` Phase: ${state.phase}`);
|
|
7896
|
-
if (state.lastOutcome) {
|
|
7897
|
-
console.log(` Last run: ${state.lastOutcome}`);
|
|
8254
|
+
if (hooks.spool.quarantineMaxAgeMs !== void 0) {
|
|
8255
|
+
console.log(
|
|
8256
|
+
` Quarantine retention: ${hooks.spool.quarantineMaxAgeMs === null ? "unbounded" : `${Math.round(hooks.spool.quarantineMaxAgeMs / 864e5)} day(s)`}`
|
|
8257
|
+
);
|
|
7898
8258
|
}
|
|
7899
|
-
if (
|
|
8259
|
+
if (hooks.spool.evictedRecords > 0) {
|
|
7900
8260
|
console.log(
|
|
7901
|
-
`
|
|
8261
|
+
` Evicted: ${hooks.spool.evictedRecords} record(s), ${formatBytes2(hooks.spool.evictedBytes)} (${hooks.spool.evictedQuarantinedRecords} quarantined, ${hooks.spool.evictedPendingRecords} pending)`
|
|
7902
8262
|
);
|
|
8263
|
+
if (hooks.spool.lastEvictedAtMs !== void 0) {
|
|
8264
|
+
console.log(
|
|
8265
|
+
` Last eviction: ${new Date(hooks.spool.lastEvictedAtMs).toISOString()}`
|
|
8266
|
+
);
|
|
8267
|
+
}
|
|
7903
8268
|
}
|
|
7904
8269
|
}
|
|
7905
|
-
console.log(`
|
|
8270
|
+
if (hooks.lastError) console.log(` Error: ${hooks.lastError}`);
|
|
7906
8271
|
}
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
const result = rawSync().reset();
|
|
7910
|
-
if (opts.json) writeJson({ ok: !result.skipped, reset: result });
|
|
7911
|
-
else if (result.skipped) {
|
|
7912
|
-
console.error(
|
|
7913
|
-
"Raw artifact reset skipped: another sync owns the queue lock"
|
|
7914
|
-
);
|
|
8272
|
+
if (hookInbox.unreadable) {
|
|
8273
|
+
console.log(" Inbox: unavailable");
|
|
7915
8274
|
} else {
|
|
7916
8275
|
console.log(
|
|
7917
|
-
`
|
|
8276
|
+
` Inbox: ${hookInbox.pendingEntries} pending (${formatBytes2(hookInbox.pendingBytes)})`
|
|
7918
8277
|
);
|
|
7919
|
-
if (result.removed > 0) {
|
|
7920
|
-
console.log(`Removed ${result.removed} missing artifact entries.`);
|
|
7921
|
-
}
|
|
7922
8278
|
console.log(
|
|
7923
|
-
"
|
|
8279
|
+
` Inbox limits: ${hookInbox.maxBytes === null ? "unbounded bytes" : formatBytes2(hookInbox.maxBytes)}, ${hookInbox.maxEntries === null ? "unbounded entries" : `${hookInbox.maxEntries} entries`}`
|
|
7924
8280
|
);
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
} else {
|
|
7929
|
-
await wakeObserverSync();
|
|
7930
|
-
}
|
|
7931
|
-
}
|
|
7932
|
-
|
|
7933
|
-
// src/commands/daemon.ts
|
|
7934
|
-
async function handleFmlStart() {
|
|
7935
|
-
await handleSyncEnable();
|
|
7936
|
-
}
|
|
7937
|
-
async function handleFmlStop(opts = {}) {
|
|
7938
|
-
setRawSyncEnabled(false);
|
|
7939
|
-
const result = await stopObserver({ force: opts.force });
|
|
7940
|
-
if (result.status === "stopped" || result.status === "killed" || result.status === "not_running" || result.status === "stale_state") {
|
|
7941
|
-
try {
|
|
7942
|
-
setHookCaptureDisabled(true);
|
|
7943
|
-
} catch (error) {
|
|
7944
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
7945
|
-
console.error(
|
|
7946
|
-
`FML observer stopped, but the hook-capture pause could not be persisted: ${message}`
|
|
8281
|
+
if (hookInbox.capacityRejectedEntries > 0) {
|
|
8282
|
+
console.log(
|
|
8283
|
+
` Inbox capacity refusals: ${hookInbox.capacityRejectedEntries} record(s), ${formatBytes2(hookInbox.capacityRejectedBytes)}`
|
|
7947
8284
|
);
|
|
7948
|
-
|
|
8285
|
+
if (hookInbox.lastCapacityRejectedAtMs !== void 0) {
|
|
8286
|
+
console.log(
|
|
8287
|
+
` Last inbox refusal: ${new Date(hookInbox.lastCapacityRejectedAtMs).toISOString()}${hookInbox.lastCapacityRejectionReason ? ` (${hookInbox.lastCapacityRejectionReason})` : ""}`
|
|
8288
|
+
);
|
|
8289
|
+
}
|
|
7949
8290
|
}
|
|
7950
|
-
|
|
7951
|
-
switch (result.status) {
|
|
7952
|
-
case "stopped":
|
|
7953
|
-
console.log(`FML observer stopped (PID ${result.pid})`);
|
|
7954
|
-
break;
|
|
7955
|
-
case "killed":
|
|
7956
|
-
console.log(`FML observer force stopped (PID ${result.pid})`);
|
|
7957
|
-
break;
|
|
7958
|
-
case "not_running":
|
|
7959
|
-
console.log("FML observer was not running");
|
|
7960
|
-
break;
|
|
7961
|
-
case "stale_state":
|
|
8291
|
+
if (hookInbox.retiredEntries > 0) {
|
|
7962
8292
|
console.log(
|
|
7963
|
-
|
|
7964
|
-
);
|
|
7965
|
-
break;
|
|
7966
|
-
case "permission_denied":
|
|
7967
|
-
console.error(
|
|
7968
|
-
`Could not stop FML observer PID ${result.pid}: permission denied`
|
|
7969
|
-
);
|
|
7970
|
-
process.exitCode = 1;
|
|
7971
|
-
break;
|
|
7972
|
-
case "foreign_listener":
|
|
7973
|
-
console.error("Refused to stop a different local observer instance");
|
|
7974
|
-
process.exitCode = 1;
|
|
7975
|
-
break;
|
|
7976
|
-
case "ambiguous_live_process":
|
|
7977
|
-
console.error(
|
|
7978
|
-
`Refused to treat unverifiable live PID ${result.pid} as a stopped FML observer`
|
|
8293
|
+
` Inbox retired: ${hookInbox.retiredEntries} record(s), ${formatBytes2(hookInbox.retiredBytes)}`
|
|
7979
8294
|
);
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
const otlp = otlpStatus(protectedStatus);
|
|
8298
|
+
console.log("\nNative OpenTelemetry:");
|
|
8299
|
+
if (!otlp) {
|
|
8300
|
+
console.log(` Status: ${observer.healthy ? "unavailable" : "stopped"}`);
|
|
8301
|
+
} else {
|
|
8302
|
+
console.log(` Status: ${otlp.phase}`);
|
|
8303
|
+
console.log(` Pending: ${otlp.pending}`);
|
|
8304
|
+
if (otlp.spool) {
|
|
8305
|
+
const usedPercent = otlp.spool.logicalBytes / otlp.spool.maxBytes * 100;
|
|
8306
|
+
console.log(
|
|
8307
|
+
` Spool: ${formatBytes2(otlp.spool.logicalBytes)} / ${formatBytes2(otlp.spool.maxBytes)} (${usedPercent.toFixed(1)}%; ${formatBytes2(otlp.spool.bodyBytes)} encoded bodies)`
|
|
7985
8308
|
);
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
8309
|
+
if (usedPercent >= 80) {
|
|
8310
|
+
console.log(
|
|
8311
|
+
` Attention: the oldest pending telemetry is dropped when the spool reaches ${formatBytes2(otlp.spool.maxBytes)}`
|
|
8312
|
+
);
|
|
8313
|
+
}
|
|
8314
|
+
if (otlp.spool.droppedRecords > 0) {
|
|
8315
|
+
console.log(
|
|
8316
|
+
` Dropped: ${otlp.spool.droppedRecords} envelope(s), ${formatBytes2(otlp.spool.droppedLogicalBytes)} logical; ${formatBytes2(otlp.spool.droppedBodyBytes)} encoded bodies`
|
|
8317
|
+
);
|
|
8318
|
+
if (otlp.spool.lastDropReason) {
|
|
8319
|
+
console.log(
|
|
8320
|
+
` Last drop: ${otlp.spool.lastDropReason}${otlp.spool.lastDroppedAtMs === void 0 ? "" : ` at ${new Date(otlp.spool.lastDroppedAtMs).toISOString()}`}`
|
|
8321
|
+
);
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8324
|
+
}
|
|
8325
|
+
if (otlp.lastError) console.log(` Error: ${otlp.lastError}`);
|
|
8326
|
+
if (otlp.lastWarning) console.log(` Warning: ${otlp.lastWarning}`);
|
|
8327
|
+
if (otlp.nextAttemptAtMs !== void 0) {
|
|
8328
|
+
console.log(
|
|
8329
|
+
` Next attempt: ${new Date(otlp.nextAttemptAtMs).toISOString()}`
|
|
7995
8330
|
);
|
|
7996
|
-
|
|
7997
|
-
break;
|
|
8331
|
+
}
|
|
7998
8332
|
}
|
|
7999
8333
|
}
|
|
8000
8334
|
|
|
@@ -8002,7 +8336,7 @@ async function handleFmlStop(opts = {}) {
|
|
|
8002
8336
|
function handleEnvShow() {
|
|
8003
8337
|
const { name, convexUrl } = getActiveEnv();
|
|
8004
8338
|
console.log(`Environment: ${name}`);
|
|
8005
|
-
console.log(`
|
|
8339
|
+
console.log(` Service URL: ${convexUrl ?? "not configured"}`);
|
|
8006
8340
|
}
|
|
8007
8341
|
function handleEnvSwitch(target2) {
|
|
8008
8342
|
let name = target2;
|
|
@@ -8025,17 +8359,20 @@ function handleEnvSwitch(target2) {
|
|
|
8025
8359
|
return;
|
|
8026
8360
|
}
|
|
8027
8361
|
if (!convexUrl) {
|
|
8028
|
-
console.error(`
|
|
8362
|
+
console.error(`Environment ${name} has no configured service URL.`);
|
|
8029
8363
|
console.error(
|
|
8030
|
-
`
|
|
8364
|
+
`Set FML_CONVEX_URL=https://<deployment>.convex.cloud and run \`fml env ${name}\` again.`
|
|
8031
8365
|
);
|
|
8032
8366
|
process.exitCode = 1;
|
|
8033
8367
|
return;
|
|
8034
8368
|
}
|
|
8035
8369
|
setActiveEnvironment(name, convexUrl);
|
|
8036
|
-
console.log(`
|
|
8037
|
-
console.log(
|
|
8038
|
-
console.log("
|
|
8370
|
+
console.log(`Environment switched to ${name}.`);
|
|
8371
|
+
console.log(`Service URL: ${convexUrl}`);
|
|
8372
|
+
console.log("CLI commands and agent activity sync now use this environment.");
|
|
8373
|
+
console.log(
|
|
8374
|
+
"Start a new agent session to use the new environment and credentials."
|
|
8375
|
+
);
|
|
8039
8376
|
}
|
|
8040
8377
|
|
|
8041
8378
|
// src/commands/data.ts
|
|
@@ -8591,8 +8928,8 @@ async function handleConfigDetail(opts) {
|
|
|
8591
8928
|
|
|
8592
8929
|
// src/commands/tools.ts
|
|
8593
8930
|
import { readFileSync } from "fs";
|
|
8594
|
-
import
|
|
8595
|
-
var pluginVersion = true ? "0.8.0-rc.
|
|
8931
|
+
import path29 from "path";
|
|
8932
|
+
var pluginVersion = true ? "0.8.0-rc.3+246af775" : void 0;
|
|
8596
8933
|
async function getApi() {
|
|
8597
8934
|
const api = await getAuthenticatedClient();
|
|
8598
8935
|
if (!api) {
|
|
@@ -8699,7 +9036,7 @@ async function handleToolsCall(name, opts) {
|
|
|
8699
9036
|
}
|
|
8700
9037
|
const raw = opts.file ? (() => {
|
|
8701
9038
|
try {
|
|
8702
|
-
return readFileSync(
|
|
9039
|
+
return readFileSync(path29.resolve(opts.file), "utf8");
|
|
8703
9040
|
} catch (err) {
|
|
8704
9041
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8705
9042
|
console.error(`Could not read args file: ${msg}`);
|
|
@@ -8725,13 +9062,31 @@ async function handleToolsCall(name, opts) {
|
|
|
8725
9062
|
console.log(JSON.stringify(result.result, null, 2));
|
|
8726
9063
|
}
|
|
8727
9064
|
|
|
9065
|
+
// src/banner.ts
|
|
9066
|
+
import pc2 from "picocolors";
|
|
9067
|
+
var LOGO_LINES = [
|
|
9068
|
+
" \u2584\u2588\u2588\u2580 \u2588\u2588\u2584\u2588\u2588\u2584\u2588\u2588 \u2588\u2588",
|
|
9069
|
+
" \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
9070
|
+
"\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
9071
|
+
" \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588",
|
|
9072
|
+
" \u2580\u2580 \u2580\u2580 \u2580\u2580 \u2580\u2580\u2580\u2580"
|
|
9073
|
+
];
|
|
9074
|
+
function printBanner() {
|
|
9075
|
+
console.log("");
|
|
9076
|
+
for (let i = 0; i < LOGO_LINES.length; i++) {
|
|
9077
|
+
const color = i < 3 ? pc2.blue : pc2.cyan;
|
|
9078
|
+
console.log(" " + color(LOGO_LINES[i]));
|
|
9079
|
+
}
|
|
9080
|
+
console.log("");
|
|
9081
|
+
}
|
|
9082
|
+
|
|
8728
9083
|
// src/cli.ts
|
|
8729
9084
|
await initSentry();
|
|
8730
9085
|
if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
8731
9086
|
printBanner();
|
|
8732
9087
|
}
|
|
8733
9088
|
var program = new Command().name("fml").description("FML CLI and agent tools").version(
|
|
8734
|
-
true ? "0.8.0-rc.
|
|
9089
|
+
true ? "0.8.0-rc.3+246af775" : "dev"
|
|
8735
9090
|
);
|
|
8736
9091
|
function formatCommandInventory(cmd, prefix = "") {
|
|
8737
9092
|
const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
|
|
@@ -8760,11 +9115,12 @@ tools.command("call").description("Invoke a backend tool by name with JSON args"
|
|
|
8760
9115
|
program.command("mcp-server", { hidden: true }).description("Run the FML MCP server on stdio").action(async () => {
|
|
8761
9116
|
await import("./mcp/server.js");
|
|
8762
9117
|
});
|
|
8763
|
-
program.command("
|
|
9118
|
+
program.command("setup").description("Install integrations, sign in if needed, and start FML").action(() => handleSetup());
|
|
9119
|
+
program.command("install").description("Register FML agent integrations and the MCP server").action(() => handleInstall());
|
|
8764
9120
|
program.command("uninstall").description("Remove FML agent integrations and optionally local data").option("--purge", "Also remove all data, logs, and auth tokens").option("-y, --yes", "Skip the purge confirmation prompt").action(
|
|
8765
9121
|
(opts) => handleUninstall({ purge: opts.purge, assumeYes: opts.yes })
|
|
8766
9122
|
);
|
|
8767
|
-
program.command("update").description("Update fml
|
|
9123
|
+
program.command("update").description("Update fml from its release channel").action(() => handleUpdate());
|
|
8768
9124
|
program.command("login").description("Sign in to your FML account").option("--device", "Use device authorization flow (no browser needed)").option(
|
|
8769
9125
|
"--service-token",
|
|
8770
9126
|
"Sign in with an org service refresh token (prompts for fml_srt_*)"
|
|
@@ -8774,28 +9130,29 @@ program.command("login").description("Sign in to your FML account").option("--de
|
|
|
8774
9130
|
program.command("logout").description("Sign out and clear stored credentials").action(() => handleLogout());
|
|
8775
9131
|
program.command("org").description("Show or select organization").argument("[slug]", "Org slug to select").action((slug) => handleOrg(slug));
|
|
8776
9132
|
program.command("status").description("Show auth, environment, and raw sync watermarks").action(() => handleStatus());
|
|
8777
|
-
program.command("doctor").description("Check FML configuration,
|
|
9133
|
+
program.command("doctor").description("Check FML configuration, agent activity sync, and connectivity").option("--json", "Output as JSON").action((opts) => handleDoctor(opts));
|
|
8778
9134
|
program.command("open").description("Open FML dashboard in browser").option("--json", "Output URL as JSON instead of opening browser").action((opts) => handleOpen(opts));
|
|
8779
9135
|
program.command("env").description("Show or switch FML backend environment").argument("[target]", "Environment name or backend URL").action((target2) => {
|
|
8780
9136
|
if (target2) handleEnvSwitch(target2);
|
|
8781
9137
|
else handleEnvShow();
|
|
8782
9138
|
});
|
|
8783
|
-
program.command("start").description("Start the local observer and continuous
|
|
8784
|
-
program.command("
|
|
8785
|
-
|
|
8786
|
-
sync.command("
|
|
9139
|
+
program.command("start").description("Start the local observer and continuous agent activity sync").action(() => handleFmlStart());
|
|
9140
|
+
program.command("restart").description("Restart the local observer and continuous agent activity sync").action(() => handleFmlRestart());
|
|
9141
|
+
program.command("stop").description("Stop the local observer and pause agent activity sync").option("--force", "Escalate to SIGTERM/SIGKILL if the observer is hung").action((opts) => handleFmlStop({ force: opts.force }));
|
|
9142
|
+
var sync = program.command("sync").description("Sync Agent activity to FML").option("--dry-run", "Discover artifacts without changing queue state").option("--json", "Output as JSON").action((opts) => handleSyncRun(opts));
|
|
9143
|
+
sync.command("enable").description("Enable Agent activity sync").option("--json", "Output as JSON").action(
|
|
8787
9144
|
(_opts, command) => handleSyncEnable(command.optsWithGlobals())
|
|
8788
9145
|
);
|
|
8789
|
-
sync.command("disable").description("
|
|
9146
|
+
sync.command("disable").description("Pause Agent activity sync").option("--json", "Output as JSON").action(
|
|
8790
9147
|
(_opts, command) => handleSyncDisable(command.optsWithGlobals())
|
|
8791
9148
|
);
|
|
8792
|
-
sync.command("status").description("Show
|
|
9149
|
+
sync.command("status").description("Show Agent activity queue watermarks").option("--json", "Output as JSON").action(
|
|
8793
9150
|
(_opts, command) => handleSyncStatus(command.optsWithGlobals())
|
|
8794
9151
|
);
|
|
8795
|
-
sync.command("reset").description("Replay
|
|
9152
|
+
sync.command("reset").description("Replay Agent activity uploads from byte zero").option("--json", "Output as JSON").action(
|
|
8796
9153
|
(_opts, command) => handleSyncReset(command.optsWithGlobals())
|
|
8797
9154
|
);
|
|
8798
|
-
var artifacts = program.command("artifacts", { hidden: true }).description("Compatibility alias for
|
|
9155
|
+
var artifacts = program.command("artifacts", { hidden: true }).description("Compatibility alias for Agent activity sync");
|
|
8799
9156
|
artifacts.command("sync").option("--dry-run", "Discover artifacts without changing queue state").option("--json", "Output as JSON").action((opts) => handleSyncRun(opts));
|
|
8800
9157
|
program.command("activity").description("Activity summary \u2014 sessions, prompts, tools, costs").option("--since <duration>", 'Time window, e.g. "24h", "7d"').action((opts) => handleActivity(opts));
|
|
8801
9158
|
program.command("sessions").description("List recent sessions").option("--since <duration>", 'Time filter, e.g. "24h", "7d"').option("--limit <n>", "Max sessions to return").action((opts) => handleSessions(opts));
|