@fml-inc/fml 0.6.1 → 0.7.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/dist/cli.js CHANGED
@@ -2,13 +2,14 @@
2
2
  import {
3
3
  createFmlClient,
4
4
  getAuthenticatedClient
5
- } from "./chunk-2H6D7RUN.js";
5
+ } from "./chunk-PTUZMKSN.js";
6
6
  import {
7
7
  CONVEX_URL,
8
8
  DEFAULT_SYNC_URL,
9
9
  DEFAULT_TARGET_NAME,
10
10
  FML_DATA_DIR,
11
11
  FML_LOG_DIR,
12
+ SERVICE_TOKEN_LOGIN_USER_ID,
12
13
  Sentry,
13
14
  WORKOS_API_URL,
14
15
  WORKOS_AUTH_URL,
@@ -22,16 +23,19 @@ import {
22
23
  isValidEnvName,
23
24
  readTokens,
24
25
  setSelectedOrg,
26
+ storeServiceRefreshToken,
25
27
  writeEnvConfig,
26
28
  writeTokens
27
- } from "./chunk-VDW5L7OB.js";
29
+ } from "./chunk-VV4JHH4H.js";
28
30
  import {
29
- handleStart,
30
- handleStop,
31
+ handleFmlStart,
32
+ handleFmlStop,
33
+ handlePanopticonStart,
34
+ handlePanopticonStop,
31
35
  handleSyncStart,
32
36
  handleSyncStop,
33
37
  parsePanopticonRunning
34
- } from "./chunk-ZFUJ5ZQP.js";
38
+ } from "./chunk-S4TJNLEN.js";
35
39
  import {
36
40
  execBinSync,
37
41
  panopticonExec,
@@ -88,7 +92,7 @@ async function handleDoctor(opts) {
88
92
  token = await getValidToken();
89
93
  if (token) {
90
94
  const stored = readTokens();
91
- const detail = stored?.user?.email ? `Logged in as ${stored.user.email}` : "Token present";
95
+ const detail = stored?.tokenType === "service" && stored.user.id === SERVICE_TOKEN_LOGIN_USER_ID ? "Service token configured" : stored?.user?.email ? `Logged in as ${stored.user.email}` : "Token present";
92
96
  pushAndReport({ label: "Auth", status: "ok", detail }, spinner);
93
97
  } else {
94
98
  pushAndReport(
@@ -705,7 +709,7 @@ function handleUninstall(opts) {
705
709
  // src/commands/update.ts
706
710
  import { execSync as execSync2 } from "child_process";
707
711
  async function handleUpdate() {
708
- const currentVersion = true ? "0.6.1+569d0d2" : "unknown";
712
+ const currentVersion = true ? "0.7.0+8fe6800" : "unknown";
709
713
  console.log(`Current: ${currentVersion}`);
710
714
  console.log("Updating via npm...\n");
711
715
  try {
@@ -1022,7 +1026,60 @@ async function linkGitHubIdentity() {
1022
1026
  console.warn(`[fml] Failed to link GitHub identity: ${msg}`);
1023
1027
  }
1024
1028
  }
1025
- function upgradeSyncTargetAfterLogin() {
1029
+ function promptHidden(question) {
1030
+ if (!process.stdin.isTTY) {
1031
+ return new Promise((resolve, reject) => {
1032
+ let input = "";
1033
+ process.stdin.setEncoding("utf8");
1034
+ process.stdin.on("data", (chunk) => {
1035
+ input += chunk;
1036
+ });
1037
+ process.stdin.once("end", () => resolve(input.trim()));
1038
+ process.stdin.once("error", reject);
1039
+ });
1040
+ }
1041
+ return new Promise((resolve, reject) => {
1042
+ const stdin = process.stdin;
1043
+ const wasRaw = stdin.isRaw === true;
1044
+ let value = "";
1045
+ let settled = false;
1046
+ const cleanup = () => {
1047
+ stdin.off("data", onData);
1048
+ stdin.setRawMode(wasRaw);
1049
+ stdin.pause();
1050
+ process.stderr.write("\n");
1051
+ };
1052
+ const finish = (fn) => {
1053
+ if (settled) return;
1054
+ settled = true;
1055
+ cleanup();
1056
+ fn();
1057
+ };
1058
+ const onData = (chunk) => {
1059
+ const text = chunk.toString("utf8");
1060
+ for (const char of text) {
1061
+ if (char === "") {
1062
+ finish(() => reject(new Error("Canceled")));
1063
+ return;
1064
+ }
1065
+ if (char === "\r" || char === "\n") {
1066
+ finish(() => resolve(value.trim()));
1067
+ return;
1068
+ }
1069
+ if (char === "\x7F" || char === "\b") {
1070
+ value = value.slice(0, -1);
1071
+ continue;
1072
+ }
1073
+ value += char;
1074
+ }
1075
+ };
1076
+ process.stderr.write(question);
1077
+ stdin.setRawMode(true);
1078
+ stdin.resume();
1079
+ stdin.on("data", onData);
1080
+ });
1081
+ }
1082
+ function upgradeSyncTargetAfterLogin(opts) {
1026
1083
  try {
1027
1084
  const { name: envName } = getActiveEnv();
1028
1085
  if (!isValidEnvName(envName)) {
@@ -1044,11 +1101,12 @@ function upgradeSyncTargetAfterLogin() {
1044
1101
  console.log("Restart panopticon to apply: fml stop && fml start");
1045
1102
  return;
1046
1103
  }
1047
- if (existing.token) return;
1104
+ if (existing.token && !opts?.forceTokenCommand) return;
1048
1105
  if (existing.tokenCommand === pinnedCmd) return;
1049
- if (existing.tokenCommand && existing.tokenCommand !== "fml sync-token") {
1106
+ if (!opts?.forceTokenCommand && existing.tokenCommand && existing.tokenCommand !== "fml sync-token") {
1050
1107
  return;
1051
1108
  }
1109
+ delete existing.token;
1052
1110
  existing.tokenCommand = pinnedCmd;
1053
1111
  saveSyncConfig(config2);
1054
1112
  console.log(`Sync target "${envName}" now using ${pinnedCmd}.`);
@@ -1058,32 +1116,75 @@ function upgradeSyncTargetAfterLogin() {
1058
1116
  console.warn(`[fml] Could not update sync target: ${msg}`);
1059
1117
  }
1060
1118
  }
1061
- async function selectOrg() {
1119
+ async function selectOrg(envName) {
1062
1120
  try {
1063
- const token = await getValidToken();
1121
+ const token = await getValidToken({ env: envName });
1064
1122
  if (!token) return;
1065
1123
  const api = createFmlClient(token);
1066
1124
  const orgs = await api.queryOrgs();
1067
1125
  if (orgs.length === 0) return;
1068
- const current = getSelectedOrg();
1126
+ const current = getSelectedOrg(envName);
1069
1127
  const org = current && orgs.find((o) => (o.slug ?? o.name) === current) || orgs[0];
1070
1128
  const slug = org.slug ?? org.name;
1071
- setSelectedOrg(slug);
1129
+ setSelectedOrg(slug, envName);
1072
1130
  console.log(`Selected org: ${org.name} (${slug})`);
1073
1131
  } catch {
1074
1132
  }
1075
1133
  }
1134
+ async function runServiceTokenLogin(refreshToken) {
1135
+ const { name: envName } = getActiveEnv();
1136
+ if (!isValidEnvName(envName)) {
1137
+ throw new Error(
1138
+ `Cannot store service-token login for unsafe env name "${envName}".`
1139
+ );
1140
+ }
1141
+ console.log(`Signing in to FML with a service token (${envName})...`);
1142
+ const token = refreshToken ?? await promptHidden("Paste FML service refresh token (fml_srt_*): ");
1143
+ const ok = await storeServiceRefreshToken(token, { env: envName });
1144
+ if (!ok) {
1145
+ throw new Error("Service token login failed.");
1146
+ }
1147
+ await selectOrg(envName);
1148
+ upgradeSyncTargetAfterLogin({ forceTokenCommand: true });
1149
+ console.log("Logged in with FML service token.");
1150
+ console.log("Queries and sync will use the stored service token.");
1151
+ console.log("You're all set! Restart Claude Code to use FML tools.");
1152
+ }
1153
+ async function handleServiceTokenLogin(refreshToken) {
1154
+ try {
1155
+ await runServiceTokenLogin(refreshToken);
1156
+ process.exit(0);
1157
+ } catch (err) {
1158
+ const msg = err instanceof Error ? err.message : String(err);
1159
+ if (msg === "Canceled") {
1160
+ console.error("Login canceled.");
1161
+ process.exit(1);
1162
+ }
1163
+ Sentry.captureException(err);
1164
+ console.error(`Login failed: ${msg}`);
1165
+ process.exit(1);
1166
+ }
1167
+ }
1076
1168
  async function handleLogin(opts) {
1169
+ if (opts?.serviceToken) {
1170
+ await handleServiceTokenLogin();
1171
+ return;
1172
+ }
1077
1173
  const { name: envName } = getActiveEnv();
1078
1174
  const existingToken = await getValidToken();
1079
1175
  if (existingToken) {
1080
- const { readTokens: readTokens2 } = await import("./token-store-JZJ3FJHA.js");
1176
+ const { readTokens: readTokens2 } = await import("./token-store-3IQSLOTQ.js");
1081
1177
  const stored = readTokens2();
1082
- console.log(
1083
- `Already logged in as ${stored?.user.name} (${stored?.user.email}) on ${envName}.`
1084
- );
1178
+ if (stored?.tokenType === "service") {
1179
+ const label = stored.user.id === SERVICE_TOKEN_LOGIN_USER_ID ? "a service token" : `${stored.user.name} (${stored.user.email})`;
1180
+ console.log(`Already logged in with ${label} on ${envName}.`);
1181
+ } else {
1182
+ console.log(
1183
+ `Already logged in as ${stored?.user.name} (${stored?.user.email}) on ${envName}.`
1184
+ );
1185
+ }
1085
1186
  await linkGitHubIdentity();
1086
- await selectOrg();
1187
+ await selectOrg(envName);
1087
1188
  upgradeSyncTargetAfterLogin();
1088
1189
  console.log("You're all set! Restart Claude Code to use FML tools.");
1089
1190
  process.exit(0);
@@ -1100,7 +1201,7 @@ async function handleLogin(opts) {
1100
1201
  console.log(`
1101
1202
  Logged in as ${result.name} (${result.email})`);
1102
1203
  await linkGitHubIdentity();
1103
- await selectOrg();
1204
+ await selectOrg(envName);
1104
1205
  upgradeSyncTargetAfterLogin();
1105
1206
  console.log("You're all set! Restart Claude Code to use FML tools.");
1106
1207
  process.exit(0);
@@ -1204,7 +1305,11 @@ async function handleStatus() {
1204
1305
  }
1205
1306
  const tokenValid = await getValidToken();
1206
1307
  console.log("Authentication:");
1207
- console.log(` User: ${tokens.user.name} (${tokens.user.email})`);
1308
+ if (tokens.tokenType === "service" && tokens.user.id === SERVICE_TOKEN_LOGIN_USER_ID) {
1309
+ console.log(" Auth: service token");
1310
+ } else {
1311
+ console.log(` User: ${tokens.user.name} (${tokens.user.email})`);
1312
+ }
1208
1313
  console.log(
1209
1314
  ` Token: ${tokenValid ? "valid" : "expired (needs refresh/re-login)"}`
1210
1315
  );
@@ -1386,9 +1491,9 @@ async function handleEnvSwitch(target) {
1386
1491
  writeEnvConfig({ active: target });
1387
1492
  const convexUrl = match.url.replace(".convex.site", ".convex.cloud");
1388
1493
  console.log(`Switched to ${target}: ${convexUrl}`);
1389
- console.log("\nRestarting daemons...");
1390
- handleStop();
1391
- await handleStart();
1494
+ console.log("\nRestarting local collection...");
1495
+ handlePanopticonStop();
1496
+ await handlePanopticonStart();
1392
1497
  console.log(
1393
1498
  "MCP server will pick up the change on next Claude Code session."
1394
1499
  );
@@ -1961,7 +2066,7 @@ async function handleConfigDetail(opts) {
1961
2066
  // src/commands/tools.ts
1962
2067
  import { readFileSync } from "fs";
1963
2068
  import path3 from "path";
1964
- var pluginVersion = true ? "0.6.1+569d0d2" : void 0;
2069
+ var pluginVersion = true ? "0.7.0+8fe6800" : void 0;
1965
2070
  async function getApi() {
1966
2071
  const api = await getAuthenticatedClient();
1967
2072
  if (!api) {
@@ -2017,6 +2122,12 @@ async function handleToolsList(opts) {
2017
2122
  console.log("No tools found.");
2018
2123
  return;
2019
2124
  }
2125
+ console.log("Backend tools available to this FML session.\n");
2126
+ console.log("Next steps:");
2127
+ console.log(" fml tools describe <tool> --json Inspect args/schema");
2128
+ console.log(" fml tools call <tool> --args '{...}' Run a tool");
2129
+ console.log(" fml tools list --json Machine-readable catalog");
2130
+ console.log("");
2020
2131
  const nameW = Math.min(
2021
2132
  40,
2022
2133
  Math.max(...descriptors.map((d) => d.name.length), 4)
@@ -2091,56 +2202,60 @@ await initSentry();
2091
2202
  if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h")) {
2092
2203
  printBanner();
2093
2204
  }
2094
- var program = new Command().name("fml").description("FML agent tools for Claude Code").version(
2095
- true ? "0.6.1+569d0d2" : "dev"
2205
+ var program = new Command().name("fml").description("FML CLI and agent tools").version(
2206
+ true ? "0.7.0+8fe6800" : "dev"
2096
2207
  );
2097
- var tools = program.command("tools", { hidden: true }).description("List all available CLI commands (including hidden agent tools)").action(() => {
2098
- function formatCmd(cmd, prefix = "") {
2099
- const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
2100
- const args = cmd.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
2101
- const opts = cmd.options.filter(
2102
- (o) => !o.hidden && o.long !== "--help" && o.long !== "--version"
2103
- ).map((o) => o.mandatory ? o.flags : `[${o.flags}]`).join(" ");
2104
- const usage = [name, args, opts].filter(Boolean).join(" ");
2105
- return `${usage}
2208
+ function formatCommandInventory(cmd, prefix = "") {
2209
+ const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
2210
+ const args = cmd.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
2211
+ const opts = cmd.options.filter((o) => !o.hidden && o.long !== "--help" && o.long !== "--version").map((o) => o.mandatory ? o.flags : `[${o.flags}]`).join(" ");
2212
+ const usage = [name, args, opts].filter(Boolean).join(" ");
2213
+ return `${usage}
2106
2214
  ${cmd.description()}`;
2107
- }
2108
- const lines = program.commands.filter((c) => c.name() !== "tools" && c.name() !== "help").flatMap((c) => {
2215
+ }
2216
+ var tools = program.command("tools").description("List backend tools available via the dynamic catalog").option("--category <category>", "Filter by category").option("--json", "Output as JSON").action((opts) => handleToolsList(opts));
2217
+ program.command("commands", { hidden: true }).description("List CLI commands, including hidden/internal commands").action(() => {
2218
+ const lines = program.commands.filter((c) => c.name() !== "commands" && c.name() !== "help").flatMap((c) => {
2109
2219
  if (c.commands.length > 0) {
2110
- return c.commands.map((s) => formatCmd(s, c.name()));
2220
+ return c.commands.map((s) => formatCommandInventory(s, c.name()));
2111
2221
  }
2112
- return [formatCmd(c)];
2222
+ return [formatCommandInventory(c)];
2113
2223
  });
2114
2224
  console.log(lines.join("\n\n"));
2115
2225
  });
2116
2226
  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));
2117
2227
  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));
2118
2228
  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));
2119
- program.command("install").description("Register plugin, hooks, and daemons").option(
2229
+ program.command("install").description("Set up FML for local agent use").option(
2120
2230
  "--force",
2121
2231
  "Reinstall even if already set up (upgrades panopticon, re-registers plugin)"
2122
2232
  ).action((opts) => handleInstall(opts));
2123
- program.command("uninstall").description("Remove fml plugin, hooks, and optionally all data").option(
2233
+ program.command("uninstall").description("Remove FML agent integrations and optionally local data").option(
2124
2234
  "--target <target>",
2125
2235
  "Target CLI: claude, gemini, codex, claude-desktop, all"
2126
2236
  ).option("--purge", "Also remove all data, logs, and auth tokens").action((opts) => handleUninstall(opts));
2127
2237
  program.command("update").description("Update fml to the latest version").action(() => handleUpdate());
2128
- program.command("login").description("Sign in to your FML account").option("--device", "Use device authorization flow (no browser needed)").action((opts) => handleLogin(opts));
2238
+ program.command("login").description("Sign in to your FML account").option("--device", "Use device authorization flow (no browser needed)").option(
2239
+ "--service-token",
2240
+ "Sign in with an org service refresh token (prompts for fml_srt_*)"
2241
+ ).action(
2242
+ (opts) => handleLogin(opts)
2243
+ );
2129
2244
  program.command("logout").description("Sign out and clear stored credentials").action(() => handleLogout());
2130
2245
  program.command("org").description("Show or select organization").argument("[slug]", "Org slug to select").action((slug) => handleOrg(slug));
2131
- program.command("status").description("Show current auth and daemon status").action(() => handleStatus());
2246
+ program.command("status").description("Show auth and local service status").action(() => handleStatus());
2132
2247
  program.command("doctor").description("Check FML and panopticon configuration and connectivity").option("--json", "Output as JSON").action((opts) => handleDoctor(opts));
2133
2248
  program.command("open").description("Open FML dashboard in browser").option("--json", "Output URL as JSON instead of opening browser").action((opts) => handleOpen(opts));
2134
- program.command("env").description("Show or switch environment (dev/prod)").argument("[target]", 'Environment name ("dev", "prod") or custom Convex URL').action((target) => {
2249
+ program.command("env").description("Show or switch FML backend environment").argument("[target]", "Environment name or backend URL").action((target) => {
2135
2250
  if (target) handleEnvSwitch(target);
2136
2251
  else handleEnvShow();
2137
2252
  });
2138
- program.command("start").description("Start the panopticon server").action(() => handleStart());
2139
- program.command("stop").description("Stop the panopticon server").action(() => handleStop());
2140
- var panopticon = program.command("panopticon").description("Start or stop the panopticon server");
2141
- panopticon.command("start").description("Start the panopticon server").action(() => handleStart());
2142
- panopticon.command("stop").description("Stop the panopticon server").action(() => handleStop());
2143
- var sync = program.command("sync").description("Manage sync configuration");
2253
+ program.command("start").description("Start FML local collection and sync").action(() => handleFmlStart());
2254
+ program.command("stop").description("Stop FML local collection and sync").action(() => handleFmlStop());
2255
+ var panopticon = program.command("panopticon", { hidden: true }).description("Start or stop the panopticon server");
2256
+ panopticon.command("start").description("Start the panopticon server").action(() => handlePanopticonStart());
2257
+ panopticon.command("stop").description("Stop the panopticon server").action(() => handlePanopticonStop());
2258
+ var sync = program.command("sync").description("Manage sync configuration and troubleshooting");
2144
2259
  sync.command("start").description("Start syncing to configured targets (enable sync)").action(() => handleSyncStart());
2145
2260
  sync.command("stop").description("Stop syncing (disable sync; leaves targets configured)").action(() => handleSyncStop());
2146
2261
  sync.command("setup").description("Configure sync targets (convenience shortcut)").action(() => handleSyncSetup());
@@ -2162,9 +2277,9 @@ program.command("sync-token", { hidden: true }).description(
2162
2277
  ).action((opts) => handleSyncToken(opts));
2163
2278
  program.command("activity").description("Activity summary \u2014 sessions, prompts, tools, costs").option("--since <duration>", 'Time window, e.g. "24h", "7d"').action((opts) => handleActivity(opts));
2164
2279
  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));
2165
- 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));
2280
+ program.command("timeline").description("Get events for a session from `fml sessions`").argument("<session-id>", "Session ID from `fml sessions`").option("--limit <n>", "Max events to return").option("--offset <n>", "Events to skip").action((sessionId, opts) => handleTimeline(sessionId, opts));
2166
2281
  program.command("spending").description("AI token usage and cost breakdown").option("--since <duration>", 'Time filter, e.g. "24h", "7d"').option("--group-by <key>", "Group by: session, model, or day").action((opts) => handleSpending(opts));
2167
- 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));
2282
+ program.command("search").description("Search FML agent 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));
2168
2283
  program.command("query", { hidden: true }).description("Query a connected integration").argument(
2169
2284
  "<provider>",
2170
2285
  "Provider name (sentry, slack, github, linear, notion, freshdesk, stripe, posthog, amplitude, meta-ads)"