@fml-inc/fml 0.0.22

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 ADDED
@@ -0,0 +1,1720 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createApiClient,
4
+ ensureNativeModules,
5
+ getAuthenticatedClient
6
+ } from "./chunk-MYCITM37.js";
7
+ import {
8
+ AUTH_STORE_PATH,
9
+ CONVEX_URL,
10
+ DEFAULT_PROD_URL,
11
+ DEFAULT_TARGET_NAME,
12
+ Sentry,
13
+ WORKOS_API_URL,
14
+ WORKOS_AUTH_URL,
15
+ getActiveEnv,
16
+ getSelectedOrg,
17
+ getValidToken,
18
+ initSentry,
19
+ readTokens,
20
+ setSelectedOrg,
21
+ writeEnvConfig,
22
+ writeTokens
23
+ } from "./chunk-DP2ILCME.js";
24
+ import {
25
+ handleStart,
26
+ handleStop,
27
+ panopticonExec,
28
+ parsePanopticonRunning
29
+ } from "./chunk-EL2WSQBR.js";
30
+ import {
31
+ FML_DATA_DIR,
32
+ FML_LOG_DIR
33
+ } from "./chunk-4YEAQJPF.js";
34
+ import "./chunk-PZ5AY32C.js";
35
+
36
+ // src/cli.ts
37
+ import { Command } from "commander";
38
+
39
+ // src/commands/doctor.ts
40
+ import ora from "ora";
41
+ import pc from "picocolors";
42
+ import { loadSyncConfig } from "@fml-inc/panopticon/sync";
43
+ import { syncPending } from "@fml-inc/panopticon/api";
44
+ function reportResult(result, spinner) {
45
+ const text = `${result.label} \u2014 ${result.detail}`;
46
+ if (spinner) {
47
+ switch (result.status) {
48
+ case "ok":
49
+ spinner.succeed(text);
50
+ break;
51
+ case "warn":
52
+ spinner.warn(text);
53
+ break;
54
+ case "fail":
55
+ spinner.fail(text);
56
+ break;
57
+ }
58
+ } else {
59
+ const icon = result.status === "ok" ? pc.green("\u2713") : result.status === "warn" ? pc.yellow("!") : pc.red("\u2717");
60
+ console.log(` ${icon} ${result.label} \u2014 ${result.detail}`);
61
+ }
62
+ }
63
+ async function handleDoctor(opts) {
64
+ const isTTY = !opts.json && process.stdout.isTTY === true;
65
+ const checks = [];
66
+ function startSpinner(label) {
67
+ if (!isTTY) return null;
68
+ return ora({ text: `Checking ${label}...`, indent: 2 }).start();
69
+ }
70
+ function pushAndReport(result, spinner) {
71
+ checks.push(result);
72
+ if (!opts.json) {
73
+ reportResult(result, spinner);
74
+ }
75
+ }
76
+ if (!opts.json && !isTTY) {
77
+ console.log("");
78
+ }
79
+ let token = null;
80
+ {
81
+ const spinner = startSpinner("auth");
82
+ token = await getValidToken();
83
+ if (token) {
84
+ const stored = readTokens();
85
+ const detail = stored?.user?.email ? `Logged in as ${stored.user.email}` : "Token present";
86
+ pushAndReport({ label: "Auth", status: "ok", detail }, spinner);
87
+ } else {
88
+ pushAndReport(
89
+ {
90
+ label: "Auth",
91
+ status: "fail",
92
+ detail: "No token. Run `fml login`"
93
+ },
94
+ spinner
95
+ );
96
+ }
97
+ }
98
+ {
99
+ const spinner = startSpinner("panopticon");
100
+ try {
101
+ const { doctor } = await import("@fml-inc/panopticon/doctor");
102
+ const result = await doctor();
103
+ if (spinner) spinner.stop();
104
+ for (const check of result.checks) {
105
+ pushAndReport(check, null);
106
+ }
107
+ } catch {
108
+ const result = panopticonExec("status");
109
+ if (result.ok) {
110
+ pushAndReport(
111
+ { label: "Panopticon", status: "ok", detail: "Available" },
112
+ spinner
113
+ );
114
+ } else {
115
+ pushAndReport(
116
+ {
117
+ label: "Panopticon",
118
+ status: "fail",
119
+ detail: "Not found. Run `fml install`"
120
+ },
121
+ spinner
122
+ );
123
+ }
124
+ }
125
+ }
126
+ if (token) {
127
+ const spinner = startSpinner("sync");
128
+ try {
129
+ const syncConfig = loadSyncConfig();
130
+ const targets = syncConfig.targets;
131
+ if (targets.length === 0) {
132
+ pushAndReport(
133
+ {
134
+ label: "Sync",
135
+ status: "warn",
136
+ detail: "No sync targets configured"
137
+ },
138
+ spinner
139
+ );
140
+ } else {
141
+ if (spinner) spinner.stop();
142
+ for (const target of targets) {
143
+ try {
144
+ const result = await syncPending(target.name);
145
+ if (result.totalPending === 0) {
146
+ pushAndReport(
147
+ {
148
+ label: `Sync \u2192 ${target.name}`,
149
+ status: "ok",
150
+ detail: "Up to date"
151
+ },
152
+ null
153
+ );
154
+ } else {
155
+ const parts2 = Object.entries(result.tables).sort(([, a], [, b]) => b.pending - a.pending).slice(0, 3).map(([t, v]) => `${v.pending} ${t}`);
156
+ const detail = parts2.length < Object.keys(result.tables).length ? `${result.totalPending} pending (${parts2.join(", ")}, ...)` : `${result.totalPending} pending (${parts2.join(", ")})`;
157
+ pushAndReport(
158
+ {
159
+ label: `Sync \u2192 ${target.name}`,
160
+ status: result.totalPending > 1e3 ? "warn" : "ok",
161
+ detail
162
+ },
163
+ null
164
+ );
165
+ }
166
+ } catch (err) {
167
+ const msg = err instanceof Error ? err.message : String(err);
168
+ pushAndReport(
169
+ {
170
+ label: `Sync \u2192 ${target.name}`,
171
+ status: "warn",
172
+ detail: `Could not query watermarks (${msg})`
173
+ },
174
+ null
175
+ );
176
+ }
177
+ }
178
+ }
179
+ } catch (err) {
180
+ pushAndReport(
181
+ {
182
+ label: "Sync",
183
+ status: "warn",
184
+ detail: err instanceof Error ? err.message : "Check failed"
185
+ },
186
+ spinner
187
+ );
188
+ }
189
+ }
190
+ {
191
+ const spinner = startSpinner("panopticon daemon");
192
+ const isRunning = parsePanopticonRunning();
193
+ if (isRunning) {
194
+ pushAndReport(
195
+ {
196
+ label: "Panopticon daemon",
197
+ status: "ok",
198
+ detail: "running"
199
+ },
200
+ spinner
201
+ );
202
+ } else {
203
+ pushAndReport(
204
+ {
205
+ label: "Panopticon daemon",
206
+ status: "warn",
207
+ detail: "Not running. Run `fml start`"
208
+ },
209
+ spinner
210
+ );
211
+ }
212
+ }
213
+ {
214
+ const spinner = startSpinner("API");
215
+ if (token) {
216
+ try {
217
+ const result = await createApiClient(token).callBackend("ping", {});
218
+ if (result.ok) {
219
+ pushAndReport(
220
+ {
221
+ label: "API",
222
+ status: "ok",
223
+ detail: `Reachable at ${CONVEX_URL}`
224
+ },
225
+ spinner
226
+ );
227
+ } else {
228
+ pushAndReport(
229
+ {
230
+ label: "API",
231
+ status: "ok",
232
+ detail: `Reachable at ${CONVEX_URL}`
233
+ },
234
+ spinner
235
+ );
236
+ }
237
+ } catch (err) {
238
+ pushAndReport(
239
+ {
240
+ label: "API",
241
+ status: "fail",
242
+ detail: err instanceof Error ? err.message : "Unreachable"
243
+ },
244
+ spinner
245
+ );
246
+ }
247
+ } else {
248
+ pushAndReport(
249
+ { label: "API", status: "fail", detail: "Auth required" },
250
+ spinner
251
+ );
252
+ }
253
+ }
254
+ if (opts.json) {
255
+ console.log(JSON.stringify(checks, null, 2));
256
+ return;
257
+ }
258
+ console.log("");
259
+ const passed = checks.filter((c) => c.status === "ok").length;
260
+ const failed = checks.filter((c) => c.status === "fail").length;
261
+ const warned = checks.filter((c) => c.status === "warn").length;
262
+ const parts = [];
263
+ if (passed > 0) parts.push(pc.green(`${passed} passed`));
264
+ if (warned > 0)
265
+ parts.push(pc.yellow(`${warned} warning${warned > 1 ? "s" : ""}`));
266
+ if (failed > 0) parts.push(pc.red(`${failed} failed`));
267
+ console.log(` ${parts.join(", ")}`);
268
+ console.log("");
269
+ }
270
+
271
+ // src/commands/install.ts
272
+ import fs from "fs";
273
+ import os from "os";
274
+ import path from "path";
275
+ import { fileURLToPath } from "url";
276
+ import { addTarget, listTargets } from "@fml-inc/panopticon/sync";
277
+
278
+ // src/banner.ts
279
+ import pc2 from "picocolors";
280
+ var LOGO_LINES = [
281
+ " \u2584\u2588\u2588\u2580 \u2588\u2588\u2584\u2588\u2588\u2584\u2588\u2588 \u2588\u2588",
282
+ " \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
283
+ "\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
284
+ " \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588",
285
+ " \u2580\u2580 \u2580\u2580 \u2580\u2580 \u2580\u2580\u2580\u2580"
286
+ ];
287
+ function printBanner() {
288
+ console.log("");
289
+ for (let i = 0; i < LOGO_LINES.length; i++) {
290
+ const color = i < 3 ? pc2.blue : pc2.cyan;
291
+ console.log(" " + color(LOGO_LINES[i]));
292
+ }
293
+ console.log("");
294
+ }
295
+
296
+ // src/sync/client.ts
297
+ import { execSync } from "child_process";
298
+ var TOKEN_TTL_MS = 5 * 60 * 1e3;
299
+ var cachedGitHubToken = null;
300
+ function resolveGitHubToken() {
301
+ if (cachedGitHubToken && Date.now() < cachedGitHubToken.expiresAt) {
302
+ return cachedGitHubToken.value;
303
+ }
304
+ const envToken = process.env.PANOPTICON_GITHUB_TOKEN;
305
+ if (envToken) {
306
+ cachedGitHubToken = {
307
+ value: envToken,
308
+ expiresAt: Date.now() + TOKEN_TTL_MS
309
+ };
310
+ return envToken;
311
+ }
312
+ try {
313
+ const token = execSync("gh auth token", {
314
+ encoding: "utf-8",
315
+ timeout: 5e3
316
+ }).trim();
317
+ if (token) {
318
+ cachedGitHubToken = {
319
+ value: token,
320
+ expiresAt: Date.now() + TOKEN_TTL_MS
321
+ };
322
+ }
323
+ return token || null;
324
+ } catch {
325
+ return null;
326
+ }
327
+ }
328
+
329
+ // src/commands/install.ts
330
+ var CLAUDE_DIR = path.join(os.homedir(), ".claude");
331
+ var CLAUDE_SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
332
+ var MARKETPLACE_DIR = path.join(
333
+ os.homedir(),
334
+ ".local",
335
+ "share",
336
+ "claude-plugins"
337
+ );
338
+ function getPluginRoot() {
339
+ let dir = path.dirname(fileURLToPath(import.meta.url));
340
+ dir = path.resolve(dir, "..");
341
+ return dir;
342
+ }
343
+ function readJsonFile(filePath) {
344
+ try {
345
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+ function writeJsonFile(filePath, data) {
351
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
352
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}
353
+ `);
354
+ }
355
+ async function handleInstall() {
356
+ const pluginRoot = getPluginRoot();
357
+ console.log("Installing fml...\n");
358
+ console.log("[1/4] Running panopticon install...");
359
+ const result = panopticonExec("install", { timeout: 6e4 });
360
+ if (result.ok) {
361
+ for (const line of result.stdout.trim().split("\n")) {
362
+ console.log(` ${line}`);
363
+ }
364
+ } else {
365
+ console.error(" panopticon install failed:");
366
+ for (const line of result.stdout.trim().split("\n")) {
367
+ console.error(` ${line}`);
368
+ }
369
+ }
370
+ console.log();
371
+ console.log("[2/4] Creating fml directories...");
372
+ for (const dir of [FML_DATA_DIR, FML_LOG_DIR]) {
373
+ fs.mkdirSync(dir, { recursive: true });
374
+ }
375
+ console.log(` ${FML_DATA_DIR}`);
376
+ console.log(` ${FML_LOG_DIR}
377
+ `);
378
+ console.log("[3/4] Setting up fml plugin...");
379
+ fs.mkdirSync(path.join(MARKETPLACE_DIR, ".claude-plugin"), {
380
+ recursive: true
381
+ });
382
+ const manifestPath = path.join(
383
+ MARKETPLACE_DIR,
384
+ ".claude-plugin",
385
+ "marketplace.json"
386
+ );
387
+ const manifest = readJsonFile(manifestPath) ?? {
388
+ name: "local-plugins",
389
+ owner: { name: os.userInfo().username },
390
+ plugins: []
391
+ };
392
+ const plugins = manifest.plugins ?? [];
393
+ if (!plugins.some((p) => p.name === "fml")) {
394
+ plugins.push({
395
+ name: "fml",
396
+ source: "./fml",
397
+ description: "FML agent tools for Claude Code"
398
+ });
399
+ manifest.plugins = plugins;
400
+ }
401
+ writeJsonFile(manifestPath, manifest);
402
+ const marketplaceLink = path.join(MARKETPLACE_DIR, "fml");
403
+ try {
404
+ fs.unlinkSync(marketplaceLink);
405
+ } catch {
406
+ }
407
+ fs.rmSync(marketplaceLink, { recursive: true, force: true });
408
+ fs.symlinkSync(pluginRoot, marketplaceLink, "dir");
409
+ console.log(` Marketplace: ${MARKETPLACE_DIR}`);
410
+ console.log(` Plugin: ${pluginRoot}`);
411
+ const settings = readJsonFile(CLAUDE_SETTINGS_PATH) ?? {};
412
+ settings.extraKnownMarketplaces = settings.extraKnownMarketplaces ?? {};
413
+ settings.extraKnownMarketplaces["local-plugins"] = {
414
+ source: { source: "directory", path: MARKETPLACE_DIR }
415
+ };
416
+ settings.enabledPlugins = settings.enabledPlugins ?? {};
417
+ settings.enabledPlugins["fml@local-plugins"] = true;
418
+ writeJsonFile(CLAUDE_SETTINGS_PATH, settings);
419
+ console.log(` Claude settings: ${CLAUDE_SETTINGS_PATH}
420
+ `);
421
+ console.log("[4/4] Configuring sync target...");
422
+ const prodSyncUrl = DEFAULT_PROD_URL.replace(".cloud", ".site");
423
+ const existingTargets = listTargets();
424
+ const existingProd = existingTargets.find((t) => t.url === prodSyncUrl);
425
+ if (existingProd) {
426
+ console.log(` Production target already configured`);
427
+ } else {
428
+ const ghToken = resolveGitHubToken();
429
+ if (ghToken) {
430
+ addTarget({
431
+ name: DEFAULT_TARGET_NAME,
432
+ url: prodSyncUrl,
433
+ tokenCommand: "gh auth token"
434
+ });
435
+ console.log(` Target "${DEFAULT_TARGET_NAME}": ${prodSyncUrl}`);
436
+ console.log(" Auth: gh auth token");
437
+ } else {
438
+ console.log(
439
+ " Skipped (no GitHub token). Run `fml sync setup` later."
440
+ );
441
+ }
442
+ }
443
+ writeEnvConfig({ active: DEFAULT_TARGET_NAME });
444
+ console.log("");
445
+ printBanner();
446
+ console.log("Done! Start a new coding session to activate.\n");
447
+ console.log("\nNext steps:");
448
+ console.log(" fml login Sign in to your FML account");
449
+ console.log(" fml org Select organization");
450
+ console.log(" fml sync status Check sync status");
451
+ console.log(" fml status Verify setup");
452
+ }
453
+
454
+ // src/commands/uninstall.ts
455
+ import fs2 from "fs";
456
+ import os2 from "os";
457
+ import path2 from "path";
458
+ var CLAUDE_DIR2 = path2.join(os2.homedir(), ".claude");
459
+ var CLAUDE_SETTINGS_PATH2 = path2.join(CLAUDE_DIR2, "settings.json");
460
+ var MARKETPLACE_DIR2 = path2.join(
461
+ os2.homedir(),
462
+ ".local",
463
+ "share",
464
+ "claude-plugins"
465
+ );
466
+ function readJsonFile2(filePath) {
467
+ try {
468
+ return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
469
+ } catch {
470
+ return null;
471
+ }
472
+ }
473
+ function writeJsonFile2(filePath, data) {
474
+ fs2.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}
475
+ `);
476
+ }
477
+ function handleUninstall(opts) {
478
+ const targetSpecific = opts.target && opts.target !== "all";
479
+ console.log("Uninstalling fml...\n");
480
+ if (targetSpecific) {
481
+ console.log("[1/4] Skipping plugin settings (target-specific uninstall)");
482
+ } else {
483
+ console.log("[1/4] Removing plugin from Claude Code settings...");
484
+ const settings = readJsonFile2(CLAUDE_SETTINGS_PATH2);
485
+ if (settings) {
486
+ if (settings.enabledPlugins) {
487
+ delete settings.enabledPlugins["fml@local-plugins"];
488
+ }
489
+ if (settings.extraKnownMarketplaces) {
490
+ delete settings.extraKnownMarketplaces["local-plugins"];
491
+ }
492
+ writeJsonFile2(CLAUDE_SETTINGS_PATH2, settings);
493
+ console.log(` Updated ${CLAUDE_SETTINGS_PATH2}`);
494
+ } else {
495
+ console.log(" No settings file found, skipping");
496
+ }
497
+ }
498
+ if (targetSpecific) {
499
+ console.log("[2/4] Skipping marketplace (target-specific uninstall)");
500
+ } else {
501
+ console.log("[2/4] Removing marketplace registration...");
502
+ const marketplaceLink = path2.join(MARKETPLACE_DIR2, "fml");
503
+ try {
504
+ fs2.rmSync(marketplaceLink, { recursive: true, force: true });
505
+ console.log(` Removed ${marketplaceLink}`);
506
+ } catch {
507
+ console.log(" No marketplace link found, skipping");
508
+ }
509
+ const manifestPath = path2.join(
510
+ MARKETPLACE_DIR2,
511
+ ".claude-plugin",
512
+ "marketplace.json"
513
+ );
514
+ const manifest = readJsonFile2(manifestPath);
515
+ if (manifest && Array.isArray(manifest.plugins)) {
516
+ manifest.plugins = manifest.plugins.filter((p) => p.name !== "fml");
517
+ writeJsonFile2(manifestPath, manifest);
518
+ console.log(` Updated ${manifestPath}`);
519
+ }
520
+ }
521
+ console.log("[3/4] Running panopticon uninstall...");
522
+ const panoArgs = ["uninstall"];
523
+ if (opts.target) panoArgs.push("--target", opts.target);
524
+ if (opts.purge) panoArgs.push("--purge");
525
+ const result = panopticonExec(...panoArgs, { timeout: 3e4 });
526
+ if (result.ok) {
527
+ for (const line of result.stdout.trim().split("\n")) {
528
+ console.log(` ${line}`);
529
+ }
530
+ } else {
531
+ console.error(" panopticon uninstall failed:");
532
+ for (const line of result.stdout.trim().split("\n")) {
533
+ console.error(` ${line}`);
534
+ }
535
+ }
536
+ if (targetSpecific) {
537
+ console.log("[4/4] Skipping data removal (target-specific uninstall)");
538
+ } else if (opts.purge) {
539
+ console.log("[4/4] Removing fml data and logs...");
540
+ for (const dir of [FML_DATA_DIR, FML_LOG_DIR]) {
541
+ try {
542
+ fs2.rmSync(dir, { recursive: true, force: true });
543
+ console.log(` Removed ${dir}`);
544
+ } catch {
545
+ console.log(` Could not remove ${dir}`);
546
+ }
547
+ }
548
+ } else {
549
+ console.log("[4/4] Keeping fml data (use --purge to remove)");
550
+ }
551
+ console.log("\nDone! FML has been uninstalled.");
552
+ }
553
+
554
+ // src/commands/update.ts
555
+ import { execSync as execSync2 } from "child_process";
556
+ async function handleUpdate() {
557
+ const currentVersion = true ? "0.0.0-dev+f2e63af1" : "unknown";
558
+ console.log(`Current: ${currentVersion}`);
559
+ console.log("Updating via npm...\n");
560
+ try {
561
+ const env = { ...process.env };
562
+ delete env.npm_config_registry;
563
+ execSync2("npm install -g @fml-inc/fml@latest", {
564
+ stdio: "inherit",
565
+ timeout: 12e4,
566
+ env
567
+ });
568
+ console.log("\nUpdated. Run `fml install --skip-build` to reconfigure.");
569
+ } catch (err) {
570
+ Sentry.captureException(err);
571
+ const msg = err instanceof Error ? err.stderr?.toString() ?? err.message : String(err);
572
+ console.error("Update failed:", msg);
573
+ process.exit(1);
574
+ }
575
+ }
576
+
577
+ // src/auth/oauth.ts
578
+ import crypto from "crypto";
579
+ import http from "http";
580
+ async function fetchWorkosClientId() {
581
+ const { ConvexHttpClient } = await import("convex/browser");
582
+ const client = new ConvexHttpClient(CONVEX_URL);
583
+ const ref = "public_config:getAuthConfig";
584
+ const result = await client.query(ref, {});
585
+ return result.workosClientId;
586
+ }
587
+ async function login() {
588
+ const clientId = await fetchWorkosClientId();
589
+ const codeVerifier = crypto.randomBytes(32).toString("base64url");
590
+ const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
591
+ const state = crypto.randomBytes(16).toString("hex");
592
+ const { port, waitForCode, close } = await startCallbackServer(state);
593
+ const redirectUri = `http://localhost:${port}/callback`;
594
+ const authUrl = new URL(`${WORKOS_AUTH_URL}/user_management/authorize`);
595
+ authUrl.searchParams.set("client_id", clientId);
596
+ authUrl.searchParams.set("redirect_uri", redirectUri);
597
+ authUrl.searchParams.set("response_type", "code");
598
+ authUrl.searchParams.set("code_challenge", codeChallenge);
599
+ authUrl.searchParams.set("code_challenge_method", "S256");
600
+ authUrl.searchParams.set("provider", "authkit");
601
+ authUrl.searchParams.set("state", state);
602
+ const open = (await import("open")).default;
603
+ await open(authUrl.toString());
604
+ console.log("\nOpening browser for login...");
605
+ console.log(`If it doesn't open, visit: ${authUrl.toString()}
606
+ `);
607
+ const code = await waitForCode();
608
+ close();
609
+ const tokenResponse = await fetch(
610
+ `${WORKOS_API_URL}/user_management/authenticate`,
611
+ {
612
+ method: "POST",
613
+ headers: { "Content-Type": "application/json" },
614
+ body: JSON.stringify({
615
+ grant_type: "authorization_code",
616
+ client_id: clientId,
617
+ code,
618
+ code_verifier: codeVerifier,
619
+ redirect_uri: redirectUri
620
+ })
621
+ }
622
+ );
623
+ if (!tokenResponse.ok) {
624
+ const error = await tokenResponse.text();
625
+ throw new Error(`Token exchange failed: ${error}`);
626
+ }
627
+ const data = await tokenResponse.json();
628
+ const userName = [data.user.first_name, data.user.last_name].filter(Boolean).join(" ");
629
+ writeTokens({
630
+ accessToken: data.access_token,
631
+ refreshToken: data.refresh_token,
632
+ expiresAt: Date.now() + (data.expires_in ?? 300) * 1e3,
633
+ user: {
634
+ id: data.user.id,
635
+ email: data.user.email,
636
+ name: userName
637
+ },
638
+ workosClientId: clientId
639
+ });
640
+ return { email: data.user.email, name: userName };
641
+ }
642
+ function startCallbackServer(expectedState) {
643
+ return new Promise((resolve, reject) => {
644
+ let resolveCode;
645
+ let rejectCode;
646
+ const codePromise = new Promise((res, rej) => {
647
+ resolveCode = res;
648
+ rejectCode = rej;
649
+ });
650
+ const server = http.createServer((req, res) => {
651
+ const url = new URL(req.url ?? "/", `http://localhost`);
652
+ if (url.pathname === "/callback") {
653
+ const code = url.searchParams.get("code");
654
+ const error = url.searchParams.get("error");
655
+ const returnedState = url.searchParams.get("state");
656
+ if (returnedState !== expectedState) {
657
+ res.writeHead(400, { "Content-Type": "text/html" });
658
+ res.end(
659
+ "<html><body><h1>Login failed</h1><p>Invalid state parameter. You can close this tab.</p></body></html>"
660
+ );
661
+ rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF attack"));
662
+ return;
663
+ }
664
+ if (error) {
665
+ res.writeHead(200, { "Content-Type": "text/html" });
666
+ res.end(
667
+ "<html><body><h1>Login failed</h1><p>You can close this tab.</p></body></html>"
668
+ );
669
+ rejectCode(new Error(`OAuth error: ${error}`));
670
+ return;
671
+ }
672
+ if (code) {
673
+ res.writeHead(200, { "Content-Type": "text/html" });
674
+ res.end(
675
+ "<html><body><h1>Login successful!</h1><p>You can close this tab and return to the terminal.</p></body></html>"
676
+ );
677
+ resolveCode(code);
678
+ return;
679
+ }
680
+ res.writeHead(400);
681
+ res.end("Missing code parameter");
682
+ return;
683
+ }
684
+ res.writeHead(404);
685
+ res.end("Not found");
686
+ });
687
+ server.listen(0, "127.0.0.1", () => {
688
+ const addr = server.address();
689
+ if (!addr || typeof addr === "string") {
690
+ reject(new Error("Failed to start callback server"));
691
+ return;
692
+ }
693
+ resolve({
694
+ port: addr.port,
695
+ waitForCode: () => codePromise,
696
+ close: () => server.close()
697
+ });
698
+ });
699
+ server.on("error", reject);
700
+ setTimeout(
701
+ () => {
702
+ rejectCode(new Error("Login timed out (5 minutes)"));
703
+ server.close();
704
+ },
705
+ 5 * 60 * 1e3
706
+ );
707
+ });
708
+ }
709
+
710
+ // src/commands/login.ts
711
+ async function linkGitHubIdentity() {
712
+ const token = resolveGitHubToken();
713
+ if (!token) {
714
+ console.warn("[fml] No GitHub token available \u2014 skipping identity link");
715
+ return;
716
+ }
717
+ try {
718
+ const response = await fetch("https://api.github.com/user", {
719
+ headers: {
720
+ Authorization: `Bearer ${token}`,
721
+ Accept: "application/vnd.github+json"
722
+ }
723
+ });
724
+ if (!response.ok) {
725
+ console.warn(
726
+ `[fml] GitHub API returned ${response.status} \u2014 skipping identity link`
727
+ );
728
+ return;
729
+ }
730
+ const user = await response.json();
731
+ const fmlToken = await getValidToken();
732
+ if (!fmlToken) {
733
+ console.warn("[fml] No FML token available \u2014 skipping identity link");
734
+ return;
735
+ }
736
+ if (!CONVEX_URL) {
737
+ console.warn(
738
+ "[fml] No Convex URL configured \u2014 skipping identity link (run fml install first)"
739
+ );
740
+ return;
741
+ }
742
+ const { ConvexHttpClient } = await import("convex/browser");
743
+ const client = new ConvexHttpClient(CONVEX_URL);
744
+ client.setAuth(fmlToken);
745
+ const ref = "user/panopticon:linkGitHubIdentity";
746
+ await client.mutation(ref, {
747
+ githubUsername: user.login,
748
+ githubId: user.id,
749
+ githubEmail: user.email ?? void 0
750
+ });
751
+ console.log(`Linked GitHub account: ${user.login}`);
752
+ } catch (err) {
753
+ const msg = err instanceof Error ? err.message : String(err);
754
+ console.warn(`[fml] Failed to link GitHub identity: ${msg}`);
755
+ }
756
+ }
757
+ async function selectOrg() {
758
+ try {
759
+ const token = await getValidToken();
760
+ if (!token) return;
761
+ const api = createApiClient(token);
762
+ const orgs = await api.queryOrgs();
763
+ if (orgs.length === 0) return;
764
+ const org = orgs[0];
765
+ const slug = org.slug ?? org.name;
766
+ setSelectedOrg(slug);
767
+ console.log(`Selected org: ${org.name} (${slug})`);
768
+ } catch {
769
+ }
770
+ }
771
+ async function handleLogin() {
772
+ const { name: envName } = getActiveEnv();
773
+ const existingToken = await getValidToken();
774
+ if (existingToken) {
775
+ const { readTokens: readTokens2 } = await import("./token-store-67FJZTYS.js");
776
+ const stored = readTokens2();
777
+ console.log(
778
+ `Already logged in as ${stored?.user.name} (${stored?.user.email}) on ${envName}.`
779
+ );
780
+ await linkGitHubIdentity();
781
+ await selectOrg();
782
+ console.log("You're all set! Restart Claude Code to use FML tools.");
783
+ process.exit(0);
784
+ }
785
+ console.log(`Signing in to FML (${envName})...`);
786
+ try {
787
+ const { email, name } = await login();
788
+ console.log(`
789
+ Logged in as ${name} (${email})`);
790
+ await linkGitHubIdentity();
791
+ await selectOrg();
792
+ console.log("You're all set! Restart Claude Code to use FML tools.");
793
+ process.exit(0);
794
+ } catch (err) {
795
+ Sentry.captureException(err);
796
+ const msg = err instanceof Error ? err.message : String(err);
797
+ console.error(`Login failed: ${msg}`);
798
+ process.exit(1);
799
+ }
800
+ }
801
+
802
+ // src/commands/logout.ts
803
+ import fs3 from "fs";
804
+ function handleLogout() {
805
+ try {
806
+ fs3.unlinkSync(AUTH_STORE_PATH);
807
+ } catch {
808
+ }
809
+ console.log("Logged out. Stored credentials cleared.");
810
+ }
811
+
812
+ // src/commands/org.ts
813
+ async function handleOrg(slug) {
814
+ const token = await getValidToken();
815
+ if (!token) {
816
+ console.error("Not logged in. Run `fml login` first.");
817
+ process.exit(1);
818
+ }
819
+ const api = createApiClient(token);
820
+ const orgs = await api.queryOrgs();
821
+ if (orgs.length === 0) {
822
+ console.error("No organizations found.");
823
+ process.exit(1);
824
+ }
825
+ const current = getSelectedOrg();
826
+ if (!slug) {
827
+ console.log("Organizations:\n");
828
+ for (const org of orgs) {
829
+ const orgSlug = org.slug ?? org.name;
830
+ const marker = orgSlug === current ? " (selected)" : "";
831
+ console.log(` ${orgSlug}${marker}`);
832
+ }
833
+ if (!current) {
834
+ console.log("\nNo org selected. Run `fml org <slug>` to select one.");
835
+ }
836
+ return;
837
+ }
838
+ const match = orgs.find(
839
+ (o) => (o.slug ?? o.name) === slug || o.name === slug
840
+ );
841
+ if (!match) {
842
+ console.error(
843
+ `Organization "${slug}" not found. Available: ${orgs.map((o) => o.slug ?? o.name).join(", ")}`
844
+ );
845
+ process.exit(1);
846
+ }
847
+ const matchSlug = match.slug ?? match.name;
848
+ setSelectedOrg(matchSlug);
849
+ console.log(`Selected org: ${match.name} (${matchSlug})`);
850
+ }
851
+
852
+ // src/commands/open.ts
853
+ var APP_BASE_URL = "https://app.fml.app";
854
+ async function handleOpen(opts) {
855
+ const api = await getAuthenticatedClient();
856
+ if (!api) {
857
+ console.error("Not authenticated. Run `fml login` first.");
858
+ process.exit(1);
859
+ }
860
+ let slug;
861
+ try {
862
+ const orgs = await api.queryOrgs();
863
+ if (orgs.length === 0) {
864
+ console.error("No organizations found.");
865
+ process.exit(1);
866
+ }
867
+ slug = orgs[0].slug ?? orgs[0].name;
868
+ } catch {
869
+ console.error("Could not fetch organizations. Opening base URL.");
870
+ slug = "";
871
+ }
872
+ const url = slug ? `${APP_BASE_URL}/${slug}` : APP_BASE_URL;
873
+ if (opts.json) {
874
+ console.log(JSON.stringify({ url }));
875
+ } else {
876
+ const openBrowser = (await import("open")).default;
877
+ await openBrowser(url);
878
+ }
879
+ }
880
+
881
+ // src/commands/status.ts
882
+ async function handleStatus() {
883
+ const { name, convexUrl } = getActiveEnv();
884
+ console.log(`Environment: ${name}`);
885
+ console.log(` Convex: ${convexUrl}
886
+ `);
887
+ const tokens = readTokens();
888
+ if (!tokens) {
889
+ console.log("Not authenticated. Run `fml login` to sign in.");
890
+ return;
891
+ }
892
+ const tokenValid = await getValidToken();
893
+ console.log("Authentication:");
894
+ console.log(` User: ${tokens.user.name} (${tokens.user.email})`);
895
+ console.log(
896
+ ` Token: ${tokenValid ? "valid" : "expired (needs refresh/re-login)"}`
897
+ );
898
+ console.log(` Org: ${tokens.orgSlug ?? "(none \u2014 run `fml org <slug>`)"}`);
899
+ }
900
+
901
+ // src/commands/sync.ts
902
+ import {
903
+ addTarget as addTarget2,
904
+ removeTarget,
905
+ listTargets as listTargets2,
906
+ loadSyncConfig as loadSyncConfig2,
907
+ saveSyncConfig
908
+ } from "@fml-inc/panopticon/sync";
909
+ import { syncPending as syncPending2, syncReset } from "@fml-inc/panopticon/api";
910
+ var FML_SYNC_URL = CONVEX_URL.replace(".cloud", ".site");
911
+ async function handleSyncSetup() {
912
+ console.log("FML Sync Setup\n");
913
+ const token = resolveGitHubToken();
914
+ if (!token) {
915
+ console.error(
916
+ "No GitHub token found. Set PANOPTICON_GITHUB_TOKEN or install gh CLI."
917
+ );
918
+ process.exit(1);
919
+ }
920
+ console.log("GitHub token: found");
921
+ addTarget2({
922
+ name: DEFAULT_TARGET_NAME,
923
+ url: FML_SYNC_URL,
924
+ tokenCommand: "gh auth token"
925
+ });
926
+ console.log(`
927
+ Sync target "${DEFAULT_TARGET_NAME}" configured:`);
928
+ console.log(` URL: ${FML_SYNC_URL}`);
929
+ console.log(` Auth: gh auth token (refreshed every 5 min)`);
930
+ console.log(`
931
+ Restart panopticon to activate: fml stop && fml start`);
932
+ }
933
+ async function handleSyncList() {
934
+ const targets = listTargets2();
935
+ if (targets.length === 0) {
936
+ console.log(
937
+ "No sync targets configured. Run `fml sync setup` or `fml sync add`."
938
+ );
939
+ return;
940
+ }
941
+ console.log("Sync targets:\n");
942
+ for (const t of targets) {
943
+ const auth = t.token ? "static token" : t.tokenCommand ? `command: ${t.tokenCommand}` : "none";
944
+ console.log(` ${t.name}`);
945
+ console.log(` URL: ${t.url}`);
946
+ console.log(` Auth: ${auth}`);
947
+ }
948
+ }
949
+ async function handleSyncAdd(name, url, opts) {
950
+ const existing = listTargets2();
951
+ if (existing.some((t) => t.name === name)) {
952
+ console.error(
953
+ `Target "${name}" already exists. Use \`fml sync edit\` to modify it.`
954
+ );
955
+ process.exit(1);
956
+ }
957
+ addTarget2({
958
+ name,
959
+ url,
960
+ tokenCommand: opts.tokenCmd,
961
+ token: opts.token
962
+ });
963
+ console.log(`Sync target "${name}" added:`);
964
+ console.log(` URL: ${url}`);
965
+ if (opts.tokenCmd) console.log(` Auth: command: ${opts.tokenCmd}`);
966
+ else if (opts.token) console.log(" Auth: static token");
967
+ else console.log(" Auth: none");
968
+ console.log("\nRestart panopticon to activate: fml stop && fml start");
969
+ }
970
+ async function handleSyncRemove(name) {
971
+ const removed = removeTarget(name);
972
+ if (removed) {
973
+ console.log(`Sync target "${name}" removed.`);
974
+ console.log("Restart panopticon to apply: fml stop && fml start");
975
+ } else {
976
+ console.error(`Target "${name}" not found.`);
977
+ process.exit(1);
978
+ }
979
+ }
980
+ async function handleSyncEdit(name, opts) {
981
+ const config2 = loadSyncConfig2();
982
+ const target = config2.targets.find((t) => t.name === name);
983
+ if (!target) {
984
+ console.error(`Target "${name}" not found.`);
985
+ process.exit(1);
986
+ }
987
+ if (opts.url) target.url = opts.url;
988
+ if (opts.tokenCmd) {
989
+ target.tokenCommand = opts.tokenCmd;
990
+ delete target.token;
991
+ }
992
+ if (opts.token) {
993
+ target.token = opts.token;
994
+ delete target.tokenCommand;
995
+ }
996
+ saveSyncConfig(config2);
997
+ console.log(`Sync target "${name}" updated:`);
998
+ console.log(` URL: ${target.url}`);
999
+ const auth = target.token ? "static token" : target.tokenCommand ? `command: ${target.tokenCommand}` : "none";
1000
+ console.log(` Auth: ${auth}`);
1001
+ console.log("\nRestart panopticon to apply: fml stop && fml start");
1002
+ }
1003
+ async function handleSyncStatus() {
1004
+ const targets = listTargets2();
1005
+ if (targets.length === 0) {
1006
+ console.log("No sync targets configured. Run `fml sync setup` first.");
1007
+ return;
1008
+ }
1009
+ for (const target of targets) {
1010
+ const auth = target.token ? "static token" : target.tokenCommand ? `command: ${target.tokenCommand}` : "none";
1011
+ console.log(`${target.name}:`);
1012
+ console.log(` URL: ${target.url}`);
1013
+ console.log(` Auth: ${auth}`);
1014
+ try {
1015
+ const result = await syncPending2(target.name);
1016
+ if (result.totalPending === 0) {
1017
+ console.log(" Status: up to date");
1018
+ } else {
1019
+ console.log(` Pending: ${result.totalPending} total`);
1020
+ for (const [table, info] of Object.entries(result.tables)) {
1021
+ console.log(
1022
+ ` ${table}: ${info.pending} pending (${info.synced} / ${info.total})`
1023
+ );
1024
+ }
1025
+ }
1026
+ } catch (err) {
1027
+ const msg = err instanceof Error ? err.message : String(err);
1028
+ console.log(` Status: unavailable (${msg})`);
1029
+ }
1030
+ console.log();
1031
+ }
1032
+ }
1033
+ async function handleSyncReset(targetName) {
1034
+ await syncReset(targetName ?? DEFAULT_TARGET_NAME);
1035
+ console.log(
1036
+ `Sync watermarks for "${targetName ?? DEFAULT_TARGET_NAME}" reset to 0.`
1037
+ );
1038
+ }
1039
+
1040
+ // src/commands/env.ts
1041
+ import { listTargets as listTargets3 } from "@fml-inc/panopticon/sync";
1042
+ function handleEnvShow() {
1043
+ const { name, convexUrl } = getActiveEnv();
1044
+ console.log(`Environment: ${name}`);
1045
+ console.log(` Convex URL: ${convexUrl}`);
1046
+ }
1047
+ async function handleEnvSwitch(target) {
1048
+ const targets = listTargets3();
1049
+ const match = targets.find((t) => t.name === target);
1050
+ if (!match) {
1051
+ console.error(`Sync target "${target}" not found. Available targets:`);
1052
+ for (const t of targets) {
1053
+ console.error(` ${t.name} ${t.url}`);
1054
+ }
1055
+ console.error("\nAdd a target with `fml sync add`.");
1056
+ process.exit(1);
1057
+ }
1058
+ writeEnvConfig({ active: target });
1059
+ const convexUrl = match.url.replace(".convex.site", ".convex.cloud");
1060
+ console.log(`Switched to ${target}: ${convexUrl}`);
1061
+ console.log("\nRestarting daemons...");
1062
+ handleStop();
1063
+ await handleStart();
1064
+ console.log(
1065
+ "MCP server will pick up the change on next Claude Code session."
1066
+ );
1067
+ }
1068
+
1069
+ // src/commands/data.ts
1070
+ async function queryBackend(toolName, args) {
1071
+ const api = await getAuthenticatedClient();
1072
+ if (!api) {
1073
+ console.error("Not authenticated. Run `fml login` to sign in.");
1074
+ process.exit(1);
1075
+ }
1076
+ const result = await api.callBackend(toolName, args);
1077
+ if (!result.ok) {
1078
+ console.error(result.error ?? "Unknown error");
1079
+ process.exit(1);
1080
+ }
1081
+ console.log(JSON.stringify(result.result, null, 2));
1082
+ }
1083
+ async function handleActivity(opts) {
1084
+ await queryBackend("get-engineering-activity", {
1085
+ timeRange: opts.since ?? "24h"
1086
+ });
1087
+ }
1088
+ async function handleSessions(opts) {
1089
+ await queryBackend("list-engineering-sessions", {
1090
+ timeRange: opts.since ?? "24h",
1091
+ limit: opts.limit ? parseInt(opts.limit, 10) : void 0
1092
+ });
1093
+ }
1094
+ async function handleTimeline(sessionId, opts) {
1095
+ await queryBackend("get-session-timeline", {
1096
+ sessionId,
1097
+ limit: opts.limit ? parseInt(opts.limit, 10) : void 0,
1098
+ offset: opts.offset ? parseInt(opts.offset, 10) : void 0
1099
+ });
1100
+ }
1101
+ async function handleSpending(opts) {
1102
+ await queryBackend("get-ai-spending", {
1103
+ timeRange: opts.since ?? "7d",
1104
+ groupBy: opts.groupBy
1105
+ });
1106
+ }
1107
+ async function handleSearch(query, opts) {
1108
+ await queryBackend("search-engineering-sessions", {
1109
+ query,
1110
+ timeRange: opts.since ?? "7d",
1111
+ limit: opts.limit ? parseInt(opts.limit, 10) : void 0
1112
+ });
1113
+ }
1114
+
1115
+ // src/commands/query.ts
1116
+ var GENERIC_PROVIDERS = [
1117
+ "sentry",
1118
+ "slack",
1119
+ "github",
1120
+ "linear",
1121
+ "notion",
1122
+ "freshdesk"
1123
+ ];
1124
+ var POSTHOG_ENDPOINTS = {
1125
+ insights: "query-posthog-insights",
1126
+ events: "query-posthog-events",
1127
+ recordings: "query-posthog-recordings",
1128
+ "feature-flags": "query-posthog-feature-flags",
1129
+ experiments: "query-posthog-experiments",
1130
+ surveys: "query-posthog-surveys",
1131
+ hogql: "run-posthog-hogql",
1132
+ cohorts: "query-posthog-cohorts"
1133
+ };
1134
+ var AMPLITUDE_ENDPOINTS = {
1135
+ events: "query-amplitude-events",
1136
+ funnel: "query-amplitude-funnel",
1137
+ retention: "query-amplitude-retention",
1138
+ chart: "get-amplitude-chart",
1139
+ "list-events": "list-amplitude-events"
1140
+ };
1141
+ var META_ADS_ENDPOINTS = {
1142
+ accounts: "list-meta-ad-accounts",
1143
+ campaigns: "list-meta-campaigns",
1144
+ insights: "get-meta-ad-insights",
1145
+ "ad-sets": "list-meta-ad-sets"
1146
+ };
1147
+ function parseBody(body) {
1148
+ if (!body) return {};
1149
+ try {
1150
+ return JSON.parse(body);
1151
+ } catch {
1152
+ console.error("Invalid JSON in --body argument.");
1153
+ process.exit(1);
1154
+ }
1155
+ }
1156
+ async function handleQuery(provider, endpoint, opts) {
1157
+ const api = await getAuthenticatedClient();
1158
+ if (!api) {
1159
+ console.error("Not authenticated. Run `fml login`.");
1160
+ process.exit(1);
1161
+ }
1162
+ const parsedBody = parseBody(opts.body);
1163
+ let toolName;
1164
+ let args;
1165
+ if (GENERIC_PROVIDERS.includes(provider)) {
1166
+ toolName = `integration-${provider}`;
1167
+ args = {
1168
+ endpoint,
1169
+ method: opts.method || "GET",
1170
+ body: parsedBody,
1171
+ projectId: opts.projectId
1172
+ };
1173
+ } else if (provider === "posthog") {
1174
+ toolName = POSTHOG_ENDPOINTS[endpoint];
1175
+ if (!toolName) {
1176
+ console.error(
1177
+ `Unknown posthog endpoint: ${endpoint}. Valid: ${Object.keys(POSTHOG_ENDPOINTS).join(", ")}`
1178
+ );
1179
+ process.exit(1);
1180
+ }
1181
+ args = { ...parsedBody };
1182
+ } else if (provider === "amplitude") {
1183
+ toolName = AMPLITUDE_ENDPOINTS[endpoint];
1184
+ if (!toolName) {
1185
+ console.error(
1186
+ `Unknown amplitude endpoint: ${endpoint}. Valid: ${Object.keys(AMPLITUDE_ENDPOINTS).join(", ")}`
1187
+ );
1188
+ process.exit(1);
1189
+ }
1190
+ args = endpoint === "list-events" ? {} : { ...parsedBody };
1191
+ } else if (provider === "meta-ads") {
1192
+ toolName = META_ADS_ENDPOINTS[endpoint];
1193
+ if (!toolName) {
1194
+ console.error(
1195
+ `Unknown meta-ads endpoint: ${endpoint}. Valid: ${Object.keys(META_ADS_ENDPOINTS).join(", ")}`
1196
+ );
1197
+ process.exit(1);
1198
+ }
1199
+ args = endpoint === "accounts" ? {} : { ...parsedBody };
1200
+ } else {
1201
+ console.error(
1202
+ `Unknown provider: ${provider}. Valid: ${[...GENERIC_PROVIDERS, "posthog", "amplitude", "meta-ads"].join(", ")}`
1203
+ );
1204
+ process.exit(1);
1205
+ }
1206
+ const result = await api.callBackend(toolName, args);
1207
+ if (!result.ok) {
1208
+ console.error(result.error || "Request failed");
1209
+ process.exit(1);
1210
+ }
1211
+ console.log(JSON.stringify(result.result, null, 2));
1212
+ }
1213
+
1214
+ // src/commands/automation.ts
1215
+ async function handleAutomationList() {
1216
+ const api = await getAuthenticatedClient();
1217
+ if (!api) {
1218
+ console.error("Not authenticated. Run `fml login`.");
1219
+ process.exit(1);
1220
+ }
1221
+ const result = await api.callBackend("list-automations", {});
1222
+ if (!result.ok) {
1223
+ console.error(result.error || "Request failed");
1224
+ process.exit(1);
1225
+ }
1226
+ console.log(JSON.stringify(result.result, null, 2));
1227
+ }
1228
+ async function handleAutomationCreate(opts) {
1229
+ const api = await getAuthenticatedClient();
1230
+ if (!api) {
1231
+ console.error("Not authenticated. Run `fml login`.");
1232
+ process.exit(1);
1233
+ }
1234
+ const result = await api.callBackend("create-automation", {
1235
+ name: opts.name,
1236
+ prompt: opts.prompt,
1237
+ frequency: opts.frequency,
1238
+ hour: parseInt(opts.hour, 10),
1239
+ minute: parseInt(opts.minute, 10),
1240
+ timezone: opts.timezone,
1241
+ dayOfWeek: opts.dayOfWeek ? parseInt(opts.dayOfWeek, 10) : void 0,
1242
+ dayOfMonth: opts.dayOfMonth ? parseInt(opts.dayOfMonth, 10) : void 0,
1243
+ maxRuns: opts.maxRuns ? parseInt(opts.maxRuns, 10) : void 0
1244
+ });
1245
+ if (!result.ok) {
1246
+ console.error(result.error || "Request failed");
1247
+ process.exit(1);
1248
+ }
1249
+ console.log(JSON.stringify(result.result, null, 2));
1250
+ }
1251
+ async function handleAutomationCreatePattern(opts) {
1252
+ const api = await getAuthenticatedClient();
1253
+ if (!api) {
1254
+ console.error("Not authenticated. Run `fml login`.");
1255
+ process.exit(1);
1256
+ }
1257
+ const result = await api.callBackend("create-pattern-automation", {
1258
+ name: opts.name,
1259
+ prompt: opts.prompt
1260
+ });
1261
+ if (!result.ok) {
1262
+ console.error(result.error || "Request failed");
1263
+ process.exit(1);
1264
+ }
1265
+ console.log(JSON.stringify(result.result, null, 2));
1266
+ }
1267
+ async function handleAutomationUpdate(id, opts) {
1268
+ const api = await getAuthenticatedClient();
1269
+ if (!api) {
1270
+ console.error("Not authenticated. Run `fml login`.");
1271
+ process.exit(1);
1272
+ }
1273
+ const result = await api.callBackend("update-automation", {
1274
+ automationId: id,
1275
+ name: opts.name,
1276
+ prompt: opts.prompt,
1277
+ enabled: opts.enabled !== void 0 ? (() => {
1278
+ if (opts.enabled !== "true" && opts.enabled !== "false") {
1279
+ console.error(
1280
+ `Invalid --enabled value: "${opts.enabled}". Use "true" or "false".`
1281
+ );
1282
+ process.exit(1);
1283
+ }
1284
+ return opts.enabled === "true";
1285
+ })() : void 0,
1286
+ frequency: opts.frequency,
1287
+ hour: opts.hour ? parseInt(opts.hour, 10) : void 0,
1288
+ minute: opts.minute ? parseInt(opts.minute, 10) : void 0,
1289
+ timezone: opts.timezone,
1290
+ dayOfWeek: opts.dayOfWeek ? parseInt(opts.dayOfWeek, 10) : void 0,
1291
+ dayOfMonth: opts.dayOfMonth ? parseInt(opts.dayOfMonth, 10) : void 0
1292
+ });
1293
+ if (!result.ok) {
1294
+ console.error(result.error || "Request failed");
1295
+ process.exit(1);
1296
+ }
1297
+ console.log(JSON.stringify(result.result, null, 2));
1298
+ }
1299
+ async function handleAutomationDelete(id) {
1300
+ const api = await getAuthenticatedClient();
1301
+ if (!api) {
1302
+ console.error("Not authenticated. Run `fml login`.");
1303
+ process.exit(1);
1304
+ }
1305
+ const result = await api.callBackend("delete-automation", {
1306
+ automationId: id
1307
+ });
1308
+ if (!result.ok) {
1309
+ console.error(result.error || "Request failed");
1310
+ process.exit(1);
1311
+ }
1312
+ console.log(JSON.stringify(result.result, null, 2));
1313
+ }
1314
+ async function handleAutomationTest(id) {
1315
+ const api = await getAuthenticatedClient();
1316
+ if (!api) {
1317
+ console.error("Not authenticated. Run `fml login`.");
1318
+ process.exit(1);
1319
+ }
1320
+ const result = await api.callBackend("test-automation", {
1321
+ automationId: id
1322
+ });
1323
+ if (!result.ok) {
1324
+ console.error(result.error || "Request failed");
1325
+ process.exit(1);
1326
+ }
1327
+ console.log(JSON.stringify(result.result, null, 2));
1328
+ }
1329
+
1330
+ // src/commands/memory.ts
1331
+ async function handleMemoryList(opts) {
1332
+ const api = await getAuthenticatedClient();
1333
+ if (!api) {
1334
+ console.error("Not authenticated. Run `fml login`.");
1335
+ process.exit(1);
1336
+ }
1337
+ const result = await api.callBackend("list-memory-files", {
1338
+ scope: opts.scope
1339
+ });
1340
+ if (!result.ok) {
1341
+ console.error(result.error || "Request failed");
1342
+ process.exit(1);
1343
+ }
1344
+ console.log(JSON.stringify(result.result, null, 2));
1345
+ }
1346
+ async function handleMemoryRead(fileId) {
1347
+ const api = await getAuthenticatedClient();
1348
+ if (!api) {
1349
+ console.error("Not authenticated. Run `fml login`.");
1350
+ process.exit(1);
1351
+ }
1352
+ const result = await api.callBackend("read-memory-file", { fileId });
1353
+ if (!result.ok) {
1354
+ console.error(result.error || "Request failed");
1355
+ process.exit(1);
1356
+ }
1357
+ console.log(JSON.stringify(result.result, null, 2));
1358
+ }
1359
+ async function handleMemoryWrite(opts) {
1360
+ const api = await getAuthenticatedClient();
1361
+ if (!api) {
1362
+ console.error("Not authenticated. Run `fml login`.");
1363
+ process.exit(1);
1364
+ }
1365
+ const result = await api.callBackend("write-memory-file", {
1366
+ title: opts.title,
1367
+ content: opts.content,
1368
+ scope: opts.scope
1369
+ });
1370
+ if (!result.ok) {
1371
+ console.error(result.error || "Request failed");
1372
+ process.exit(1);
1373
+ }
1374
+ console.log(JSON.stringify(result.result, null, 2));
1375
+ }
1376
+ async function handleMemoryDelete(fileId) {
1377
+ const api = await getAuthenticatedClient();
1378
+ if (!api) {
1379
+ console.error("Not authenticated. Run `fml login`.");
1380
+ process.exit(1);
1381
+ }
1382
+ const result = await api.callBackend("delete-memory-file", { fileId });
1383
+ if (!result.ok) {
1384
+ console.error(result.error || "Request failed");
1385
+ process.exit(1);
1386
+ }
1387
+ console.log(JSON.stringify(result.result, null, 2));
1388
+ }
1389
+
1390
+ // src/commands/analysis.ts
1391
+ async function handleSearchAnalysis(query, opts) {
1392
+ const api = await getAuthenticatedClient();
1393
+ if (!api) {
1394
+ console.error("Not authenticated. Run `fml login`.");
1395
+ process.exit(1);
1396
+ }
1397
+ const result = await api.callBackend("search-analysis", {
1398
+ query,
1399
+ status: opts.status,
1400
+ limit: opts.limit ? parseInt(opts.limit, 10) : void 0
1401
+ });
1402
+ if (!result.ok) {
1403
+ console.error(result.error || "Request failed");
1404
+ process.exit(1);
1405
+ }
1406
+ console.log(JSON.stringify(result.result, null, 2));
1407
+ }
1408
+ async function handleRunAnalysis(opts) {
1409
+ const api = await getAuthenticatedClient();
1410
+ if (!api) {
1411
+ console.error("Not authenticated. Run `fml login`.");
1412
+ process.exit(1);
1413
+ }
1414
+ const result = await api.callBackend("run-analysis-workflow", {
1415
+ selectedPromptKeys: opts.prompts?.split(",")
1416
+ });
1417
+ if (!result.ok) {
1418
+ console.error(result.error || "Request failed");
1419
+ process.exit(1);
1420
+ }
1421
+ console.log(JSON.stringify(result.result, null, 2));
1422
+ }
1423
+
1424
+ // src/commands/integrations.ts
1425
+ async function handleIntegrations() {
1426
+ const api = await getAuthenticatedClient();
1427
+ if (!api) {
1428
+ console.error("Not authenticated. Run `fml login`.");
1429
+ process.exit(1);
1430
+ }
1431
+ const result = await api.callBackend("list-integrations", {});
1432
+ if (!result.ok) {
1433
+ console.error(result.error || "Request failed");
1434
+ process.exit(1);
1435
+ }
1436
+ console.log(JSON.stringify(result.result, null, 2));
1437
+ }
1438
+ async function handleEvents(opts) {
1439
+ const api = await getAuthenticatedClient();
1440
+ if (!api) {
1441
+ console.error("Not authenticated. Run `fml login`.");
1442
+ process.exit(1);
1443
+ }
1444
+ const result = await api.callBackend("get-integration-events", {
1445
+ source: opts.source,
1446
+ eventType: opts.eventType,
1447
+ projectId: opts.projectId,
1448
+ timeRange: opts.since
1449
+ });
1450
+ if (!result.ok) {
1451
+ console.error(result.error || "Request failed");
1452
+ process.exit(1);
1453
+ }
1454
+ console.log(JSON.stringify(result.result, null, 2));
1455
+ }
1456
+ async function handleResolveIdentity(opts) {
1457
+ const api = await getAuthenticatedClient();
1458
+ if (!api) {
1459
+ console.error("Not authenticated. Run `fml login`.");
1460
+ process.exit(1);
1461
+ }
1462
+ const result = await api.callBackend("resolve-identities", {
1463
+ provider: opts.provider,
1464
+ externalUsername: opts.username,
1465
+ normalizedEmail: opts.email,
1466
+ externalId: opts.externalId,
1467
+ externalUserId: opts.externalUserId
1468
+ });
1469
+ if (!result.ok) {
1470
+ console.error(result.error || "Request failed");
1471
+ process.exit(1);
1472
+ }
1473
+ console.log(JSON.stringify(result.result, null, 2));
1474
+ }
1475
+
1476
+ // src/commands/slack.ts
1477
+ async function handleSlackHistory(opts) {
1478
+ const api = await getAuthenticatedClient();
1479
+ if (!api) {
1480
+ console.error("Not authenticated. Run `fml login`.");
1481
+ process.exit(1);
1482
+ }
1483
+ const result = await api.callBackend("get-slack-channel-history", {
1484
+ channelId: opts.channel,
1485
+ limit: opts.limit ? parseInt(opts.limit, 10) : void 0
1486
+ });
1487
+ if (!result.ok) {
1488
+ console.error(result.error || "Request failed");
1489
+ process.exit(1);
1490
+ }
1491
+ console.log(JSON.stringify(result.result, null, 2));
1492
+ }
1493
+ async function handleSlackMessage(opts) {
1494
+ const api = await getAuthenticatedClient();
1495
+ if (!api) {
1496
+ console.error("Not authenticated. Run `fml login`.");
1497
+ process.exit(1);
1498
+ }
1499
+ const result = await api.callBackend("get-slack-message", {
1500
+ permalink: opts.permalink,
1501
+ channelId: opts.channel,
1502
+ messageTs: opts.ts,
1503
+ includeThreadContext: opts.includeThread
1504
+ });
1505
+ if (!result.ok) {
1506
+ console.error(result.error || "Request failed");
1507
+ process.exit(1);
1508
+ }
1509
+ console.log(JSON.stringify(result.result, null, 2));
1510
+ }
1511
+
1512
+ // src/commands/messages.ts
1513
+ async function handleMessagesList(opts) {
1514
+ const api = await getAuthenticatedClient();
1515
+ if (!api) {
1516
+ console.error("Not authenticated. Run `fml login`.");
1517
+ process.exit(1);
1518
+ }
1519
+ const result = await api.callBackend("list-messages", {
1520
+ startTime: opts.start ? parseInt(opts.start, 10) : void 0,
1521
+ endTime: opts.end ? parseInt(opts.end, 10) : void 0,
1522
+ limit: opts.limit ? parseInt(opts.limit, 10) : void 0,
1523
+ cursor: opts.cursor
1524
+ });
1525
+ if (!result.ok) {
1526
+ console.error(result.error || "Request failed");
1527
+ process.exit(1);
1528
+ }
1529
+ console.log(JSON.stringify(result.result, null, 2));
1530
+ }
1531
+ async function handleMessagesContext(messageId, opts) {
1532
+ const api = await getAuthenticatedClient();
1533
+ if (!api) {
1534
+ console.error("Not authenticated. Run `fml login`.");
1535
+ process.exit(1);
1536
+ }
1537
+ const result = await api.callBackend("list-messages-with-context", {
1538
+ messageId,
1539
+ before: opts.before ? parseInt(opts.before, 10) : void 0,
1540
+ after: opts.after ? parseInt(opts.after, 10) : void 0
1541
+ });
1542
+ if (!result.ok) {
1543
+ console.error(result.error || "Request failed");
1544
+ process.exit(1);
1545
+ }
1546
+ console.log(JSON.stringify(result.result, null, 2));
1547
+ }
1548
+
1549
+ // src/commands/skills.ts
1550
+ async function handleSkillsList() {
1551
+ const api = await getAuthenticatedClient();
1552
+ if (!api) {
1553
+ console.error("Not authenticated. Run `fml login`.");
1554
+ process.exit(1);
1555
+ }
1556
+ const result = await api.callBackend("list-skills", {});
1557
+ if (!result.ok) {
1558
+ console.error(result.error || "Request failed");
1559
+ process.exit(1);
1560
+ }
1561
+ console.log(JSON.stringify(result.result, null, 2));
1562
+ }
1563
+ async function handleSkillsLoad(skillId) {
1564
+ const api = await getAuthenticatedClient();
1565
+ if (!api) {
1566
+ console.error("Not authenticated. Run `fml login`.");
1567
+ process.exit(1);
1568
+ }
1569
+ const result = await api.callBackend("load-skill", { skillId });
1570
+ if (!result.ok) {
1571
+ console.error(result.error || "Request failed");
1572
+ process.exit(1);
1573
+ }
1574
+ console.log(JSON.stringify(result.result, null, 2));
1575
+ }
1576
+
1577
+ // src/commands/config-snapshots.ts
1578
+ async function handleConfigList(opts) {
1579
+ const api = await getAuthenticatedClient();
1580
+ if (!api) {
1581
+ console.error("Not authenticated. Run `fml login`.");
1582
+ process.exit(1);
1583
+ }
1584
+ try {
1585
+ const result = opts.repo ? await api.listRepoConfigSnapshots(opts.org, opts.repo) : await api.listUserConfigSnapshots(opts.org);
1586
+ console.log(JSON.stringify(result, null, 2));
1587
+ } catch (err) {
1588
+ console.error(err instanceof Error ? err.message : String(err));
1589
+ process.exit(1);
1590
+ }
1591
+ }
1592
+ async function handleConfigDetail(opts) {
1593
+ const api = await getAuthenticatedClient();
1594
+ if (!api) {
1595
+ console.error("Not authenticated. Run `fml login`.");
1596
+ process.exit(1);
1597
+ }
1598
+ try {
1599
+ const result = opts.repo ? await api.getRepoConfigDetail(opts.org, opts.repo) : opts.user ? await api.getUserConfigDetail(opts.org, opts.user) : null;
1600
+ if (!result) {
1601
+ console.error("Provide --user or --repo");
1602
+ process.exit(1);
1603
+ }
1604
+ console.log(JSON.stringify(result, null, 2));
1605
+ } catch (err) {
1606
+ console.error(err instanceof Error ? err.message : String(err));
1607
+ process.exit(1);
1608
+ }
1609
+ }
1610
+
1611
+ // src/cli.ts
1612
+ ensureNativeModules();
1613
+ await initSentry();
1614
+ if (process.argv.length <= 2 || process.argv.includes("--help") || process.argv.includes("-h")) {
1615
+ printBanner();
1616
+ }
1617
+ var program = new Command().name("fml").description("FML agent tools for Claude Code").version(
1618
+ true ? "0.0.0-dev+f2e63af1" : "dev"
1619
+ );
1620
+ program.command("tools", { hidden: true }).description("List all available CLI commands (including hidden agent tools)").action(() => {
1621
+ function formatCmd(cmd, prefix = "") {
1622
+ const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
1623
+ const args = cmd.registeredArguments.map((a) => a.required ? `<${a.name()}>` : `[${a.name()}]`).join(" ");
1624
+ const opts = cmd.options.filter(
1625
+ (o) => !o.hidden && o.long !== "--help" && o.long !== "--version"
1626
+ ).map((o) => o.mandatory ? o.flags : `[${o.flags}]`).join(" ");
1627
+ const usage = [name, args, opts].filter(Boolean).join(" ");
1628
+ return `${usage}
1629
+ ${cmd.description()}`;
1630
+ }
1631
+ const lines = program.commands.filter((c) => c.name() !== "tools" && c.name() !== "help").flatMap((c) => {
1632
+ if (c.commands.length > 0) {
1633
+ return c.commands.map((s) => formatCmd(s, c.name()));
1634
+ }
1635
+ return [formatCmd(c)];
1636
+ });
1637
+ console.log(lines.join("\n\n"));
1638
+ });
1639
+ program.command("install").description("Register plugin, hooks, and daemons").action(() => handleInstall());
1640
+ program.command("uninstall").description("Remove fml plugin, hooks, and optionally all data").option(
1641
+ "--target <target>",
1642
+ "Target CLI: claude, gemini, codex, claude-desktop, all"
1643
+ ).option("--purge", "Also remove all data, logs, and auth tokens").action((opts) => handleUninstall(opts));
1644
+ program.command("update").description("Update fml to the latest version").action(() => handleUpdate());
1645
+ program.command("login").description("Sign in to your FML account").action(() => handleLogin());
1646
+ program.command("logout").description("Sign out and clear stored credentials").action(() => handleLogout());
1647
+ program.command("org").description("Show or select organization").argument("[slug]", "Org slug to select").action((slug) => handleOrg(slug));
1648
+ program.command("status").description("Show current auth and daemon status").action(() => handleStatus());
1649
+ program.command("doctor").description("Check FML and panopticon configuration and connectivity").option("--json", "Output as JSON").action((opts) => handleDoctor(opts));
1650
+ program.command("open").description("Open FML dashboard in browser").option("--json", "Output URL as JSON instead of opening browser").action((opts) => handleOpen(opts));
1651
+ program.command("env").description("Show or switch environment (dev/prod)").argument("[target]", 'Environment name ("dev", "prod") or custom Convex URL').action((target) => {
1652
+ if (target) handleEnvSwitch(target);
1653
+ else handleEnvShow();
1654
+ });
1655
+ program.command("start").description("Start panopticon server and sync").action(() => handleStart());
1656
+ program.command("stop").description("Stop panopticon server and sync").action(() => handleStop());
1657
+ var sync = program.command("sync").description("Manage sync configuration");
1658
+ sync.command("setup").description("Configure sync targets (convenience shortcut)").action(() => handleSyncSetup());
1659
+ sync.command("list").description("List all sync targets").action(() => handleSyncList());
1660
+ sync.command("add").description("Add a sync target").argument("<name>", "Unique target name").argument("<url>", "OTLP HTTP endpoint URL").option("--token-cmd <cmd>", "Shell command that returns a bearer token").option("--token <token>", "Static bearer token").action(
1661
+ (name, url, opts) => handleSyncAdd(name, url, opts)
1662
+ );
1663
+ sync.command("remove").description("Remove a sync target").argument("<name>", "Target name to remove").action((name) => handleSyncRemove(name));
1664
+ sync.command("edit").description("Edit a sync target").argument("<name>", "Target name to edit").option("--url <url>", "New OTLP HTTP endpoint URL").option("--token-cmd <cmd>", "New shell command for bearer token").option("--token <token>", "New static bearer token").action(
1665
+ (name, opts) => handleSyncEdit(name, opts)
1666
+ );
1667
+ sync.command("status").description("Show sync config and watermarks").action(() => handleSyncStatus());
1668
+ sync.command("reset").description("Reset sync watermarks").argument("[name]", "Target name (resets all if omitted)").action((name) => handleSyncReset(name));
1669
+ program.command("activity").description("Activity summary \u2014 sessions, prompts, tools, costs").option("--since <duration>", 'Time window, e.g. "24h", "7d"').action((opts) => handleActivity(opts));
1670
+ 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));
1671
+ 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));
1672
+ 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));
1673
+ 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));
1674
+ program.command("query", { hidden: true }).description("Query a connected integration").argument(
1675
+ "<provider>",
1676
+ "Provider name (sentry, slack, github, linear, notion, freshdesk, posthog, amplitude, meta-ads)"
1677
+ ).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));
1678
+ var automation = program.command("automation", { hidden: true }).description("Manage automations");
1679
+ automation.command("list").description("List all automations").action(() => handleAutomationList());
1680
+ automation.command("create-scheduled").description("Create a scheduled automation").requiredOption("--name <name>", "Automation name").requiredOption("--prompt <prompt>", "What to run").requiredOption("--frequency <freq>", "hourly, daily, weekly, or monthly").requiredOption("--hour <hour>", "Hour (0-23)").requiredOption("--minute <minute>", "Minute (0-59)").requiredOption("--timezone <tz>", "IANA timezone, e.g. America/Chicago").option("--day-of-week <day>", "Day of week (0=Sun..6=Sat) for weekly").option("--day-of-month <day>", "Day of month (1-31) for monthly").option("--max-runs <n>", "Max runs (1 for one-time)").action((opts) => handleAutomationCreate(opts));
1681
+ automation.command("create-pattern").description("Create an event-triggered automation").requiredOption("--name <name>", "Automation name").requiredOption("--prompt <prompt>", "What to watch for and do").action((opts) => handleAutomationCreatePattern(opts));
1682
+ automation.command("update").description("Update an automation").argument("<id>", "Automation ID").option("--name <name>", "New name").option("--prompt <prompt>", "New prompt").option("--enabled <bool>", "Enable or disable (true/false)").option("--frequency <freq>", "New frequency").option("--hour <hour>", "New hour").option("--minute <minute>", "New minute").option("--timezone <tz>", "New timezone").option("--day-of-week <day>", "New day of week").option("--day-of-month <day>", "New day of month").action((id, opts) => handleAutomationUpdate(id, opts));
1683
+ automation.command("delete").description("Delete an automation").argument("<id>", "Automation ID").action((id) => handleAutomationDelete(id));
1684
+ automation.command("test").description("Preview/test an automation").argument("<id>", "Automation ID").action((id) => handleAutomationTest(id));
1685
+ var memory = program.command("memory", { hidden: true }).description("Manage memory files");
1686
+ memory.command("list").description("List memory files").option("--scope <scope>", "Scope: project, org, or user").action((opts) => handleMemoryList(opts));
1687
+ memory.command("read").description("Read a memory file").argument("<file-id>", "Memory file ID").action((fileId) => handleMemoryRead(fileId));
1688
+ memory.command("write").description("Create or update a memory file").requiredOption("--title <title>", "File title").requiredOption("--content <content>", "File content").option("--scope <scope>", "Scope: project, org, or user").action((opts) => handleMemoryWrite(opts));
1689
+ memory.command("delete").description("Delete a memory file").argument("<file-id>", "Memory file ID").action((fileId) => handleMemoryDelete(fileId));
1690
+ program.command("search-analysis", { hidden: true }).description("Search codebase analysis results").argument("<query>", "Search query").option("--status <status>", "Filter by status").option("--limit <n>", "Max results").action((query, opts) => handleSearchAnalysis(query, opts));
1691
+ program.command("run-analysis", { hidden: true }).description("Run deep codebase analysis workflows").option(
1692
+ "--prompts <list>",
1693
+ "Comma-separated: security,architecture,code-quality,performance,ux,dependencies,cost,ai-architecture,ai-security"
1694
+ ).action((opts) => handleRunAnalysis(opts));
1695
+ program.command("integrations", { hidden: true }).description("List connected integrations").action(() => handleIntegrations());
1696
+ program.command("events", { hidden: true }).description("Recent integration events (deploys, releases, errors)").option(
1697
+ "--source <source>",
1698
+ "Filter: github, sentry, linear, amplitude, stripe"
1699
+ ).option("--event-type <type>", "Filter by event type").option("--project-id <id>", "Filter by project").option("--since <duration>", "Time range: 1h, 6h, 24h, 7d, 30d").action((opts) => handleEvents(opts));
1700
+ program.command("resolve-identity", { hidden: true }).description("Resolve external identity to FML user").argument("[username]", "External username (shorthand for --username)").option("--provider <p>", "Provider: github, linear, slack").option("--username <u>", "External username").option("--email <e>", "Email address").option("--external-id <id>", "External ID").option("--external-user-id <id>", "External user ID").action(
1701
+ (username, opts) => handleResolveIdentity(username ? { ...opts, username } : opts)
1702
+ );
1703
+ var slack = program.command("slack", { hidden: true }).description("Slack shortcuts");
1704
+ slack.command("history").description("Recent messages from a channel").option("--channel <id>", "Channel ID").option("--limit <n>", "Max messages").action((opts) => handleSlackHistory(opts));
1705
+ slack.command("message").description("Fetch a specific message").option("--permalink <url>", "Message permalink").option("--channel <id>", "Channel ID").option("--ts <timestamp>", "Message timestamp").option("--include-thread", "Include thread context").action((opts) => handleSlackMessage(opts));
1706
+ var messages = program.command("messages", { hidden: true }).description("Conversation messages");
1707
+ messages.command("list").description("List messages from a conversation").option("--start <timestamp>", "Start time (ms epoch)").option("--end <timestamp>", "End time (ms epoch)").option("--limit <n>", "Max messages").option("--cursor <cursor>", "Pagination cursor").action((opts) => handleMessagesList(opts));
1708
+ messages.command("context").description("Get messages around a specific message").argument("<message-id>", "Message ID").option("--before <n>", "Messages before").option("--after <n>", "Messages after").action((messageId, opts) => handleMessagesContext(messageId, opts));
1709
+ var skills = program.command("skills", { hidden: true }).description("Browse and load skills");
1710
+ skills.command("list").description("List available skills").action(() => handleSkillsList());
1711
+ skills.command("load").description("Load a skill by ID").argument("<skill-id>", "Skill ID").action((skillId) => handleSkillsLoad(skillId));
1712
+ var config = program.command("config", { hidden: true }).description("View team config snapshots");
1713
+ config.command("list").description("List config snapshots for an org").requiredOption("--org <slug>", "Organization slug").option("--repo <full-name>", "Filter by repo (owner/name)").action((opts) => handleConfigList(opts));
1714
+ config.command("detail").description("Get config snapshot detail").requiredOption("--org <slug>", "Organization slug").option("--user <username>", "GitHub username").option("--repo <full-name>", "Repository (owner/name)").action((opts) => handleConfigDetail(opts));
1715
+ program.parseAsync(process.argv).catch((err) => {
1716
+ Sentry.captureException(err);
1717
+ console.error(err);
1718
+ process.exit(1);
1719
+ });
1720
+ //# sourceMappingURL=cli.js.map