@fml-inc/fml 0.7.0 → 0.8.0-rc.2
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 +2 -4
- package/README.md +323 -79
- package/agent-assets/fml/SKILL.md +180 -0
- package/bin/fml +4 -18
- package/bin/hook-handler +7 -0
- package/bin/mcp-server +4 -18
- package/bin/observer +6 -0
- package/dist/chunk-3KISRT57.js +140 -0
- package/dist/chunk-56TPYMNK.js +4657 -0
- package/dist/chunk-7V7G7NVF.js +142 -0
- package/dist/chunk-A6HAEVJX.js +1606 -0
- package/dist/chunk-DZKYW327.js +1037 -0
- package/dist/chunk-ENW2GJNO.js +109 -0
- package/dist/{chunk-PZ5AY32C.js → chunk-MLKGABMK.js} +0 -1
- package/dist/{chunk-PTUZMKSN.js → chunk-R45CWWZO.js} +34 -23
- package/dist/chunk-WZLLO563.js +953 -0
- package/dist/chunk-YSU5CS2V.js +3331 -0
- package/dist/cli.js +7742 -1072
- package/dist/{esm-2EUDFZWP.js → esm-4YKLKFRX.js} +546 -741
- package/dist/hook-handler.js +440 -0
- package/dist/mcp/server.js +112 -98
- package/dist/observer-server.js +1566 -0
- package/dist/sync-cycle.js +31 -0
- package/dist/{token-store-3IQSLOTQ.js → token-store-BT5THIAJ.js} +10 -3
- package/hooks/hooks.json +290 -0
- package/package.json +24 -27
- package/dist/chunk-PTUZMKSN.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-S4TJNLEN.js +0 -61
- package/dist/chunk-S4TJNLEN.js.map +0 -1
- package/dist/chunk-VV4JHH4H.js +0 -437
- package/dist/chunk-VV4JHH4H.js.map +0 -1
- package/dist/chunk-YH5ROR7G.js +0 -69
- package/dist/chunk-YH5ROR7G.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/daemon-MSLDHLMH.js +0 -21
- package/dist/daemon-MSLDHLMH.js.map +0 -1
- package/dist/daemon-utils-OODBIYB7.js +0 -10
- package/dist/daemon-utils-OODBIYB7.js.map +0 -1
- package/dist/esm-2EUDFZWP.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/postinstall.js +0 -16
- package/dist/postinstall.js.map +0 -1
- package/dist/token-store-3IQSLOTQ.js.map +0 -1
- package/scripts/postinstall.cjs +0 -25
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FML_HOOK_CAPTURE_DISABLED_FILE
|
|
3
|
+
} from "./chunk-A6HAEVJX.js";
|
|
4
|
+
|
|
5
|
+
// src/json-file.ts
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
var NEW_FILE_MODE = 384;
|
|
10
|
+
var UNSUPPORTED_DIRECTORY_FSYNC_CODES = /* @__PURE__ */ new Set([
|
|
11
|
+
"EBADF",
|
|
12
|
+
"EINVAL",
|
|
13
|
+
"EISDIR",
|
|
14
|
+
"ENOTSUP",
|
|
15
|
+
"EOPNOTSUPP",
|
|
16
|
+
"EPERM"
|
|
17
|
+
]);
|
|
18
|
+
function fsyncParentDirectory(directory) {
|
|
19
|
+
if (process.platform === "win32") return;
|
|
20
|
+
let descriptor;
|
|
21
|
+
try {
|
|
22
|
+
descriptor = fs.openSync(directory, "r");
|
|
23
|
+
fs.fsyncSync(descriptor);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const code = error.code;
|
|
26
|
+
if (code === void 0 || !UNSUPPORTED_DIRECTORY_FSYNC_CODES.has(code)) {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
} finally {
|
|
30
|
+
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readJsonFile(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
36
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function writeTextFileAtomic(filePath, contents) {
|
|
42
|
+
try {
|
|
43
|
+
if (fs.readFileSync(filePath, "utf8") === contents) return;
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
let mode = NEW_FILE_MODE;
|
|
47
|
+
try {
|
|
48
|
+
mode = fs.statSync(filePath).mode & 511;
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
const directory = path.dirname(filePath);
|
|
52
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
53
|
+
const tempPath = path.join(
|
|
54
|
+
directory,
|
|
55
|
+
`.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
|
|
56
|
+
);
|
|
57
|
+
try {
|
|
58
|
+
const descriptor = fs.openSync(tempPath, "wx", mode);
|
|
59
|
+
try {
|
|
60
|
+
fs.writeFileSync(descriptor, contents, { encoding: "utf8" });
|
|
61
|
+
fs.fsyncSync(descriptor);
|
|
62
|
+
} finally {
|
|
63
|
+
fs.closeSync(descriptor);
|
|
64
|
+
}
|
|
65
|
+
fs.renameSync(tempPath, filePath);
|
|
66
|
+
fsyncParentDirectory(directory);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
fs.rmSync(tempPath, { force: true });
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function writeJsonFile(filePath, data) {
|
|
73
|
+
writeTextFileAtomic(filePath, `${JSON.stringify(data, null, 2)}
|
|
74
|
+
`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/hooks/capture-state.ts
|
|
78
|
+
import fs2 from "fs";
|
|
79
|
+
import path2 from "path";
|
|
80
|
+
function hookCaptureIsDisabled(stateFile = FML_HOOK_CAPTURE_DISABLED_FILE) {
|
|
81
|
+
try {
|
|
82
|
+
fs2.lstatSync(stateFile);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return error.code !== "ENOENT";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function setHookCaptureDisabled(disabled, stateFile = FML_HOOK_CAPTURE_DISABLED_FILE) {
|
|
89
|
+
if (disabled) {
|
|
90
|
+
const directory = path2.dirname(stateFile);
|
|
91
|
+
fs2.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
92
|
+
try {
|
|
93
|
+
fs2.chmodSync(directory, 448);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (process.platform !== "win32") throw error;
|
|
96
|
+
}
|
|
97
|
+
writeTextFileAtomic(stateFile, "disabled\n");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
fs2.rmSync(stateFile, { force: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
readJsonFile,
|
|
105
|
+
writeTextFileAtomic,
|
|
106
|
+
writeJsonFile,
|
|
107
|
+
hookCaptureIsDisabled,
|
|
108
|
+
setHookCaptureDisabled
|
|
109
|
+
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveRepoFromCwd
|
|
3
|
+
} from "./chunk-7V7G7NVF.js";
|
|
1
4
|
import {
|
|
2
5
|
CONVEX_URL,
|
|
6
|
+
getActiveEnv,
|
|
3
7
|
getSelectedOrg,
|
|
8
|
+
getSiteUrlForConvexUrl,
|
|
4
9
|
getValidToken
|
|
5
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-A6HAEVJX.js";
|
|
6
11
|
|
|
7
12
|
// src/fml-client.ts
|
|
8
|
-
import { resolveRepoFromCwd } from "@fml-inc/panopticon/repo";
|
|
9
13
|
var LOGIN_EXPIRED_MESSAGE = "Authentication expired. Run `fml login` to sign in again, then restart Claude Code.";
|
|
10
14
|
function isAuthError(data) {
|
|
11
15
|
return data.code === "UNAUTHENTICATED" || data.code === "TOKEN_EXPIRED" || data.error?.includes("Unauthorized") === true || data.error?.includes("not authenticated") === true;
|
|
@@ -22,17 +26,12 @@ function requireToolResult(result) {
|
|
|
22
26
|
}
|
|
23
27
|
return result.result;
|
|
24
28
|
}
|
|
25
|
-
function createFmlClient(token) {
|
|
29
|
+
function createFmlClient(token, options = {}) {
|
|
26
30
|
const isServiceToken = token.startsWith("fml_st_");
|
|
27
|
-
|
|
28
|
-
return CONVEX_URL.replace(".convex.cloud", ".convex.site").replace(
|
|
29
|
-
/\/$/,
|
|
30
|
-
""
|
|
31
|
-
);
|
|
32
|
-
}
|
|
31
|
+
const siteUrl = (options.siteUrl ?? getSiteUrlForConvexUrl(CONVEX_URL)).replace(/\/$/, "");
|
|
33
32
|
async function fetchContext(pathAndQuery, init) {
|
|
34
33
|
try {
|
|
35
|
-
const res = await fetch(`${
|
|
34
|
+
const res = await fetch(`${siteUrl}${pathAndQuery}`, {
|
|
36
35
|
...init,
|
|
37
36
|
headers: {
|
|
38
37
|
Authorization: `Bearer ${token}`,
|
|
@@ -44,7 +43,10 @@ function createFmlClient(token) {
|
|
|
44
43
|
try {
|
|
45
44
|
data = JSON.parse(text);
|
|
46
45
|
} catch {
|
|
47
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: `HTTP ${res.status}: ${text.slice(0, 200)}`
|
|
49
|
+
};
|
|
48
50
|
}
|
|
49
51
|
if (!res.ok) {
|
|
50
52
|
const envelope = data;
|
|
@@ -56,7 +58,10 @@ function createFmlClient(token) {
|
|
|
56
58
|
}
|
|
57
59
|
return { ok: true, result: data };
|
|
58
60
|
} catch (err) {
|
|
59
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: err instanceof Error ? err.message : String(err)
|
|
64
|
+
};
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
return {
|
|
@@ -69,18 +74,18 @@ function createFmlClient(token) {
|
|
|
69
74
|
async callBackend(toolName, args, opts) {
|
|
70
75
|
try {
|
|
71
76
|
const body = { toolName, args };
|
|
72
|
-
const org = opts?.org ?? getSelectedOrg();
|
|
77
|
+
const org = opts?.org ?? getSelectedOrg(options.envName);
|
|
73
78
|
if (org) body.org = org;
|
|
74
79
|
const repo = resolveRepoFromCwd(process.cwd());
|
|
75
80
|
if (repo) body.repo = repo.repo;
|
|
76
81
|
if (isServiceToken) {
|
|
77
|
-
const { readTokens, SERVICE_TOKEN_LOGIN_USER_ID } = await import("./token-store-
|
|
78
|
-
const stored = readTokens();
|
|
82
|
+
const { readTokens, SERVICE_TOKEN_LOGIN_USER_ID } = await import("./token-store-BT5THIAJ.js");
|
|
83
|
+
const stored = readTokens(options.envName);
|
|
79
84
|
const storedUserExternalId = stored?.tokenType === "service" && stored.user.id !== SERVICE_TOKEN_LOGIN_USER_ID ? stored.user.id : void 0;
|
|
80
85
|
const userExternalId = process.env.FML_USER_EXTERNAL_ID ?? storedUserExternalId;
|
|
81
86
|
if (userExternalId) body.userExternalId = userExternalId;
|
|
82
87
|
}
|
|
83
|
-
const res = await fetch(`${
|
|
88
|
+
const res = await fetch(`${siteUrl}/api/tools/execute`, {
|
|
84
89
|
method: "POST",
|
|
85
90
|
headers: {
|
|
86
91
|
"Content-Type": "application/json",
|
|
@@ -98,8 +103,12 @@ function createFmlClient(token) {
|
|
|
98
103
|
error: `HTTP ${res.status}: ${text.slice(0, 200)}`
|
|
99
104
|
};
|
|
100
105
|
}
|
|
101
|
-
if (!res.ok
|
|
102
|
-
return {
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
return normalizeToolResult({
|
|
108
|
+
ok: false,
|
|
109
|
+
error: data.error ?? `HTTP ${res.status}`,
|
|
110
|
+
code: data.code
|
|
111
|
+
});
|
|
103
112
|
}
|
|
104
113
|
return normalizeToolResult(data);
|
|
105
114
|
} catch (err) {
|
|
@@ -113,7 +122,7 @@ function createFmlClient(token) {
|
|
|
113
122
|
// ── Tool catalog ─────────────────────────────────────────────────────
|
|
114
123
|
async listTools(pluginVersion) {
|
|
115
124
|
try {
|
|
116
|
-
const url = new URL(`${
|
|
125
|
+
const url = new URL(`${siteUrl}/api/tools/list`);
|
|
117
126
|
if (pluginVersion) url.searchParams.set("pluginVersion", pluginVersion);
|
|
118
127
|
const res = await fetch(url.toString(), {
|
|
119
128
|
headers: { Authorization: `Bearer ${token}` }
|
|
@@ -206,14 +215,16 @@ function createFmlClient(token) {
|
|
|
206
215
|
}
|
|
207
216
|
};
|
|
208
217
|
}
|
|
209
|
-
async function getAuthenticatedClient() {
|
|
210
|
-
const token = await getValidToken();
|
|
218
|
+
async function getAuthenticatedClient(environment = getActiveEnv()) {
|
|
219
|
+
const token = await getValidToken({ env: environment.name });
|
|
211
220
|
if (!token) return null;
|
|
212
|
-
return createFmlClient(token
|
|
221
|
+
return createFmlClient(token, {
|
|
222
|
+
envName: environment.name,
|
|
223
|
+
siteUrl: getSiteUrlForConvexUrl(environment.convexUrl)
|
|
224
|
+
});
|
|
213
225
|
}
|
|
214
226
|
|
|
215
227
|
export {
|
|
216
228
|
createFmlClient,
|
|
217
229
|
getAuthenticatedClient
|
|
218
230
|
};
|
|
219
|
-
//# sourceMappingURL=chunk-PTUZMKSN.js.map
|