@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fml",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "FML agent tools for Claude Code",
5
5
  "mcpServers": {
6
6
  "fml": {
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-USJO3G5V.js";
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-Y35HF3FJ.js");
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-ECXIFGGY.js.map
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 ?? "").replace(/\/$/, "");
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
- DEFAULT_PROD_URL,
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-USJO3G5V.js.map
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-ECXIFGGY.js";
11
+ } from "./chunk-RJOFOTNH.js";
12
12
  import "./chunk-UALBZA5X.js";
13
13
  import {
14
14
  AUTH_STORE_PATH,
15
15
  CONVEX_URL,
16
- DEFAULT_PROD_URL,
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-USJO3G5V.js";
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 === prodSyncUrl);
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 ghToken = resolveGitHubToken();
2235
- if (ghToken) {
2236
- addTarget({
2237
- name: DEFAULT_TARGET_NAME,
2238
- url: prodSyncUrl,
2239
- tokenCommand: "gh auth token"
2240
- });
2241
- console.log(` Target "${DEFAULT_TARGET_NAME}": ${prodSyncUrl}`);
2242
- console.log(" Auth: gh auth token");
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
- " Skipped (no GitHub token). Run `fml sync setup` later."
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.28+44c986f" : "unknown";
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-Y35HF3FJ.js");
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 token = resolveGitHubToken();
2880
- if (!token) {
2905
+ const tokenCommand = resolveSyncTokenCommand();
2906
+ if (!tokenCommand) {
2881
2907
  console.error(
2882
- "No GitHub token found. Set PANOPTICON_GITHUB_TOKEN or install gh CLI."
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
- console.log("GitHub token: found");
2912
+ removeTarget(DEFAULT_TARGET_NAME);
2887
2913
  addTarget({
2888
2914
  name: DEFAULT_TARGET_NAME,
2889
- url: FML_SYNC_URL,
2890
- tokenCommand: "gh auth token"
2915
+ url: DEFAULT_SYNC_URL,
2916
+ tokenCommand
2891
2917
  });
2892
- console.log(`
2893
- Sync target "${DEFAULT_TARGET_NAME}" configured:`);
2894
- console.log(` URL: ${FML_SYNC_URL}`);
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.28+44c986f" : "dev"
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());