@fml-inc/fml 0.0.28 → 0.0.30
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 +10 -0
- package/dist/{chunk-ECXIFGGY.js → chunk-RJOFOTNH.js} +3 -3
- package/dist/{chunk-USJO3G5V.js → chunk-TXQRKU2Y.js} +7 -3
- package/dist/chunk-TXQRKU2Y.js.map +1 -0
- package/dist/cli.js +80 -37
- package/dist/cli.js.map +1 -1
- package/dist/mcp/server.js +8 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/{token-store-Y35HF3FJ.js → token-store-NE7AM2PH.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-USJO3G5V.js.map +0 -1
- /package/dist/{chunk-ECXIFGGY.js.map → chunk-RJOFOTNH.js.map} +0 -0
- /package/dist/{token-store-Y35HF3FJ.js.map → token-store-NE7AM2PH.js.map} +0 -0
package/README.md
CHANGED
|
@@ -16,6 +16,16 @@ Then sign in:
|
|
|
16
16
|
fml login
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
## For LLM agents
|
|
20
|
+
|
|
21
|
+
Setting up FML from inside a coding-agent sandbox (Claude Code, Codex, Gemini CLI, etc.)? Paste this to your agent:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Follow the installation instructions here: https://github.com/fml-inc/observe/blob/main/docs/agent_installation_guide.md
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The guide covers: `npm install -g @fml-inc/fml` → `fml install` → `fml login --device` (the agent relays the verification URL to you, you paste the code back) → `fml doctor`. No browser, no `gh` CLI, no manual shell-profile edits needed.
|
|
28
|
+
|
|
19
29
|
## What it does
|
|
20
30
|
|
|
21
31
|
**Local observability** — Panopticon captures OpenTelemetry signals, hook events, session files, and API traffic from Claude Code, Gemini CLI, and Codex CLI into a local SQLite database.
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CONVEX_URL,
|
|
6
6
|
getSelectedOrg,
|
|
7
7
|
getValidToken
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TXQRKU2Y.js";
|
|
9
9
|
|
|
10
10
|
// src/native-check.ts
|
|
11
11
|
import { createRequire } from "module";
|
|
@@ -213,7 +213,7 @@ function createApiClient(token) {
|
|
|
213
213
|
async callBackend(toolName, args, opts) {
|
|
214
214
|
try {
|
|
215
215
|
if (isServiceToken) {
|
|
216
|
-
const { readTokens } = await import("./token-store-
|
|
216
|
+
const { readTokens } = await import("./token-store-NE7AM2PH.js");
|
|
217
217
|
const userExternalId = process.env.FML_USER_EXTERNAL_ID ?? readTokens()?.user.id;
|
|
218
218
|
const res = await fetch(`${getSiteUrl()}/api/tools/execute`, {
|
|
219
219
|
method: "POST",
|
|
@@ -316,4 +316,4 @@ export {
|
|
|
316
316
|
createApiClient,
|
|
317
317
|
getAuthenticatedClient
|
|
318
318
|
};
|
|
319
|
-
//# sourceMappingURL=chunk-
|
|
319
|
+
//# sourceMappingURL=chunk-RJOFOTNH.js.map
|
|
@@ -11,6 +11,10 @@ import fs from "fs";
|
|
|
11
11
|
import os from "os";
|
|
12
12
|
import path from "path";
|
|
13
13
|
var DEFAULT_PROD_URL = "https://trustworthy-chihuahua-382.convex.cloud";
|
|
14
|
+
var DEFAULT_SYNC_URL = DEFAULT_PROD_URL.replace(
|
|
15
|
+
".convex.cloud",
|
|
16
|
+
".convex.site"
|
|
17
|
+
);
|
|
14
18
|
var DEFAULT_TARGET_NAME = "fml";
|
|
15
19
|
var ENV_CONFIG_PATH = path.join(FML_DATA_DIR, "env.json");
|
|
16
20
|
function readEnvConfig() {
|
|
@@ -56,7 +60,7 @@ function getActiveEnv() {
|
|
|
56
60
|
return { name, convexUrl: null };
|
|
57
61
|
}
|
|
58
62
|
var env = getActiveEnv();
|
|
59
|
-
var CONVEX_URL = (process.env.FML_CONVEX_URL ?? env.convexUrl ??
|
|
63
|
+
var CONVEX_URL = (process.env.FML_CONVEX_URL ?? env.convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
|
|
60
64
|
var WORKOS_API_URL = "https://api.workos.com";
|
|
61
65
|
var WORKOS_AUTH_URL = "https://auth.fml.inc";
|
|
62
66
|
var AUTH_STORE_PATH = path.join(FML_DATA_DIR, `auth.${env.name}.json`);
|
|
@@ -290,7 +294,7 @@ async function refreshToken(stored) {
|
|
|
290
294
|
export {
|
|
291
295
|
initSentry,
|
|
292
296
|
Sentry,
|
|
293
|
-
|
|
297
|
+
DEFAULT_SYNC_URL,
|
|
294
298
|
DEFAULT_TARGET_NAME,
|
|
295
299
|
writeEnvConfig,
|
|
296
300
|
getActiveEnv,
|
|
@@ -305,4 +309,4 @@ export {
|
|
|
305
309
|
setSelectedOrg,
|
|
306
310
|
getValidToken
|
|
307
311
|
};
|
|
308
|
-
//# sourceMappingURL=chunk-
|
|
312
|
+
//# sourceMappingURL=chunk-TXQRKU2Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/token-store.ts","../src/config.ts","../src/sentry.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { AUTH_STORE_PATH, CONVEX_URL, WORKOS_API_URL } from \"../config.js\";\nimport { FML_DATA_DIR } from \"../dirs.js\";\nimport { Sentry } from \"../sentry.js\";\n\ninterface StoredAuth {\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix timestamp in ms\n user: {\n id: string;\n email: string;\n name: string;\n };\n orgSlug?: string;\n /** WorkOS client ID used to obtain these tokens (needed for refresh) */\n workosClientId?: string;\n /** Token type: \"oauth\" (default) for WorkOS JWT, \"service\" for device flow tokens */\n tokenType?: \"oauth\" | \"service\";\n}\n\nfunction ensureDir(): void {\n const dir = path.dirname(AUTH_STORE_PATH);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n}\n\nexport function readTokens(): StoredAuth | null {\n try {\n const data = fs.readFileSync(AUTH_STORE_PATH, \"utf-8\");\n return JSON.parse(data) as StoredAuth;\n } catch {\n return null;\n }\n}\n\nexport function writeTokens(auth: StoredAuth): void {\n ensureDir();\n fs.writeFileSync(AUTH_STORE_PATH, JSON.stringify(auth, null, 2), {\n mode: 0o600,\n });\n}\n\nexport function getSelectedOrg(): string | null {\n return readTokens()?.orgSlug ?? null;\n}\n\nexport function setSelectedOrg(orgSlug: string): void {\n const stored = readTokens();\n if (!stored) return;\n writeTokens({ ...stored, orgSlug });\n}\n\n// ── Service Token Refresh (sandbox fml_srt_* → fml_st_*) ────────────────────\n\ninterface ServiceTokenCache {\n accessToken: string;\n expiresAt: number;\n}\n\nlet serviceTokenCache: ServiceTokenCache | null = null;\nlet serviceRefreshPromise: Promise<string | null> | null = null;\n\n/** File where the current access token is written for panopticon's tokenCommand */\nconst SERVICE_ACCESS_TOKEN_PATH = path.join(FML_DATA_DIR, \"access_token\");\n\nfunction getSiteUrl(): string {\n const explicit = process.env.CONVEX_SITE_URL;\n if (explicit) return explicit.replace(/\\/$/, \"\");\n return CONVEX_URL.replace(\".convex.cloud\", \".convex.site\");\n}\n\nasync function refreshServiceToken(\n refreshToken: string,\n): Promise<string | null> {\n try {\n const response = await fetch(`${getSiteUrl()}/api/tokens/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${refreshToken}`,\n },\n });\n\n if (!response.ok) {\n console.error(\n `[fml] Service token refresh failed: HTTP ${response.status}`,\n );\n return null;\n }\n\n const data = (await response.json()) as {\n ok: boolean;\n accessToken: string;\n expiresAt: number;\n error?: string;\n };\n\n if (!data.ok || !data.accessToken) {\n console.error(`[fml] Service token refresh error: ${data.error}`);\n return null;\n }\n\n serviceTokenCache = {\n accessToken: data.accessToken,\n expiresAt: data.expiresAt,\n };\n\n // Write to file for panopticon's tokenCommand\n try {\n const dir = path.dirname(SERVICE_ACCESS_TOKEN_PATH);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n fs.writeFileSync(SERVICE_ACCESS_TOKEN_PATH, data.accessToken, {\n mode: 0o600,\n });\n } catch {\n // Non-fatal — in-memory cache still works for the plugin itself\n }\n\n return data.accessToken;\n } catch (err: unknown) {\n Sentry.captureException(err);\n console.error(`[fml] Service token refresh error: ${err}`);\n return null;\n }\n}\n\n// ── Token Resolution ────────────────────────────────────────────────────────\n\n// Mutex to prevent concurrent token refresh races\nlet refreshPromise: Promise<string | null> | null = null;\n\n/**\n * Get a valid access token, refreshing if expired.\n *\n * Three paths:\n * 1. FML_TOKEN=fml_srt_* → exchange refresh token for short-lived access token\n * 2. FML_TOKEN=fml_st_* → use directly (legacy/CI)\n * 3. No FML_TOKEN → OAuth flow (interactive user)\n */\nexport async function getValidToken(): Promise<string | null> {\n const envToken = process.env.FML_TOKEN;\n\n // Service refresh token → exchange for short-lived access token\n if (envToken?.startsWith(\"fml_srt_\")) {\n if (\n serviceTokenCache &&\n serviceTokenCache.expiresAt > Date.now() + 60_000\n ) {\n return serviceTokenCache.accessToken;\n }\n\n if (serviceRefreshPromise) return serviceRefreshPromise;\n serviceRefreshPromise = refreshServiceToken(envToken);\n try {\n return await serviceRefreshPromise;\n } finally {\n serviceRefreshPromise = null;\n }\n }\n\n // Static service token (legacy) or plain env override\n if (envToken) return envToken;\n\n // Stored token path (OAuth or device flow)\n const stored = readTokens();\n if (!stored) {\n console.error(\"[fml] Auth: no stored tokens\");\n return null;\n }\n\n // If token expires in more than 60 seconds, use it\n if (stored.expiresAt > Date.now() + 60_000) {\n return stored.accessToken;\n }\n\n // Device flow tokens: refresh via /api/tokens/refresh (same as sandbox tokens)\n if (stored.tokenType === \"service\") {\n if (serviceRefreshPromise) return serviceRefreshPromise;\n serviceRefreshPromise = (async () => {\n const newAccessToken = await refreshServiceToken(stored.refreshToken);\n if (newAccessToken && serviceTokenCache) {\n writeTokens({\n ...stored,\n accessToken: newAccessToken,\n expiresAt: serviceTokenCache.expiresAt,\n });\n }\n return newAccessToken;\n })();\n try {\n return await serviceRefreshPromise;\n } finally {\n serviceRefreshPromise = null;\n }\n }\n\n // OAuth tokens: refresh via WorkOS\n if (refreshPromise) return refreshPromise;\n\n refreshPromise = refreshToken(stored);\n try {\n return await refreshPromise;\n } finally {\n refreshPromise = null;\n }\n}\n\nasync function refreshToken(stored: StoredAuth): Promise<string | null> {\n if (!stored.workosClientId) {\n console.error(\n \"[fml] Auth: token expired, missing workosClientId — run `fml login`\",\n );\n return null;\n }\n\n try {\n const response = await fetch(\n `${WORKOS_API_URL}/user_management/authenticate`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n client_id: stored.workosClientId,\n refresh_token: stored.refreshToken,\n }),\n },\n );\n\n if (!response.ok) {\n console.error(\n `[fml] Auth: refresh failed (HTTP ${response.status}) — token preserved`,\n );\n return null;\n }\n\n const data = (await response.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n user: {\n id: string;\n email: string;\n first_name?: string;\n last_name?: string;\n };\n };\n\n const userName = [data.user.first_name, data.user.last_name]\n .filter(Boolean)\n .join(\" \");\n\n const refreshed: StoredAuth = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Date.now() + (data.expires_in ?? 300) * 1000,\n user: {\n id: data.user.id,\n email: data.user.email,\n name: userName || stored.user.name,\n },\n orgSlug: stored.orgSlug,\n workosClientId: stored.workosClientId,\n };\n\n writeTokens(refreshed);\n return refreshed.accessToken;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(\n `[fml] Auth: refresh failed (network error: ${msg}) — token preserved`,\n );\n Sentry.captureException(err);\n return null;\n }\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { FML_DATA_DIR } from \"./dirs.js\";\n\n// ── Default production deployment ───────────────────────────────────────────\n\nexport const DEFAULT_PROD_URL =\n \"https://trustworthy-chihuahua-382.convex.cloud\";\n\n/** Convex site URL for sync/HTTP actions — derived from DEFAULT_PROD_URL. */\nexport const DEFAULT_SYNC_URL = DEFAULT_PROD_URL.replace(\n \".convex.cloud\",\n \".convex.site\",\n);\n\n/** Default sync target name created by `fml install` */\nexport const DEFAULT_TARGET_NAME = \"fml\";\n\n// ── Persistent env selection ────────────────────────────────────────────────\n\ninterface EnvConfig {\n /** Name of the active sync target */\n active: string;\n}\n\nconst ENV_CONFIG_PATH = path.join(FML_DATA_DIR, \"env.json\");\n\nfunction readEnvConfig(): EnvConfig {\n try {\n const raw = fs.readFileSync(ENV_CONFIG_PATH, \"utf-8\");\n return JSON.parse(raw) as EnvConfig;\n } catch {\n return { active: DEFAULT_TARGET_NAME };\n }\n}\n\nexport function writeEnvConfig(config: EnvConfig): void {\n const dir = path.dirname(ENV_CONFIG_PATH);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n fs.writeFileSync(ENV_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\\n`, {\n mode: 0o600,\n });\n}\n\n/**\n * Resolve the active environment by looking up the sync target.\n * Derives the .convex.cloud URL from the target's .convex.site URL.\n * Returns null convexUrl if the target is not found (panopticon not installed).\n */\nexport function getActiveEnv(): { name: string; convexUrl: string | null } {\n const config = readEnvConfig();\n const name = config.active;\n\n try {\n const panoDataDir =\n process.env.PANOPTICON_DATA_DIR ??\n (process.platform === \"darwin\"\n ? path.join(\n os.homedir(),\n \"Library\",\n \"Application Support\",\n \"panopticon\",\n )\n : process.platform === \"win32\"\n ? path.join(\n process.env.APPDATA ??\n path.join(os.homedir(), \"AppData\", \"Roaming\"),\n \"panopticon\",\n )\n : path.join(os.homedir(), \".local\", \"share\", \"panopticon\"));\n const raw = fs.readFileSync(path.join(panoDataDir, \"config.json\"), \"utf-8\");\n const panoConf = JSON.parse(raw) as {\n sync?: { targets?: Array<{ name: string; url: string }> };\n };\n const target = panoConf.sync?.targets?.find((t) => t.name === name);\n if (target) {\n const convexUrl = target.url.replace(\".convex.site\", \".convex.cloud\");\n return { name, convexUrl };\n }\n } catch {\n // Panopticon config not available\n }\n\n return { name, convexUrl: null };\n}\n\n/**\n * Require a resolved Convex URL. Exits with an error if the sync target\n * is not configured (panopticon not installed).\n */\nexport function requireConvexUrl(): string {\n const { name, convexUrl } = getActiveEnv();\n if (!convexUrl) {\n console.error(\n `Sync target \"${name}\" not found. Run \\`fml install\\` or \\`fml sync setup\\`.`,\n );\n process.exit(1);\n }\n return convexUrl;\n}\n\n// ── Exports ─────────────────────────────────────────────────────────────────\n\nconst env = getActiveEnv();\n\n/**\n * Convex deployment URL (switches with `fml env`).\n *\n * Resolution order:\n * 1. `FML_CONVEX_URL` env var (dev/preview overrides)\n * 2. Active panopticon sync target\n * 3. `DEFAULT_PROD_URL` — lets a fresh `npm install -g` log in against\n * prod before `fml install` has had a chance to seed a sync target.\n */\nexport const CONVEX_URL: string = (\n process.env.FML_CONVEX_URL ??\n env.convexUrl ??\n DEFAULT_PROD_URL\n).replace(/\\/$/, \"\");\n\n/** WorkOS API base URL */\nexport const WORKOS_API_URL = \"https://api.workos.com\";\n\n/** WorkOS authorization base URL */\nexport const WORKOS_AUTH_URL = \"https://auth.fml.inc\";\n\n/** Path to auth token store (per-environment) */\nexport const AUTH_STORE_PATH = path.join(FML_DATA_DIR, `auth.${env.name}.json`);\n\n/** Convex site URL (HTTP actions) — derived from CONVEX_URL */\nexport function getSiteUrl(): string {\n return CONVEX_URL.replace(\".convex.cloud\", \".convex.site\").replace(\n /:\\d+$/,\n \"\",\n );\n}\n","declare const __SENTRY_DSN__: string;\n\nlet initialized = false;\nlet sentry: typeof import(\"@sentry/core\") | null = null;\n\nasync function loadSentry(): Promise<typeof import(\"@sentry/core\") | null> {\n if (sentry) return sentry;\n try {\n sentry = await import(\"@sentry/core\");\n return sentry;\n } catch {\n return null;\n }\n}\n\nexport async function initSentry(): Promise<void> {\n if (initialized) return;\n initialized = true;\n\n const mod = await loadSentry();\n if (!mod) return;\n\n const client = new mod.ServerRuntimeClient({\n dsn: __SENTRY_DSN__,\n tracesSampleRate: 0,\n integrations: [],\n stackParser: mod.createStackParser(mod.nodeStackLineParser()),\n transport: (options) =>\n mod.createTransport(options, async (request) => {\n const res = await fetch(options.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-sentry-envelope\" },\n body: request.body as BodyInit,\n });\n return { statusCode: res.status };\n }),\n });\n\n mod.setCurrentClient(client);\n client.init();\n}\n\nexport const Sentry = {\n captureException: (err: unknown) => sentry?.captureException(err),\n captureMessage: (msg: string) => sentry?.captureMessage(msg),\n} as const;\n"],"mappings":"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","names":["fs","path","path","fs","getSiteUrl","refreshToken"]}
|
package/dist/cli.js
CHANGED
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
createApiClient,
|
|
9
9
|
ensureNativeModules,
|
|
10
10
|
getAuthenticatedClient
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-RJOFOTNH.js";
|
|
12
12
|
import "./chunk-UALBZA5X.js";
|
|
13
13
|
import {
|
|
14
14
|
AUTH_STORE_PATH,
|
|
15
15
|
CONVEX_URL,
|
|
16
|
-
|
|
16
|
+
DEFAULT_SYNC_URL,
|
|
17
17
|
DEFAULT_TARGET_NAME,
|
|
18
18
|
Sentry,
|
|
19
19
|
WORKOS_API_URL,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
setSelectedOrg,
|
|
28
28
|
writeEnvConfig,
|
|
29
29
|
writeTokens
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-TXQRKU2Y.js";
|
|
31
31
|
import {
|
|
32
32
|
handleStart,
|
|
33
33
|
handleStop,
|
|
@@ -2064,6 +2064,11 @@ function resolveGitHubToken() {
|
|
|
2064
2064
|
return null;
|
|
2065
2065
|
}
|
|
2066
2066
|
}
|
|
2067
|
+
function resolveSyncTokenCommand() {
|
|
2068
|
+
if (resolveGitHubToken()) return "gh auth token";
|
|
2069
|
+
if (readTokens()) return "fml sync-token";
|
|
2070
|
+
return void 0;
|
|
2071
|
+
}
|
|
2067
2072
|
|
|
2068
2073
|
// src/commands/install.ts
|
|
2069
2074
|
var CLAUDE_DIR = path2.join(os2.homedir(), ".claude");
|
|
@@ -2225,24 +2230,23 @@ async function handleInstall() {
|
|
|
2225
2230
|
}
|
|
2226
2231
|
console.log();
|
|
2227
2232
|
console.log("[5/5] Configuring sync target...");
|
|
2228
|
-
const prodSyncUrl = DEFAULT_PROD_URL.replace(".cloud", ".site");
|
|
2229
2233
|
const existingTargets = listTargets();
|
|
2230
|
-
const existingProd = existingTargets.find((t) => t.url ===
|
|
2234
|
+
const existingProd = existingTargets.find((t) => t.url === DEFAULT_SYNC_URL);
|
|
2231
2235
|
if (existingProd) {
|
|
2232
2236
|
console.log(` Production target already configured`);
|
|
2233
2237
|
} else {
|
|
2234
|
-
const
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
});
|
|
2241
|
-
|
|
2242
|
-
console.log(
|
|
2238
|
+
const tokenCommand = resolveSyncTokenCommand();
|
|
2239
|
+
addTarget({
|
|
2240
|
+
name: DEFAULT_TARGET_NAME,
|
|
2241
|
+
url: DEFAULT_SYNC_URL,
|
|
2242
|
+
tokenCommand
|
|
2243
|
+
});
|
|
2244
|
+
console.log(` Target "${DEFAULT_TARGET_NAME}": ${DEFAULT_SYNC_URL}`);
|
|
2245
|
+
if (tokenCommand) {
|
|
2246
|
+
console.log(` Auth: ${tokenCommand}`);
|
|
2243
2247
|
} else {
|
|
2244
2248
|
console.log(
|
|
2245
|
-
"
|
|
2249
|
+
" Auth: not configured \u2014 run `fml login` to enable sync."
|
|
2246
2250
|
);
|
|
2247
2251
|
}
|
|
2248
2252
|
}
|
|
@@ -2416,7 +2420,7 @@ function handleUninstall(opts) {
|
|
|
2416
2420
|
// src/commands/update.ts
|
|
2417
2421
|
import { execSync as execSync2 } from "child_process";
|
|
2418
2422
|
async function handleUpdate() {
|
|
2419
|
-
const currentVersion = true ? "0.0.
|
|
2423
|
+
const currentVersion = true ? "0.0.30+90b4fd3" : "unknown";
|
|
2420
2424
|
console.log(`Current: ${currentVersion}`);
|
|
2421
2425
|
console.log("Updating via npm...\n");
|
|
2422
2426
|
try {
|
|
@@ -2701,12 +2705,6 @@ async function linkGitHubIdentity() {
|
|
|
2701
2705
|
if (fmlToken.startsWith("fml_st_")) {
|
|
2702
2706
|
return;
|
|
2703
2707
|
}
|
|
2704
|
-
if (!CONVEX_URL) {
|
|
2705
|
-
console.warn(
|
|
2706
|
-
"[fml] No Convex URL configured \u2014 skipping identity link (run fml install first)"
|
|
2707
|
-
);
|
|
2708
|
-
return;
|
|
2709
|
-
}
|
|
2710
2708
|
const { ConvexHttpClient } = await import("convex/browser");
|
|
2711
2709
|
const client = new ConvexHttpClient(CONVEX_URL);
|
|
2712
2710
|
client.setAuth(fmlToken);
|
|
@@ -2722,6 +2720,33 @@ async function linkGitHubIdentity() {
|
|
|
2722
2720
|
console.warn(`[fml] Failed to link GitHub identity: ${msg}`);
|
|
2723
2721
|
}
|
|
2724
2722
|
}
|
|
2723
|
+
function upgradeSyncTargetAfterLogin() {
|
|
2724
|
+
try {
|
|
2725
|
+
const config3 = loadSyncConfig();
|
|
2726
|
+
const existing = config3.targets.find((t) => t.name === DEFAULT_TARGET_NAME);
|
|
2727
|
+
if (!existing) {
|
|
2728
|
+
addTarget({
|
|
2729
|
+
name: DEFAULT_TARGET_NAME,
|
|
2730
|
+
url: DEFAULT_SYNC_URL,
|
|
2731
|
+
tokenCommand: "fml sync-token"
|
|
2732
|
+
});
|
|
2733
|
+
console.log(
|
|
2734
|
+
`Sync target "${DEFAULT_TARGET_NAME}" configured with fml sync-token.`
|
|
2735
|
+
);
|
|
2736
|
+
console.log("Restart panopticon to apply: fml stop && fml start");
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (!existing.tokenCommand && !existing.token) {
|
|
2740
|
+
existing.tokenCommand = "fml sync-token";
|
|
2741
|
+
saveSyncConfig(config3);
|
|
2742
|
+
console.log(`Sync target "${existing.name}" now using fml sync-token.`);
|
|
2743
|
+
console.log("Restart panopticon to apply: fml stop && fml start");
|
|
2744
|
+
}
|
|
2745
|
+
} catch (err) {
|
|
2746
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2747
|
+
console.warn(`[fml] Could not update sync target: ${msg}`);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2725
2750
|
async function selectOrg() {
|
|
2726
2751
|
try {
|
|
2727
2752
|
const token = await getValidToken();
|
|
@@ -2740,13 +2765,14 @@ async function handleLogin(opts) {
|
|
|
2740
2765
|
const { name: envName } = getActiveEnv();
|
|
2741
2766
|
const existingToken = await getValidToken();
|
|
2742
2767
|
if (existingToken) {
|
|
2743
|
-
const { readTokens: readTokens2 } = await import("./token-store-
|
|
2768
|
+
const { readTokens: readTokens2 } = await import("./token-store-NE7AM2PH.js");
|
|
2744
2769
|
const stored = readTokens2();
|
|
2745
2770
|
console.log(
|
|
2746
2771
|
`Already logged in as ${stored?.user.name} (${stored?.user.email}) on ${envName}.`
|
|
2747
2772
|
);
|
|
2748
2773
|
await linkGitHubIdentity();
|
|
2749
2774
|
await selectOrg();
|
|
2775
|
+
upgradeSyncTargetAfterLogin();
|
|
2750
2776
|
console.log("You're all set! Restart Claude Code to use FML tools.");
|
|
2751
2777
|
process.exit(0);
|
|
2752
2778
|
}
|
|
@@ -2763,6 +2789,7 @@ async function handleLogin(opts) {
|
|
|
2763
2789
|
Logged in as ${result.name} (${result.email})`);
|
|
2764
2790
|
await linkGitHubIdentity();
|
|
2765
2791
|
await selectOrg();
|
|
2792
|
+
upgradeSyncTargetAfterLogin();
|
|
2766
2793
|
console.log("You're all set! Restart Claude Code to use FML tools.");
|
|
2767
2794
|
process.exit(0);
|
|
2768
2795
|
} catch (err) {
|
|
@@ -2873,26 +2900,24 @@ async function handleStatus() {
|
|
|
2873
2900
|
}
|
|
2874
2901
|
|
|
2875
2902
|
// src/commands/sync.ts
|
|
2876
|
-
var FML_SYNC_URL = CONVEX_URL.replace(".cloud", ".site");
|
|
2877
2903
|
async function handleSyncSetup() {
|
|
2878
2904
|
console.log("FML Sync Setup\n");
|
|
2879
|
-
const
|
|
2880
|
-
if (!
|
|
2905
|
+
const tokenCommand = resolveSyncTokenCommand();
|
|
2906
|
+
if (!tokenCommand) {
|
|
2881
2907
|
console.error(
|
|
2882
|
-
"No
|
|
2908
|
+
"No credential available. Run `fml login` first, or set PANOPTICON_GITHUB_TOKEN / install gh CLI."
|
|
2883
2909
|
);
|
|
2884
2910
|
process.exit(1);
|
|
2885
2911
|
}
|
|
2886
|
-
|
|
2912
|
+
removeTarget(DEFAULT_TARGET_NAME);
|
|
2887
2913
|
addTarget({
|
|
2888
2914
|
name: DEFAULT_TARGET_NAME,
|
|
2889
|
-
url:
|
|
2890
|
-
tokenCommand
|
|
2915
|
+
url: DEFAULT_SYNC_URL,
|
|
2916
|
+
tokenCommand
|
|
2891
2917
|
});
|
|
2892
|
-
console.log(`
|
|
2893
|
-
|
|
2894
|
-
console.log(`
|
|
2895
|
-
console.log(` Auth: gh auth token (refreshed every 5 min)`);
|
|
2918
|
+
console.log(`Sync target "${DEFAULT_TARGET_NAME}" configured:`);
|
|
2919
|
+
console.log(` URL: ${DEFAULT_SYNC_URL}`);
|
|
2920
|
+
console.log(` Auth: ${tokenCommand}`);
|
|
2896
2921
|
console.log(`
|
|
2897
2922
|
Restart panopticon to activate: fml stop && fml start`);
|
|
2898
2923
|
}
|
|
@@ -3003,6 +3028,20 @@ async function handleSyncReset(targetName) {
|
|
|
3003
3028
|
);
|
|
3004
3029
|
}
|
|
3005
3030
|
|
|
3031
|
+
// src/commands/sync-token.ts
|
|
3032
|
+
import { once } from "events";
|
|
3033
|
+
async function handleSyncToken() {
|
|
3034
|
+
const token = await getValidToken();
|
|
3035
|
+
if (!token) {
|
|
3036
|
+
console.error("fml: not logged in. Run `fml login` to enable sync.");
|
|
3037
|
+
process.exit(1);
|
|
3038
|
+
}
|
|
3039
|
+
if (!process.stdout.write(token)) {
|
|
3040
|
+
await once(process.stdout, "drain");
|
|
3041
|
+
}
|
|
3042
|
+
process.exit(0);
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3006
3045
|
// src/commands/env.ts
|
|
3007
3046
|
function handleEnvShow() {
|
|
3008
3047
|
const { name, convexUrl } = getActiveEnv();
|
|
@@ -3084,7 +3123,8 @@ var GENERIC_PROVIDERS = [
|
|
|
3084
3123
|
"github",
|
|
3085
3124
|
"linear",
|
|
3086
3125
|
"notion",
|
|
3087
|
-
"freshdesk"
|
|
3126
|
+
"freshdesk",
|
|
3127
|
+
"stripe"
|
|
3088
3128
|
];
|
|
3089
3129
|
var POSTHOG_ENDPOINTS = {
|
|
3090
3130
|
insights: "query-posthog-insights",
|
|
@@ -3580,7 +3620,7 @@ if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.
|
|
|
3580
3620
|
printBanner();
|
|
3581
3621
|
}
|
|
3582
3622
|
var program = new Command().name("fml").description("FML agent tools for Claude Code").version(
|
|
3583
|
-
true ? "0.0.
|
|
3623
|
+
true ? "0.0.30+90b4fd3" : "dev"
|
|
3584
3624
|
);
|
|
3585
3625
|
program.command("tools", { hidden: true }).description("List all available CLI commands (including hidden agent tools)").action(() => {
|
|
3586
3626
|
function formatCmd(cmd, prefix = "") {
|
|
@@ -3631,6 +3671,9 @@ sync.command("edit").description("Edit a sync target").argument("<name>", "Targe
|
|
|
3631
3671
|
);
|
|
3632
3672
|
sync.command("status").description("Show sync config and watermarks").action(() => handleSyncStatus());
|
|
3633
3673
|
sync.command("reset").description("Reset sync watermarks").argument("[name]", "Target name (resets all if omitted)").action((name) => handleSyncReset(name));
|
|
3674
|
+
program.command("sync-token", { hidden: true }).description(
|
|
3675
|
+
"Print a refreshed FML access token (for panopticon tokenCommand)"
|
|
3676
|
+
).action(() => handleSyncToken());
|
|
3634
3677
|
program.command("activity").description("Activity summary \u2014 sessions, prompts, tools, costs").option("--since <duration>", 'Time window, e.g. "24h", "7d"').action((opts) => handleActivity(opts));
|
|
3635
3678
|
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));
|
|
3636
3679
|
program.command("timeline").description("Get events for a session").argument("<session-id>", "Session ID").option("--limit <n>", "Max events to return").option("--offset <n>", "Events to skip").action((sessionId, opts) => handleTimeline(sessionId, opts));
|
|
@@ -3638,7 +3681,7 @@ program.command("spending").description("AI token usage and cost breakdown").opt
|
|
|
3638
3681
|
program.command("search").description("Search across all sessions").argument("<query>", "Text to search for").option("--since <duration>", 'Time filter, e.g. "24h", "7d"').option("--limit <n>", "Max results").action((query, opts) => handleSearch(query, opts));
|
|
3639
3682
|
program.command("query", { hidden: true }).description("Query a connected integration").argument(
|
|
3640
3683
|
"<provider>",
|
|
3641
|
-
"Provider name (sentry, slack, github, linear, notion, freshdesk, posthog, amplitude, meta-ads)"
|
|
3684
|
+
"Provider name (sentry, slack, github, linear, notion, freshdesk, stripe, posthog, amplitude, meta-ads)"
|
|
3642
3685
|
).argument("<endpoint>", "API endpoint or subcommand").option("--method <method>", "HTTP method", "GET").option("--body <json>", "Request body as JSON string").option("--project-id <id>", "Project ID override").action((provider, endpoint, opts) => handleQuery(provider, endpoint, opts));
|
|
3643
3686
|
var automation = program.command("automation", { hidden: true }).description("Manage automations");
|
|
3644
3687
|
automation.command("list").description("List all automations").action(() => handleAutomationList());
|