@fml-inc/fml 0.0.27 → 0.0.29
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-ND2YSPQ6.js → chunk-RJOFOTNH.js} +9 -3
- package/dist/{chunk-ND2YSPQ6.js.map → chunk-RJOFOTNH.js.map} +1 -1
- package/dist/{chunk-FS55W24B.js → chunk-TXQRKU2Y.js} +35 -5
- package/dist/chunk-TXQRKU2Y.js.map +1 -0
- package/dist/cli.js +192 -37
- package/dist/cli.js.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/{token-store-KQSMR6XA.js → token-store-NE7AM2PH.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-FS55W24B.js.map +0 -1
- /package/dist/{token-store-KQSMR6XA.js.map → token-store-NE7AM2PH.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -8,25 +8,26 @@ 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,
|
|
20
20
|
WORKOS_AUTH_URL,
|
|
21
21
|
getActiveEnv,
|
|
22
22
|
getSelectedOrg,
|
|
23
|
+
getSiteUrl,
|
|
23
24
|
getValidToken,
|
|
24
25
|
initSentry,
|
|
25
26
|
readTokens,
|
|
26
27
|
setSelectedOrg,
|
|
27
28
|
writeEnvConfig,
|
|
28
29
|
writeTokens
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-TXQRKU2Y.js";
|
|
30
31
|
import {
|
|
31
32
|
handleStart,
|
|
32
33
|
handleStop,
|
|
@@ -2063,6 +2064,11 @@ function resolveGitHubToken() {
|
|
|
2063
2064
|
return null;
|
|
2064
2065
|
}
|
|
2065
2066
|
}
|
|
2067
|
+
function resolveSyncTokenCommand() {
|
|
2068
|
+
if (resolveGitHubToken()) return "gh auth token";
|
|
2069
|
+
if (readTokens()) return "fml sync-token";
|
|
2070
|
+
return void 0;
|
|
2071
|
+
}
|
|
2066
2072
|
|
|
2067
2073
|
// src/commands/install.ts
|
|
2068
2074
|
var CLAUDE_DIR = path2.join(os2.homedir(), ".claude");
|
|
@@ -2224,24 +2230,23 @@ async function handleInstall() {
|
|
|
2224
2230
|
}
|
|
2225
2231
|
console.log();
|
|
2226
2232
|
console.log("[5/5] Configuring sync target...");
|
|
2227
|
-
const prodSyncUrl = DEFAULT_PROD_URL.replace(".cloud", ".site");
|
|
2228
2233
|
const existingTargets = listTargets();
|
|
2229
|
-
const existingProd = existingTargets.find((t) => t.url ===
|
|
2234
|
+
const existingProd = existingTargets.find((t) => t.url === DEFAULT_SYNC_URL);
|
|
2230
2235
|
if (existingProd) {
|
|
2231
2236
|
console.log(` Production target already configured`);
|
|
2232
2237
|
} else {
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
});
|
|
2240
|
-
|
|
2241
|
-
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}`);
|
|
2242
2247
|
} else {
|
|
2243
2248
|
console.log(
|
|
2244
|
-
"
|
|
2249
|
+
" Auth: not configured \u2014 run `fml login` to enable sync."
|
|
2245
2250
|
);
|
|
2246
2251
|
}
|
|
2247
2252
|
}
|
|
@@ -2415,7 +2420,7 @@ function handleUninstall(opts) {
|
|
|
2415
2420
|
// src/commands/update.ts
|
|
2416
2421
|
import { execSync as execSync2 } from "child_process";
|
|
2417
2422
|
async function handleUpdate() {
|
|
2418
|
-
const currentVersion = true ? "0.0.
|
|
2423
|
+
const currentVersion = true ? "0.0.29+df7d11e" : "unknown";
|
|
2419
2424
|
console.log(`Current: ${currentVersion}`);
|
|
2420
2425
|
console.log("Updating via npm...\n");
|
|
2421
2426
|
try {
|
|
@@ -2445,6 +2450,22 @@ async function fetchWorkosClientId() {
|
|
|
2445
2450
|
const result = await client.query(ref, {});
|
|
2446
2451
|
return result.workosClientId;
|
|
2447
2452
|
}
|
|
2453
|
+
async function canOpenBrowser() {
|
|
2454
|
+
if (process.env.FML_DEVICE_FLOW === "1") return false;
|
|
2455
|
+
if (process.env.FML_NO_BROWSER === "1") return false;
|
|
2456
|
+
if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
if (process.env.SSH_CONNECTION) return false;
|
|
2460
|
+
if (process.env.CODESPACES) return false;
|
|
2461
|
+
if (process.env.REMOTE_CONTAINERS) return false;
|
|
2462
|
+
try {
|
|
2463
|
+
const fs4 = await import("fs");
|
|
2464
|
+
if (fs4.existsSync("/.dockerenv")) return false;
|
|
2465
|
+
} catch {
|
|
2466
|
+
}
|
|
2467
|
+
return true;
|
|
2468
|
+
}
|
|
2448
2469
|
async function login() {
|
|
2449
2470
|
const clientId = await fetchWorkosClientId();
|
|
2450
2471
|
const codeVerifier = crypto.randomBytes(32).toString("base64url");
|
|
@@ -2568,6 +2589,93 @@ function startCallbackServer(expectedState) {
|
|
|
2568
2589
|
});
|
|
2569
2590
|
}
|
|
2570
2591
|
|
|
2592
|
+
// src/auth/device-flow.ts
|
|
2593
|
+
import { createInterface } from "readline";
|
|
2594
|
+
function sleep(ms) {
|
|
2595
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2596
|
+
}
|
|
2597
|
+
function prompt(question) {
|
|
2598
|
+
const rl = createInterface({
|
|
2599
|
+
input: process.stdin,
|
|
2600
|
+
output: process.stderr
|
|
2601
|
+
// Use stderr so stdout stays clean for MCP
|
|
2602
|
+
});
|
|
2603
|
+
return new Promise((resolve) => {
|
|
2604
|
+
rl.question(question, (answer) => {
|
|
2605
|
+
rl.close();
|
|
2606
|
+
resolve(answer.trim());
|
|
2607
|
+
});
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
async function deviceLogin() {
|
|
2611
|
+
const siteUrl = getSiteUrl();
|
|
2612
|
+
const codeRes = await fetch(`${siteUrl}/api/auth/device/code`, {
|
|
2613
|
+
method: "POST",
|
|
2614
|
+
headers: { "Content-Type": "application/json" }
|
|
2615
|
+
});
|
|
2616
|
+
if (!codeRes.ok) {
|
|
2617
|
+
const body = await codeRes.text();
|
|
2618
|
+
throw new Error(`Failed to start device flow: ${body}`);
|
|
2619
|
+
}
|
|
2620
|
+
const codeData = await codeRes.json();
|
|
2621
|
+
console.log("");
|
|
2622
|
+
console.log(" Paste this URL in your browser to sign in:");
|
|
2623
|
+
console.log("");
|
|
2624
|
+
console.log(` ${codeData.verificationUri}`);
|
|
2625
|
+
console.log("");
|
|
2626
|
+
const confirmationCode = await prompt(" Enter the code from your browser: ");
|
|
2627
|
+
if (!confirmationCode) {
|
|
2628
|
+
throw new Error("No confirmation code entered.");
|
|
2629
|
+
}
|
|
2630
|
+
const normalizedCode = confirmationCode.toUpperCase().replace(/[\s-]/g, "").replace(/^(.{4})/, "$1-");
|
|
2631
|
+
console.log(" Verifying...");
|
|
2632
|
+
let pollInterval = codeData.interval * 1e3;
|
|
2633
|
+
while (Date.now() < codeData.expiresAt) {
|
|
2634
|
+
const pollRes = await fetch(`${siteUrl}/api/auth/device/poll`, {
|
|
2635
|
+
method: "POST",
|
|
2636
|
+
headers: { "Content-Type": "application/json" },
|
|
2637
|
+
body: JSON.stringify({
|
|
2638
|
+
deviceCode: codeData.deviceCode,
|
|
2639
|
+
confirmationCode: normalizedCode
|
|
2640
|
+
})
|
|
2641
|
+
});
|
|
2642
|
+
if (pollRes.status === 429) {
|
|
2643
|
+
const data2 = await pollRes.json();
|
|
2644
|
+
pollInterval = (data2.interval ?? codeData.interval * 2) * 1e3;
|
|
2645
|
+
await sleep(pollInterval);
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
const data = await pollRes.json();
|
|
2649
|
+
if (data.status === "pending") {
|
|
2650
|
+
await sleep(pollInterval);
|
|
2651
|
+
continue;
|
|
2652
|
+
}
|
|
2653
|
+
if (data.status === "authorized") {
|
|
2654
|
+
writeTokens({
|
|
2655
|
+
accessToken: data.accessToken,
|
|
2656
|
+
refreshToken: data.refreshToken,
|
|
2657
|
+
expiresAt: data.expiresAt,
|
|
2658
|
+
user: data.user,
|
|
2659
|
+
orgSlug: data.orgSlug ?? void 0,
|
|
2660
|
+
tokenType: "service"
|
|
2661
|
+
});
|
|
2662
|
+
return { email: data.user.email, name: data.user.name };
|
|
2663
|
+
}
|
|
2664
|
+
if (data.status === "invalid_code") {
|
|
2665
|
+
throw new Error(
|
|
2666
|
+
"Invalid confirmation code. Make sure you copied the code from the browser correctly."
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
if (data.status === "expired") {
|
|
2670
|
+
throw new Error("Device authorization expired. Run `fml login` again.");
|
|
2671
|
+
}
|
|
2672
|
+
throw new Error(`Device authorization failed: ${data.status}`);
|
|
2673
|
+
}
|
|
2674
|
+
throw new Error(
|
|
2675
|
+
"Device authorization timed out (15 minutes). Run `fml login` again."
|
|
2676
|
+
);
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2571
2679
|
// src/commands/login.ts
|
|
2572
2680
|
async function linkGitHubIdentity() {
|
|
2573
2681
|
const token = resolveGitHubToken();
|
|
@@ -2594,10 +2702,7 @@ async function linkGitHubIdentity() {
|
|
|
2594
2702
|
console.warn("[fml] No FML token available \u2014 skipping identity link");
|
|
2595
2703
|
return;
|
|
2596
2704
|
}
|
|
2597
|
-
if (
|
|
2598
|
-
console.warn(
|
|
2599
|
-
"[fml] No Convex URL configured \u2014 skipping identity link (run fml install first)"
|
|
2600
|
-
);
|
|
2705
|
+
if (fmlToken.startsWith("fml_st_")) {
|
|
2601
2706
|
return;
|
|
2602
2707
|
}
|
|
2603
2708
|
const { ConvexHttpClient } = await import("convex/browser");
|
|
@@ -2615,6 +2720,33 @@ async function linkGitHubIdentity() {
|
|
|
2615
2720
|
console.warn(`[fml] Failed to link GitHub identity: ${msg}`);
|
|
2616
2721
|
}
|
|
2617
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
|
+
}
|
|
2618
2750
|
async function selectOrg() {
|
|
2619
2751
|
try {
|
|
2620
2752
|
const token = await getValidToken();
|
|
@@ -2629,27 +2761,35 @@ async function selectOrg() {
|
|
|
2629
2761
|
} catch {
|
|
2630
2762
|
}
|
|
2631
2763
|
}
|
|
2632
|
-
async function handleLogin() {
|
|
2764
|
+
async function handleLogin(opts) {
|
|
2633
2765
|
const { name: envName } = getActiveEnv();
|
|
2634
2766
|
const existingToken = await getValidToken();
|
|
2635
2767
|
if (existingToken) {
|
|
2636
|
-
const { readTokens: readTokens2 } = await import("./token-store-
|
|
2768
|
+
const { readTokens: readTokens2 } = await import("./token-store-NE7AM2PH.js");
|
|
2637
2769
|
const stored = readTokens2();
|
|
2638
2770
|
console.log(
|
|
2639
2771
|
`Already logged in as ${stored?.user.name} (${stored?.user.email}) on ${envName}.`
|
|
2640
2772
|
);
|
|
2641
2773
|
await linkGitHubIdentity();
|
|
2642
2774
|
await selectOrg();
|
|
2775
|
+
upgradeSyncTargetAfterLogin();
|
|
2643
2776
|
console.log("You're all set! Restart Claude Code to use FML tools.");
|
|
2644
2777
|
process.exit(0);
|
|
2645
2778
|
}
|
|
2646
2779
|
console.log(`Signing in to FML (${envName})...`);
|
|
2647
2780
|
try {
|
|
2648
|
-
const
|
|
2781
|
+
const useDeviceFlow = opts?.device || !await canOpenBrowser();
|
|
2782
|
+
let result;
|
|
2783
|
+
if (useDeviceFlow) {
|
|
2784
|
+
result = await deviceLogin();
|
|
2785
|
+
} else {
|
|
2786
|
+
result = await login();
|
|
2787
|
+
}
|
|
2649
2788
|
console.log(`
|
|
2650
|
-
Logged in as ${name} (${email})`);
|
|
2789
|
+
Logged in as ${result.name} (${result.email})`);
|
|
2651
2790
|
await linkGitHubIdentity();
|
|
2652
2791
|
await selectOrg();
|
|
2792
|
+
upgradeSyncTargetAfterLogin();
|
|
2653
2793
|
console.log("You're all set! Restart Claude Code to use FML tools.");
|
|
2654
2794
|
process.exit(0);
|
|
2655
2795
|
} catch (err) {
|
|
@@ -2760,26 +2900,24 @@ async function handleStatus() {
|
|
|
2760
2900
|
}
|
|
2761
2901
|
|
|
2762
2902
|
// src/commands/sync.ts
|
|
2763
|
-
var FML_SYNC_URL = CONVEX_URL.replace(".cloud", ".site");
|
|
2764
2903
|
async function handleSyncSetup() {
|
|
2765
2904
|
console.log("FML Sync Setup\n");
|
|
2766
|
-
const
|
|
2767
|
-
if (!
|
|
2905
|
+
const tokenCommand = resolveSyncTokenCommand();
|
|
2906
|
+
if (!tokenCommand) {
|
|
2768
2907
|
console.error(
|
|
2769
|
-
"No
|
|
2908
|
+
"No credential available. Run `fml login` first, or set PANOPTICON_GITHUB_TOKEN / install gh CLI."
|
|
2770
2909
|
);
|
|
2771
2910
|
process.exit(1);
|
|
2772
2911
|
}
|
|
2773
|
-
|
|
2912
|
+
removeTarget(DEFAULT_TARGET_NAME);
|
|
2774
2913
|
addTarget({
|
|
2775
2914
|
name: DEFAULT_TARGET_NAME,
|
|
2776
|
-
url:
|
|
2777
|
-
tokenCommand
|
|
2915
|
+
url: DEFAULT_SYNC_URL,
|
|
2916
|
+
tokenCommand
|
|
2778
2917
|
});
|
|
2779
|
-
console.log(`
|
|
2780
|
-
|
|
2781
|
-
console.log(`
|
|
2782
|
-
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}`);
|
|
2783
2921
|
console.log(`
|
|
2784
2922
|
Restart panopticon to activate: fml stop && fml start`);
|
|
2785
2923
|
}
|
|
@@ -2890,6 +3028,20 @@ async function handleSyncReset(targetName) {
|
|
|
2890
3028
|
);
|
|
2891
3029
|
}
|
|
2892
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
|
+
|
|
2893
3045
|
// src/commands/env.ts
|
|
2894
3046
|
function handleEnvShow() {
|
|
2895
3047
|
const { name, convexUrl } = getActiveEnv();
|
|
@@ -3467,7 +3619,7 @@ if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.
|
|
|
3467
3619
|
printBanner();
|
|
3468
3620
|
}
|
|
3469
3621
|
var program = new Command().name("fml").description("FML agent tools for Claude Code").version(
|
|
3470
|
-
true ? "0.0.
|
|
3622
|
+
true ? "0.0.29+df7d11e" : "dev"
|
|
3471
3623
|
);
|
|
3472
3624
|
program.command("tools", { hidden: true }).description("List all available CLI commands (including hidden agent tools)").action(() => {
|
|
3473
3625
|
function formatCmd(cmd, prefix = "") {
|
|
@@ -3494,7 +3646,7 @@ program.command("uninstall").description("Remove fml plugin, hooks, and optional
|
|
|
3494
3646
|
"Target CLI: claude, gemini, codex, claude-desktop, all"
|
|
3495
3647
|
).option("--purge", "Also remove all data, logs, and auth tokens").action((opts) => handleUninstall(opts));
|
|
3496
3648
|
program.command("update").description("Update fml to the latest version").action(() => handleUpdate());
|
|
3497
|
-
program.command("login").description("Sign in to your FML account").action(() => handleLogin());
|
|
3649
|
+
program.command("login").description("Sign in to your FML account").option("--device", "Use device authorization flow (no browser needed)").action((opts) => handleLogin(opts));
|
|
3498
3650
|
program.command("logout").description("Sign out and clear stored credentials").action(() => handleLogout());
|
|
3499
3651
|
program.command("org").description("Show or select organization").argument("[slug]", "Org slug to select").action((slug) => handleOrg(slug));
|
|
3500
3652
|
program.command("status").description("Show current auth and daemon status").action(() => handleStatus());
|
|
@@ -3518,6 +3670,9 @@ sync.command("edit").description("Edit a sync target").argument("<name>", "Targe
|
|
|
3518
3670
|
);
|
|
3519
3671
|
sync.command("status").description("Show sync config and watermarks").action(() => handleSyncStatus());
|
|
3520
3672
|
sync.command("reset").description("Reset sync watermarks").argument("[name]", "Target name (resets all if omitted)").action((name) => handleSyncReset(name));
|
|
3673
|
+
program.command("sync-token", { hidden: true }).description(
|
|
3674
|
+
"Print a refreshed FML access token (for panopticon tokenCommand)"
|
|
3675
|
+
).action(() => handleSyncToken());
|
|
3521
3676
|
program.command("activity").description("Activity summary \u2014 sessions, prompts, tools, costs").option("--since <duration>", 'Time window, e.g. "24h", "7d"').action((opts) => handleActivity(opts));
|
|
3522
3677
|
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));
|
|
3523
3678
|
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));
|