@fml-inc/fml 0.1.3 → 0.2.0
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/dist/{chunk-I5BHNW37.js → chunk-BOREGHA6.js} +37 -1
- package/dist/chunk-BOREGHA6.js.map +1 -0
- package/dist/{chunk-YIWT7BWN.js → chunk-NFC24I3N.js} +2 -2
- package/dist/{chunk-ZA4RG25C.js → chunk-ZKHH6EYQ.js} +2 -2
- package/dist/{chunk-ZA4RG25C.js.map → chunk-ZKHH6EYQ.js.map} +1 -1
- package/dist/cli.js +138 -7
- package/dist/cli.js.map +1 -1
- package/dist/{daemon-2YGD43RJ.js → daemon-WXE6FYZD.js} +3 -3
- package/dist/{daemon-utils-LIG6K2MK.js → daemon-utils-EUOTVCKL.js} +2 -2
- package/dist/mcp/server.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-I5BHNW37.js.map +0 -1
- /package/dist/{chunk-YIWT7BWN.js.map → chunk-NFC24I3N.js.map} +0 -0
- /package/dist/{daemon-2YGD43RJ.js.map → daemon-WXE6FYZD.js.map} +0 -0
- /package/dist/{daemon-utils-LIG6K2MK.js.map → daemon-utils-EUOTVCKL.js.map} +0 -0
|
@@ -130,6 +130,42 @@ function createApiClient(token) {
|
|
|
130
130
|
return { ok: false, error: msg };
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
|
+
// ── Tool catalog ─────────────────────────────────────────────────────
|
|
134
|
+
async listTools(pluginVersion) {
|
|
135
|
+
try {
|
|
136
|
+
if (isServiceToken) {
|
|
137
|
+
const url = new URL(`${getSiteUrl()}/api/tools/list`);
|
|
138
|
+
if (pluginVersion) url.searchParams.set("pluginVersion", pluginVersion);
|
|
139
|
+
const res = await fetch(url.toString(), {
|
|
140
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
141
|
+
});
|
|
142
|
+
const text = await res.text();
|
|
143
|
+
let data;
|
|
144
|
+
try {
|
|
145
|
+
data = JSON.parse(text);
|
|
146
|
+
} catch {
|
|
147
|
+
throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
148
|
+
}
|
|
149
|
+
if (!data.ok || !data.descriptors) {
|
|
150
|
+
throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
151
|
+
}
|
|
152
|
+
return data.descriptors;
|
|
153
|
+
}
|
|
154
|
+
const client = await getClient();
|
|
155
|
+
return await client.query(
|
|
156
|
+
ref("user/plugin_tools:listTools"),
|
|
157
|
+
pluginVersion ? { pluginVersion } : {}
|
|
158
|
+
);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
161
|
+
if (msg.includes("Unauthorized") || msg.includes("not authenticated")) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"Authentication expired. Run `fml login` to sign in again, then restart Claude Code."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
133
169
|
// ── Repo resolution ──────────────────────────────────────────────────
|
|
134
170
|
async resolveRepo(orgSlug, repoFullName) {
|
|
135
171
|
const client = await getClient();
|
|
@@ -182,4 +218,4 @@ export {
|
|
|
182
218
|
createApiClient,
|
|
183
219
|
getAuthenticatedClient
|
|
184
220
|
};
|
|
185
|
-
//# sourceMappingURL=chunk-
|
|
221
|
+
//# sourceMappingURL=chunk-BOREGHA6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/native-check.ts","../src/convex-client.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { execSync } from \"node:child_process\";\nimport { rmSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Verify that better-sqlite3's native addon matches the running Node version.\n * If mismatched (e.g. user upgraded Node), attempt an automatic rebuild.\n * Call this before any code that imports better-sqlite3.\n */\nexport function ensureNativeModules(): void {\n const require = createRequire(import.meta.url);\n try {\n require(\"better-sqlite3\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"\";\n if (!msg.includes(\"NODE_MODULE_VERSION\")) {\n throw err;\n }\n\n console.error(\n \"[fml] better-sqlite3 was compiled for a different Node.js version, rebuilding...\",\n );\n\n const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), \"..\");\n\n // Remove stale build directory to avoid EEXIST errors from node-gyp\n const sqlitePath = dirname(require.resolve(\"better-sqlite3/package.json\"));\n rmSync(join(sqlitePath, \"build\"), { recursive: true, force: true });\n\n try {\n execSync(\"npm rebuild better-sqlite3\", {\n cwd: pkgRoot,\n stdio: \"inherit\",\n timeout: 60_000,\n });\n console.error(\"[fml] Rebuild complete.\");\n } catch {\n console.error(\n \"[fml] Automatic rebuild failed. Please run manually:\\n\" +\n ` cd ${pkgRoot} && rm -rf node_modules/better-sqlite3/build && npm rebuild better-sqlite3`,\n );\n process.exit(1);\n }\n }\n}\n","import type { FunctionReference } from \"convex/server\";\nimport { resolveRepoFromCwd } from \"@fml-inc/panopticon/repo\";\nimport { getSelectedOrg, getValidToken } from \"./auth/token-store.js\";\nimport { CONVEX_URL } from \"./config.js\";\nimport type {\n RepoConfigSnapshotDetail,\n RepoConfigSnapshotSummary,\n ResolvedRepo,\n UserConfigSnapshotDetail,\n UserConfigSnapshotSummary,\n} from \"./types.js\";\n\n// ── Shared plumbing ─────────────────────────────────────────────────────────\n\nexport type ToolCategory =\n | \"messages\"\n | \"slack\"\n | \"skills\"\n | \"analysis\"\n | \"integrations\"\n | \"engineering\"\n | \"memory\"\n | \"automations\"\n | \"amplitude\"\n | \"posthog\"\n | \"meta-ads\";\n\nexport interface PublicToolDescriptor {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n category?: ToolCategory;\n experimental?: boolean;\n}\n\nexport interface ToolResult {\n ok: boolean;\n result?: unknown;\n error?: string;\n}\n\nexport interface OrgInfo {\n _id: string;\n name: string;\n slug?: string;\n repos?: Array<{\n _id: string;\n fullName: string;\n owner: string;\n name: string;\n private: boolean;\n }>;\n}\n\nfunction ref<T extends \"query\" | \"mutation\" | \"action\">(\n path: string,\n): FunctionReference<T> {\n return path as unknown as FunctionReference<T>;\n}\n\n// ── API client factory ──────────────────────────────────────────────────────\n\nexport function createApiClient(token: string) {\n const isServiceToken = token.startsWith(\"fml_st_\");\n\n let clientPromise: Promise<{\n query: <T>(\n ref: FunctionReference<\"query\">,\n args: Record<string, unknown>,\n ) => Promise<T>;\n mutation: <T>(\n ref: FunctionReference<\"mutation\">,\n args: Record<string, unknown>,\n ) => Promise<T>;\n action: <T>(\n ref: FunctionReference<\"action\">,\n args: Record<string, unknown>,\n ) => Promise<T>;\n }> | null = null;\n\n function getClient() {\n if (!clientPromise) {\n clientPromise = import(\"convex/browser\").then(({ ConvexHttpClient }) => {\n const client = new ConvexHttpClient(CONVEX_URL);\n client.setAuth(token);\n return client;\n });\n }\n return clientPromise;\n }\n\n /**\n * Derive the Convex site URL (HTTP actions) from the cloud URL.\n * Convex uses paired domains: *.convex.cloud for client APIs,\n * *.convex.site for HTTP actions. This is a stable Convex convention.\n */\n function getSiteUrl(): string {\n return CONVEX_URL.replace(\".convex.cloud\", \".convex.site\").replace(\n /\\/$/,\n \"\",\n );\n }\n\n return {\n // ── Orgs ──────────────────────────────────────────────────────────────\n\n async queryOrgs(): Promise<OrgInfo[]> {\n if (isServiceToken) {\n // Service tokens can't call Convex actions directly — org is embedded in the token\n return [];\n }\n const client = await getClient();\n return await client.query(ref(\"user/plugin:getMyOrgsAndRepos\"), {});\n },\n\n // ── Tool gateway ─────────────────────────────────────────────────────\n\n async callBackend(\n toolName: string,\n args: Record<string, unknown>,\n opts?: { org?: string },\n ): Promise<ToolResult> {\n try {\n if (isServiceToken) {\n // Service token path: POST to HTTP endpoint (Convex can't validate non-JWT tokens)\n // Thread user identity: sandbox agents set FML_USER_EXTERNAL_ID;\n // device-flow logins fall back to the id stashed on the stored token\n // so tools can execute with the real user context even though the\n // token's actAsExternalId is the namespaced system:cli:* string.\n const { readTokens } = await import(\"./auth/token-store.js\");\n const userExternalId =\n process.env.FML_USER_EXTERNAL_ID ?? readTokens()?.user.id;\n const res = await fetch(`${getSiteUrl()}/api/tools/execute`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n toolName,\n args,\n ...(userExternalId && { userExternalId }),\n }),\n });\n const text = await res.text();\n let data: ToolResult;\n try {\n data = JSON.parse(text) as ToolResult;\n } catch {\n return {\n ok: false,\n error: `HTTP ${res.status}: ${text.slice(0, 200)}`,\n };\n }\n if (!res.ok && !data.error) {\n return { ok: false, error: `HTTP ${res.status}` };\n }\n return data;\n }\n\n // JWT path: standard Convex action\n const client = await getClient();\n const actionArgs: Record<string, unknown> = { toolName, args };\n // Explicit org > stored org selection > repo-based inference\n const org = opts?.org ?? getSelectedOrg();\n if (org) actionArgs.org = org;\n const repo = resolveRepoFromCwd(process.cwd());\n if (repo) actionArgs.repo = repo.repo;\n\n const result = await client.action(\n ref<\"action\">(\"user/tool_gateway:executeTool\"),\n actionArgs,\n );\n return { ok: true, result };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Unauthorized\") || msg.includes(\"not authenticated\")) {\n return {\n ok: false,\n error:\n \"Authentication expired. Run `fml login` to sign in again, then restart Claude Code.\",\n };\n }\n return { ok: false, error: msg };\n }\n },\n\n // ── Tool catalog ─────────────────────────────────────────────────────\n\n async listTools(pluginVersion?: string): Promise<PublicToolDescriptor[]> {\n try {\n if (isServiceToken) {\n const url = new URL(`${getSiteUrl()}/api/tools/list`);\n if (pluginVersion) url.searchParams.set(\"pluginVersion\", pluginVersion);\n const res = await fetch(url.toString(), {\n headers: { Authorization: `Bearer ${token}` },\n });\n const text = await res.text();\n let data: { ok: boolean; descriptors?: PublicToolDescriptor[]; error?: string };\n try {\n data = JSON.parse(text) as typeof data;\n } catch {\n throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);\n }\n if (!data.ok || !data.descriptors) {\n throw new Error(data.error ?? `HTTP ${res.status}`);\n }\n return data.descriptors;\n }\n\n const client = await getClient();\n return await client.query<PublicToolDescriptor[]>(\n ref<\"query\">(\"user/plugin_tools:listTools\"),\n pluginVersion ? { pluginVersion } : {},\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Unauthorized\") || msg.includes(\"not authenticated\")) {\n throw new Error(\n \"Authentication expired. Run `fml login` to sign in again, then restart Claude Code.\",\n );\n }\n throw err;\n }\n },\n\n // ── Repo resolution ──────────────────────────────────────────────────\n\n async resolveRepo(\n orgSlug: string,\n repoFullName: string,\n ): Promise<ResolvedRepo | null> {\n const client = await getClient();\n return await client.query(ref(\"user/cli:resolveRepo\"), {\n orgSlug,\n repoFullName,\n });\n },\n\n // ── Config snapshots ─────────────────────────────────────────────────\n\n async listUserConfigSnapshots(\n orgSlug: string,\n ): Promise<UserConfigSnapshotSummary[]> {\n const client = await getClient();\n const result = await client.query<UserConfigSnapshotSummary[] | null>(\n ref(\"user/config_snapshots:listUserSnapshots\"),\n { orgSlug },\n );\n return result ?? [];\n },\n\n async getUserConfigDetail(\n orgSlug: string,\n githubUsername: string,\n ): Promise<UserConfigSnapshotDetail | null> {\n const client = await getClient();\n return await client.query(ref(\"user/config_snapshots:getUserDetail\"), {\n orgSlug,\n githubUsername,\n });\n },\n\n async listRepoConfigSnapshots(\n orgSlug: string,\n repository?: string,\n ): Promise<RepoConfigSnapshotSummary[]> {\n const client = await getClient();\n const result = await client.query<RepoConfigSnapshotSummary[] | null>(\n ref(\"user/config_snapshots:listRepoSnapshots\"),\n { orgSlug, repository },\n );\n return result ?? [];\n },\n\n async getRepoConfigDetail(\n orgSlug: string,\n repository: string,\n ): Promise<RepoConfigSnapshotDetail | null> {\n const client = await getClient();\n return await client.query(ref(\"user/config_snapshots:getRepoDetail\"), {\n orgSlug,\n repository,\n });\n },\n };\n}\n\n// ── Convenience: auto-authenticated client ──────────────────────────────────\n\n/**\n * Create an API client using the stored auth token.\n * Returns null if not authenticated.\n */\nexport async function getAuthenticatedClient() {\n const token = await getValidToken();\n if (!token) return null;\n return createApiClient(token);\n}\n"],"mappings":"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","names":["require"]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
SYNC_PID_FILE,
|
|
3
3
|
isDaemonRunning,
|
|
4
4
|
panopticonExec
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ZKHH6EYQ.js";
|
|
6
6
|
|
|
7
7
|
// src/commands/daemon.ts
|
|
8
8
|
function parsePanopticonRunning() {
|
|
@@ -30,4 +30,4 @@ export {
|
|
|
30
30
|
handleStart,
|
|
31
31
|
handleStop
|
|
32
32
|
};
|
|
33
|
-
//# sourceMappingURL=chunk-
|
|
33
|
+
//# sourceMappingURL=chunk-NFC24I3N.js.map
|
|
@@ -40,7 +40,7 @@ function panopticonExec(...argsAndOpts) {
|
|
|
40
40
|
const bin = resolvePanopticonBin();
|
|
41
41
|
if (!bin) return { ok: false, stdout: "panopticon binary not found" };
|
|
42
42
|
try {
|
|
43
|
-
const stdout = execFileSync(
|
|
43
|
+
const stdout = execFileSync(bin, args, {
|
|
44
44
|
encoding: "utf-8",
|
|
45
45
|
timeout
|
|
46
46
|
});
|
|
@@ -58,4 +58,4 @@ export {
|
|
|
58
58
|
resolvePanopticonBin,
|
|
59
59
|
panopticonExec
|
|
60
60
|
};
|
|
61
|
-
//# sourceMappingURL=chunk-
|
|
61
|
+
//# sourceMappingURL=chunk-ZKHH6EYQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon-utils.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\nimport { FML_DATA_DIR, FML_LOG_DIR } from \"./dirs.js\";\n\nexport const SYNC_PID_FILE = path.join(FML_DATA_DIR, \"sync.pid\");\nexport const SYNC_LOG_FILE = path.join(FML_LOG_DIR, \"sync.log\");\n\nexport function isDaemonRunning(pidFile: string): {\n running: boolean;\n pid: number | null;\n} {\n if (!fs.existsSync(pidFile)) return { running: false, pid: null };\n const pid = parseInt(fs.readFileSync(pidFile, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, 0);\n return { running: true, pid };\n } catch {\n return { running: false, pid };\n }\n}\n\n/** Resolve the panopticon CLI binary from PATH (globally installed). */\nexport function resolvePanopticonBin(): string | null {\n try {\n const result = execFileSync(\"which\", [\"panopticon\"], {\n encoding: \"utf-8\",\n timeout: 5_000,\n }).trim();\n if (result) return result;\n } catch {}\n\n return null;\n}\n\n/** Run a panopticon CLI command and return stdout. */\nexport function panopticonExec(...args: string[]): {\n ok: boolean;\n stdout: string;\n};\nexport function panopticonExec(\n ...argsAndOpts: [...string[], { timeout?: number }]\n): { ok: boolean; stdout: string };\nexport function panopticonExec(\n ...argsAndOpts: Array<string | { timeout?: number }>\n): {\n ok: boolean;\n stdout: string;\n} {\n let timeout = 10_000;\n const args: string[] = [];\n for (const a of argsAndOpts) {\n if (typeof a === \"string\") args.push(a);\n else timeout = a.timeout ?? timeout;\n }\n const bin = resolvePanopticonBin();\n if (!bin) return { ok: false, stdout: \"panopticon binary not found\" };\n try {\n const stdout = execFileSync(
|
|
1
|
+
{"version":3,"sources":["../src/daemon-utils.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\nimport { FML_DATA_DIR, FML_LOG_DIR } from \"./dirs.js\";\n\nexport const SYNC_PID_FILE = path.join(FML_DATA_DIR, \"sync.pid\");\nexport const SYNC_LOG_FILE = path.join(FML_LOG_DIR, \"sync.log\");\n\nexport function isDaemonRunning(pidFile: string): {\n running: boolean;\n pid: number | null;\n} {\n if (!fs.existsSync(pidFile)) return { running: false, pid: null };\n const pid = parseInt(fs.readFileSync(pidFile, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, 0);\n return { running: true, pid };\n } catch {\n return { running: false, pid };\n }\n}\n\n/** Resolve the panopticon CLI binary from PATH (globally installed). */\nexport function resolvePanopticonBin(): string | null {\n try {\n const result = execFileSync(\"which\", [\"panopticon\"], {\n encoding: \"utf-8\",\n timeout: 5_000,\n }).trim();\n if (result) return result;\n } catch {}\n\n return null;\n}\n\n/** Run a panopticon CLI command and return stdout. */\nexport function panopticonExec(...args: string[]): {\n ok: boolean;\n stdout: string;\n};\nexport function panopticonExec(\n ...argsAndOpts: [...string[], { timeout?: number }]\n): { ok: boolean; stdout: string };\nexport function panopticonExec(\n ...argsAndOpts: Array<string | { timeout?: number }>\n): {\n ok: boolean;\n stdout: string;\n} {\n let timeout = 10_000;\n const args: string[] = [];\n for (const a of argsAndOpts) {\n if (typeof a === \"string\") args.push(a);\n else timeout = a.timeout ?? timeout;\n }\n const bin = resolvePanopticonBin();\n if (!bin) return { ok: false, stdout: \"panopticon binary not found\" };\n try {\n const stdout = execFileSync(bin, args, {\n encoding: \"utf-8\",\n timeout,\n });\n return { ok: true, stdout };\n } catch (err: unknown) {\n const e = err as { stdout?: string; stderr?: string; status?: number };\n return { ok: false, stdout: (e.stdout ?? \"\") + (e.stderr ?? \"\") };\n }\n}\n"],"mappings":";;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,oBAAoB;AAGtB,IAAM,gBAAgB,KAAK,KAAK,cAAc,UAAU;AACxD,IAAM,gBAAgB,KAAK,KAAK,aAAa,UAAU;AAEvD,SAAS,gBAAgB,SAG9B;AACA,MAAI,CAAC,GAAG,WAAW,OAAO,EAAG,QAAO,EAAE,SAAS,OAAO,KAAK,KAAK;AAChE,QAAM,MAAM,SAAS,GAAG,aAAa,SAAS,OAAO,EAAE,KAAK,GAAG,EAAE;AACjE,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO,EAAE,SAAS,MAAM,IAAI;AAAA,EAC9B,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,IAAI;AAAA,EAC/B;AACF;AAGO,SAAS,uBAAsC;AACpD,MAAI;AACF,UAAM,SAAS,aAAa,SAAS,CAAC,YAAY,GAAG;AAAA,MACnD,UAAU;AAAA,MACV,SAAS;AAAA,IACX,CAAC,EAAE,KAAK;AACR,QAAI,OAAQ,QAAO;AAAA,EACrB,QAAQ;AAAA,EAAC;AAET,SAAO;AACT;AAUO,SAAS,kBACX,aAIH;AACA,MAAI,UAAU;AACd,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,aAAa;AAC3B,QAAI,OAAO,MAAM,SAAU,MAAK,KAAK,CAAC;AAAA,QACjC,WAAU,EAAE,WAAW;AAAA,EAC9B;AACA,QAAM,MAAM,qBAAqB;AACjC,MAAI,CAAC,IAAK,QAAO,EAAE,IAAI,OAAO,QAAQ,8BAA8B;AACpE,MAAI;AACF,UAAM,SAAS,aAAa,KAAK,MAAM;AAAA,MACrC,UAAU;AAAA,MACV;AAAA,IACF,CAAC;AACD,WAAO,EAAE,IAAI,MAAM,OAAO;AAAA,EAC5B,SAAS,KAAc;AACrB,UAAM,IAAI;AACV,WAAO,EAAE,IAAI,OAAO,SAAS,EAAE,UAAU,OAAO,EAAE,UAAU,IAAI;AAAA,EAClE;AACF;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createApiClient,
|
|
4
4
|
ensureNativeModules,
|
|
5
5
|
getAuthenticatedClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BOREGHA6.js";
|
|
7
7
|
import {
|
|
8
8
|
CONVEX_URL,
|
|
9
9
|
DEFAULT_SYNC_URL,
|
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
handleStart,
|
|
28
28
|
handleStop,
|
|
29
29
|
parsePanopticonRunning
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-NFC24I3N.js";
|
|
31
31
|
import {
|
|
32
32
|
panopticonExec
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-ZKHH6EYQ.js";
|
|
34
34
|
import {
|
|
35
35
|
FML_DATA_DIR,
|
|
36
36
|
FML_LOG_DIR
|
|
@@ -369,7 +369,7 @@ async function handleInstall() {
|
|
|
369
369
|
const pluginRoot = getPluginRoot();
|
|
370
370
|
console.log("Installing fml...\n");
|
|
371
371
|
console.log("[1/5] Setting up panopticon...");
|
|
372
|
-
const { resolvePanopticonBin } = await import("./daemon-utils-
|
|
372
|
+
const { resolvePanopticonBin } = await import("./daemon-utils-EUOTVCKL.js");
|
|
373
373
|
let freshInstall = false;
|
|
374
374
|
const bin = resolvePanopticonBin();
|
|
375
375
|
let needsInstall = !bin;
|
|
@@ -689,7 +689,7 @@ function handleUninstall(opts) {
|
|
|
689
689
|
// src/commands/update.ts
|
|
690
690
|
import { execSync as execSync2 } from "child_process";
|
|
691
691
|
async function handleUpdate() {
|
|
692
|
-
const currentVersion = true ? "0.
|
|
692
|
+
const currentVersion = true ? "0.2.0+beaba19" : "unknown";
|
|
693
693
|
console.log(`Current: ${currentVersion}`);
|
|
694
694
|
console.log("Updating via npm...\n");
|
|
695
695
|
try {
|
|
@@ -1931,6 +1931,134 @@ async function handleConfigDetail(opts) {
|
|
|
1931
1931
|
}
|
|
1932
1932
|
}
|
|
1933
1933
|
|
|
1934
|
+
// src/commands/tools.ts
|
|
1935
|
+
import { readFileSync } from "fs";
|
|
1936
|
+
import path3 from "path";
|
|
1937
|
+
var pluginVersion = true ? "0.2.0+beaba19" : void 0;
|
|
1938
|
+
async function getApi() {
|
|
1939
|
+
const api = await getAuthenticatedClient();
|
|
1940
|
+
if (!api) {
|
|
1941
|
+
console.error("Not authenticated. Run `fml login`.");
|
|
1942
|
+
process.exit(1);
|
|
1943
|
+
}
|
|
1944
|
+
return api;
|
|
1945
|
+
}
|
|
1946
|
+
function levenshtein(a, b) {
|
|
1947
|
+
if (a === b) return 0;
|
|
1948
|
+
if (a.length === 0) return b.length;
|
|
1949
|
+
if (b.length === 0) return a.length;
|
|
1950
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1951
|
+
for (let i = 0; i < a.length; i++) {
|
|
1952
|
+
const curr = [i + 1];
|
|
1953
|
+
for (let j = 0; j < b.length; j++) {
|
|
1954
|
+
const cost = a[i] === b[j] ? 0 : 1;
|
|
1955
|
+
curr.push(Math.min(curr[j] + 1, prev[j + 1] + 1, prev[j] + cost));
|
|
1956
|
+
}
|
|
1957
|
+
prev = curr;
|
|
1958
|
+
}
|
|
1959
|
+
return prev[b.length];
|
|
1960
|
+
}
|
|
1961
|
+
function closestMatches(name, descriptors) {
|
|
1962
|
+
const threshold = Math.max(2, Math.ceil(name.length * 0.4));
|
|
1963
|
+
return descriptors.map((d) => ({
|
|
1964
|
+
name: d.name,
|
|
1965
|
+
// Substring match always beats fuzzy match.
|
|
1966
|
+
score: d.name.includes(name) ? 0 : levenshtein(name, d.name)
|
|
1967
|
+
})).filter((m) => m.score <= threshold).sort((a, b) => a.score - b.score).slice(0, 3).map((m) => m.name);
|
|
1968
|
+
}
|
|
1969
|
+
function findOrSuggestExit(name, descriptors) {
|
|
1970
|
+
const descriptor = descriptors.find((d) => d.name === name);
|
|
1971
|
+
if (descriptor) return descriptor;
|
|
1972
|
+
console.error(`Unknown tool: ${name}`);
|
|
1973
|
+
const suggestions = closestMatches(name, descriptors);
|
|
1974
|
+
if (suggestions.length > 0) {
|
|
1975
|
+
console.error(`Did you mean: ${suggestions.join(", ")}?`);
|
|
1976
|
+
}
|
|
1977
|
+
process.exit(1);
|
|
1978
|
+
}
|
|
1979
|
+
async function handleToolsList(opts) {
|
|
1980
|
+
const api = await getApi();
|
|
1981
|
+
let descriptors = await api.listTools(pluginVersion);
|
|
1982
|
+
if (opts.category) {
|
|
1983
|
+
descriptors = descriptors.filter((d) => d.category === opts.category);
|
|
1984
|
+
}
|
|
1985
|
+
if (opts.json) {
|
|
1986
|
+
console.log(JSON.stringify(descriptors, null, 2));
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
if (descriptors.length === 0) {
|
|
1990
|
+
console.log("No tools found.");
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
const nameW = Math.min(
|
|
1994
|
+
40,
|
|
1995
|
+
Math.max(...descriptors.map((d) => d.name.length), 4)
|
|
1996
|
+
);
|
|
1997
|
+
const catW = 14;
|
|
1998
|
+
const header = "name".padEnd(nameW) + " " + "category".padEnd(catW) + " description";
|
|
1999
|
+
const divider = "-".repeat(header.length);
|
|
2000
|
+
console.log(header);
|
|
2001
|
+
console.log(divider);
|
|
2002
|
+
for (const d of descriptors) {
|
|
2003
|
+
const cat = d.category ?? "";
|
|
2004
|
+
const suffix = d.experimental ? " (experimental)" : "";
|
|
2005
|
+
const desc = (d.description + suffix).slice(0, 80);
|
|
2006
|
+
console.log(d.name.padEnd(nameW) + " " + cat.padEnd(catW) + " " + desc);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
async function handleToolsDescribe(name, opts) {
|
|
2010
|
+
const api = await getApi();
|
|
2011
|
+
const descriptors = await api.listTools(pluginVersion);
|
|
2012
|
+
const descriptor = findOrSuggestExit(name, descriptors);
|
|
2013
|
+
if (opts.json) {
|
|
2014
|
+
console.log(JSON.stringify(descriptor, null, 2));
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
console.log(`Name: ${descriptor.name}`);
|
|
2018
|
+
if (descriptor.category) console.log(`Category: ${descriptor.category}`);
|
|
2019
|
+
if (descriptor.experimental) console.log(`Experimental: true`);
|
|
2020
|
+
console.log(`
|
|
2021
|
+
Description:
|
|
2022
|
+
${descriptor.description}`);
|
|
2023
|
+
console.log(`
|
|
2024
|
+
Input schema:
|
|
2025
|
+
${JSON.stringify(descriptor.inputSchema, null, 2)}`);
|
|
2026
|
+
}
|
|
2027
|
+
async function handleToolsCall(name, opts) {
|
|
2028
|
+
if (opts.args !== void 0 && opts.file !== void 0) {
|
|
2029
|
+
console.error("Error: --args and --file are mutually exclusive.");
|
|
2030
|
+
process.exit(1);
|
|
2031
|
+
}
|
|
2032
|
+
const raw = opts.file ? (() => {
|
|
2033
|
+
try {
|
|
2034
|
+
return readFileSync(path3.resolve(opts.file), "utf8");
|
|
2035
|
+
} catch (err) {
|
|
2036
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2037
|
+
console.error(`Could not read args file: ${msg}`);
|
|
2038
|
+
process.exit(1);
|
|
2039
|
+
}
|
|
2040
|
+
})() : opts.args ?? "{}";
|
|
2041
|
+
let parsedArgs;
|
|
2042
|
+
try {
|
|
2043
|
+
parsedArgs = JSON.parse(raw);
|
|
2044
|
+
} catch (err) {
|
|
2045
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2046
|
+
console.error(
|
|
2047
|
+
`Invalid JSON in ${opts.file ? "--file" : "--args"}: ${msg}`
|
|
2048
|
+
);
|
|
2049
|
+
process.exit(1);
|
|
2050
|
+
}
|
|
2051
|
+
const api = await getApi();
|
|
2052
|
+
const descriptors = await api.listTools(pluginVersion);
|
|
2053
|
+
findOrSuggestExit(name, descriptors);
|
|
2054
|
+
const result = await api.callBackend(name, parsedArgs);
|
|
2055
|
+
if (!result.ok) {
|
|
2056
|
+
console.error(result.error || "Request failed");
|
|
2057
|
+
process.exit(1);
|
|
2058
|
+
}
|
|
2059
|
+
console.log(JSON.stringify(result.result, null, 2));
|
|
2060
|
+
}
|
|
2061
|
+
|
|
1934
2062
|
// src/cli.ts
|
|
1935
2063
|
ensureNativeModules();
|
|
1936
2064
|
await initSentry();
|
|
@@ -1938,9 +2066,9 @@ if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.
|
|
|
1938
2066
|
printBanner();
|
|
1939
2067
|
}
|
|
1940
2068
|
var program = new Command().name("fml").description("FML agent tools for Claude Code").version(
|
|
1941
|
-
true ? "0.
|
|
2069
|
+
true ? "0.2.0+beaba19" : "dev"
|
|
1942
2070
|
);
|
|
1943
|
-
program.command("tools", { hidden: true }).description("List all available CLI commands (including hidden agent tools)").action(() => {
|
|
2071
|
+
var tools = program.command("tools", { hidden: true }).description("List all available CLI commands (including hidden agent tools)").action(() => {
|
|
1944
2072
|
function formatCmd(cmd, prefix = "") {
|
|
1945
2073
|
const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
|
|
1946
2074
|
const args = cmd.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
|
|
@@ -1959,6 +2087,9 @@ program.command("tools", { hidden: true }).description("List all available CLI c
|
|
|
1959
2087
|
});
|
|
1960
2088
|
console.log(lines.join("\n\n"));
|
|
1961
2089
|
});
|
|
2090
|
+
tools.command("list").description("List backend tools available via the dynamic catalog").option("--category <category>", "Filter by category").option("--json", "Output as JSON").action((opts) => handleToolsList(opts));
|
|
2091
|
+
tools.command("describe").description("Show description and input schema for a backend tool").argument("<name>", "Tool name (e.g. integration-github)").option("--json", "Output as JSON").action((name, opts) => handleToolsDescribe(name, opts));
|
|
2092
|
+
tools.command("call").description("Invoke a backend tool by name with JSON args").argument("<name>", "Tool name").option("--args <json>", "Args as a JSON string (defaults to {})").option("--file <path>", "Path to a JSON file containing args").action((name, opts) => handleToolsCall(name, opts));
|
|
1962
2093
|
program.command("install").description("Register plugin, hooks, and daemons").action(() => handleInstall());
|
|
1963
2094
|
program.command("uninstall").description("Remove fml plugin, hooks, and optionally all data").option(
|
|
1964
2095
|
"--target <target>",
|