@fml-inc/fml 0.0.29 → 0.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +10 -0
- package/dist/{chunk-TXQRKU2Y.js → chunk-CUBBYHH7.js} +92 -53
- package/dist/chunk-CUBBYHH7.js.map +1 -0
- package/dist/{chunk-RJOFOTNH.js → chunk-I5BHNW37.js} +6 -140
- package/dist/chunk-I5BHNW37.js.map +1 -0
- package/dist/cli.js +109 -1808
- package/dist/cli.js.map +1 -1
- package/dist/mcp/server.js +12 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/{token-store-NE7AM2PH.js → token-store-HOFFHNNN.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-2CRFCFE4.js +0 -11
- package/dist/chunk-2CRFCFE4.js.map +0 -1
- package/dist/chunk-NLFKYSN7.js +0 -640
- package/dist/chunk-NLFKYSN7.js.map +0 -1
- package/dist/chunk-RJOFOTNH.js.map +0 -1
- package/dist/chunk-TXQRKU2Y.js.map +0 -1
- package/dist/chunk-UALBZA5X.js +0 -123
- package/dist/chunk-UALBZA5X.js.map +0 -1
- package/dist/doctor-YV6KQEGW.js +0 -2019
- package/dist/doctor-YV6KQEGW.js.map +0 -1
- package/dist/esm-GTDP7OMQ-EP6UYLQG.js +0 -14442
- package/dist/esm-GTDP7OMQ-EP6UYLQG.js.map +0 -1
- /package/dist/{token-store-NE7AM2PH.js.map → token-store-HOFFHNNN.js.map} +0 -0
package/README.md
CHANGED
|
@@ -16,6 +16,16 @@ Then sign in:
|
|
|
16
16
|
fml login
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
## For LLM agents
|
|
20
|
+
|
|
21
|
+
Setting up FML from inside a coding-agent sandbox (Claude Code, Codex, Gemini CLI, etc.)? Paste this to your agent:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Follow the installation instructions here: https://github.com/fml-inc/observe/blob/main/docs/agent_installation_guide.md
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The guide covers: `npm install -g @fml-inc/fml` → `fml install` → `fml login --device` (the agent relays the verification URL to you, you paste the code back) → `fml doctor`. No browser, no `gh` CLI, no manual shell-profile edits needed.
|
|
28
|
+
|
|
19
29
|
## What it does
|
|
20
30
|
|
|
21
31
|
**Local observability** — Panopticon captures OpenTelemetry signals, hook events, session files, and API traffic from Claude Code, Gemini CLI, and Codex CLI into a local SQLite database.
|
|
@@ -35,9 +35,7 @@ function writeEnvConfig(config) {
|
|
|
35
35
|
mode: 384
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
function
|
|
39
|
-
const config = readEnvConfig();
|
|
40
|
-
const name = config.active;
|
|
38
|
+
function resolveEnvConvexUrl(envName) {
|
|
41
39
|
try {
|
|
42
40
|
const panoDataDir = process.env.PANOPTICON_DATA_DIR ?? (process.platform === "darwin" ? path.join(
|
|
43
41
|
os.homedir(),
|
|
@@ -50,20 +48,31 @@ function getActiveEnv() {
|
|
|
50
48
|
) : path.join(os.homedir(), ".local", "share", "panopticon"));
|
|
51
49
|
const raw = fs.readFileSync(path.join(panoDataDir, "config.json"), "utf-8");
|
|
52
50
|
const panoConf = JSON.parse(raw);
|
|
53
|
-
const target = panoConf.sync?.targets?.find((t) => t.name ===
|
|
51
|
+
const target = panoConf.sync?.targets?.find((t) => t.name === envName);
|
|
54
52
|
if (target) {
|
|
55
|
-
|
|
56
|
-
return { name, convexUrl };
|
|
53
|
+
return target.url.replace(".convex.site", ".convex.cloud");
|
|
57
54
|
}
|
|
58
55
|
} catch {
|
|
59
56
|
}
|
|
60
|
-
return
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function getActiveEnv() {
|
|
60
|
+
const name = readEnvConfig().active;
|
|
61
|
+
return { name, convexUrl: resolveEnvConvexUrl(name) };
|
|
62
|
+
}
|
|
63
|
+
function authStorePathFor(envName) {
|
|
64
|
+
return path.join(FML_DATA_DIR, `auth.${envName}.json`);
|
|
61
65
|
}
|
|
62
|
-
var
|
|
63
|
-
|
|
66
|
+
var ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
67
|
+
function isValidEnvName(envName) {
|
|
68
|
+
return ENV_NAME_RE.test(envName);
|
|
69
|
+
}
|
|
70
|
+
var CONVEX_URL = (process.env.FML_CONVEX_URL ?? getActiveEnv().convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
|
|
64
71
|
var WORKOS_API_URL = "https://api.workos.com";
|
|
65
72
|
var WORKOS_AUTH_URL = "https://auth.fml.inc";
|
|
66
|
-
|
|
73
|
+
function authStorePath() {
|
|
74
|
+
return authStorePathFor(getActiveEnv().name);
|
|
75
|
+
}
|
|
67
76
|
function getSiteUrl() {
|
|
68
77
|
return CONVEX_URL.replace(".convex.cloud", ".convex.site").replace(
|
|
69
78
|
/:\d+$/,
|
|
@@ -111,23 +120,26 @@ var Sentry = {
|
|
|
111
120
|
};
|
|
112
121
|
|
|
113
122
|
// src/auth/token-store.ts
|
|
114
|
-
function
|
|
115
|
-
|
|
123
|
+
function storePathFor(envName) {
|
|
124
|
+
return envName ? authStorePathFor(envName) : authStorePath();
|
|
125
|
+
}
|
|
126
|
+
function ensureDir(envName) {
|
|
127
|
+
const dir = path2.dirname(storePathFor(envName));
|
|
116
128
|
if (!fs2.existsSync(dir)) {
|
|
117
129
|
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
118
130
|
}
|
|
119
131
|
}
|
|
120
|
-
function readTokens() {
|
|
132
|
+
function readTokens(envName) {
|
|
121
133
|
try {
|
|
122
|
-
const data = fs2.readFileSync(
|
|
134
|
+
const data = fs2.readFileSync(storePathFor(envName), "utf-8");
|
|
123
135
|
return JSON.parse(data);
|
|
124
136
|
} catch {
|
|
125
137
|
return null;
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
|
-
function writeTokens(auth) {
|
|
129
|
-
ensureDir();
|
|
130
|
-
fs2.writeFileSync(
|
|
140
|
+
function writeTokens(auth, envName) {
|
|
141
|
+
ensureDir(envName);
|
|
142
|
+
fs2.writeFileSync(storePathFor(envName), JSON.stringify(auth, null, 2), {
|
|
131
143
|
mode: 384
|
|
132
144
|
});
|
|
133
145
|
}
|
|
@@ -139,17 +151,26 @@ function setSelectedOrg(orgSlug) {
|
|
|
139
151
|
if (!stored) return;
|
|
140
152
|
writeTokens({ ...stored, orgSlug });
|
|
141
153
|
}
|
|
142
|
-
var
|
|
143
|
-
var
|
|
154
|
+
var ACTIVE_KEY = "__active__";
|
|
155
|
+
var serviceTokenCaches = /* @__PURE__ */ new Map();
|
|
156
|
+
var serviceRefreshPromises = /* @__PURE__ */ new Map();
|
|
157
|
+
var oauthRefreshPromises = /* @__PURE__ */ new Map();
|
|
158
|
+
var cacheKey = (envName) => envName ?? ACTIVE_KEY;
|
|
144
159
|
var SERVICE_ACCESS_TOKEN_PATH = path2.join(FML_DATA_DIR, "access_token");
|
|
145
|
-
function getSiteUrl2() {
|
|
160
|
+
function getSiteUrl2(envName) {
|
|
146
161
|
const explicit = process.env.CONVEX_SITE_URL;
|
|
147
162
|
if (explicit) return explicit.replace(/\/$/, "");
|
|
163
|
+
if (envName) {
|
|
164
|
+
const envConvexUrl = resolveEnvConvexUrl(envName);
|
|
165
|
+
if (envConvexUrl) {
|
|
166
|
+
return envConvexUrl.replace(".convex.cloud", ".convex.site");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
148
169
|
return CONVEX_URL.replace(".convex.cloud", ".convex.site");
|
|
149
170
|
}
|
|
150
|
-
async function refreshServiceToken(refreshToken2) {
|
|
171
|
+
async function refreshServiceToken(refreshToken2, envName) {
|
|
151
172
|
try {
|
|
152
|
-
const response = await fetch(`${getSiteUrl2()}/api/tokens/refresh`, {
|
|
173
|
+
const response = await fetch(`${getSiteUrl2(envName)}/api/tokens/refresh`, {
|
|
153
174
|
method: "POST",
|
|
154
175
|
headers: {
|
|
155
176
|
"Content-Type": "application/json",
|
|
@@ -167,10 +188,10 @@ async function refreshServiceToken(refreshToken2) {
|
|
|
167
188
|
console.error(`[fml] Service token refresh error: ${data.error}`);
|
|
168
189
|
return null;
|
|
169
190
|
}
|
|
170
|
-
|
|
191
|
+
serviceTokenCaches.set(cacheKey(envName), {
|
|
171
192
|
accessToken: data.accessToken,
|
|
172
193
|
expiresAt: data.expiresAt
|
|
173
|
-
};
|
|
194
|
+
});
|
|
174
195
|
try {
|
|
175
196
|
const dir = path2.dirname(SERVICE_ACCESS_TOKEN_PATH);
|
|
176
197
|
if (!fs2.existsSync(dir)) {
|
|
@@ -188,58 +209,75 @@ async function refreshServiceToken(refreshToken2) {
|
|
|
188
209
|
return null;
|
|
189
210
|
}
|
|
190
211
|
}
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
async function getValidToken(opts) {
|
|
213
|
+
const envName = opts?.env;
|
|
214
|
+
const key = cacheKey(envName);
|
|
193
215
|
const envToken = process.env.FML_TOKEN;
|
|
194
216
|
if (envToken?.startsWith("fml_srt_")) {
|
|
195
|
-
|
|
196
|
-
|
|
217
|
+
const cached = serviceTokenCaches.get(key);
|
|
218
|
+
if (cached && cached.expiresAt > Date.now() + 6e4) {
|
|
219
|
+
return cached.accessToken;
|
|
197
220
|
}
|
|
198
|
-
|
|
199
|
-
|
|
221
|
+
const inflight2 = serviceRefreshPromises.get(key);
|
|
222
|
+
if (inflight2) return inflight2;
|
|
223
|
+
const promise2 = refreshServiceToken(envToken, envName);
|
|
224
|
+
serviceRefreshPromises.set(key, promise2);
|
|
200
225
|
try {
|
|
201
|
-
return await
|
|
226
|
+
return await promise2;
|
|
202
227
|
} finally {
|
|
203
|
-
|
|
228
|
+
serviceRefreshPromises.delete(key);
|
|
204
229
|
}
|
|
205
230
|
}
|
|
206
231
|
if (envToken) return envToken;
|
|
207
|
-
const stored = readTokens();
|
|
232
|
+
const stored = readTokens(envName);
|
|
208
233
|
if (!stored) {
|
|
209
|
-
console.error(
|
|
234
|
+
console.error(
|
|
235
|
+
envName ? `[fml] Auth: no stored tokens for env "${envName}"` : "[fml] Auth: no stored tokens"
|
|
236
|
+
);
|
|
210
237
|
return null;
|
|
211
238
|
}
|
|
212
239
|
if (stored.expiresAt > Date.now() + 6e4) {
|
|
213
240
|
return stored.accessToken;
|
|
214
241
|
}
|
|
215
242
|
if (stored.tokenType === "service") {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
const inflight2 = serviceRefreshPromises.get(key);
|
|
244
|
+
if (inflight2) return inflight2;
|
|
245
|
+
const promise2 = (async () => {
|
|
246
|
+
const newAccessToken = await refreshServiceToken(
|
|
247
|
+
stored.refreshToken,
|
|
248
|
+
envName
|
|
249
|
+
);
|
|
250
|
+
const cached = serviceTokenCaches.get(key);
|
|
251
|
+
if (newAccessToken && cached) {
|
|
252
|
+
writeTokens(
|
|
253
|
+
{
|
|
254
|
+
...stored,
|
|
255
|
+
accessToken: newAccessToken,
|
|
256
|
+
expiresAt: cached.expiresAt
|
|
257
|
+
},
|
|
258
|
+
envName
|
|
259
|
+
);
|
|
225
260
|
}
|
|
226
261
|
return newAccessToken;
|
|
227
262
|
})();
|
|
263
|
+
serviceRefreshPromises.set(key, promise2);
|
|
228
264
|
try {
|
|
229
|
-
return await
|
|
265
|
+
return await promise2;
|
|
230
266
|
} finally {
|
|
231
|
-
|
|
267
|
+
serviceRefreshPromises.delete(key);
|
|
232
268
|
}
|
|
233
269
|
}
|
|
234
|
-
|
|
235
|
-
|
|
270
|
+
const inflight = oauthRefreshPromises.get(key);
|
|
271
|
+
if (inflight) return inflight;
|
|
272
|
+
const promise = refreshToken(stored, envName);
|
|
273
|
+
oauthRefreshPromises.set(key, promise);
|
|
236
274
|
try {
|
|
237
|
-
return await
|
|
275
|
+
return await promise;
|
|
238
276
|
} finally {
|
|
239
|
-
|
|
277
|
+
oauthRefreshPromises.delete(key);
|
|
240
278
|
}
|
|
241
279
|
}
|
|
242
|
-
async function refreshToken(stored) {
|
|
280
|
+
async function refreshToken(stored, envName) {
|
|
243
281
|
if (!stored.workosClientId) {
|
|
244
282
|
console.error(
|
|
245
283
|
"[fml] Auth: token expired, missing workosClientId \u2014 run `fml login`"
|
|
@@ -279,7 +317,7 @@ async function refreshToken(stored) {
|
|
|
279
317
|
orgSlug: stored.orgSlug,
|
|
280
318
|
workosClientId: stored.workosClientId
|
|
281
319
|
};
|
|
282
|
-
writeTokens(refreshed);
|
|
320
|
+
writeTokens(refreshed, envName);
|
|
283
321
|
return refreshed.accessToken;
|
|
284
322
|
} catch (err) {
|
|
285
323
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -298,10 +336,11 @@ export {
|
|
|
298
336
|
DEFAULT_TARGET_NAME,
|
|
299
337
|
writeEnvConfig,
|
|
300
338
|
getActiveEnv,
|
|
339
|
+
isValidEnvName,
|
|
301
340
|
CONVEX_URL,
|
|
302
341
|
WORKOS_API_URL,
|
|
303
342
|
WORKOS_AUTH_URL,
|
|
304
|
-
|
|
343
|
+
authStorePath,
|
|
305
344
|
getSiteUrl,
|
|
306
345
|
readTokens,
|
|
307
346
|
writeTokens,
|
|
@@ -309,4 +348,4 @@ export {
|
|
|
309
348
|
setSelectedOrg,
|
|
310
349
|
getValidToken
|
|
311
350
|
};
|
|
312
|
-
//# sourceMappingURL=chunk-
|
|
351
|
+
//# sourceMappingURL=chunk-CUBBYHH7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/token-store.ts","../src/config.ts","../src/sentry.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n CONVEX_URL,\n WORKOS_API_URL,\n authStorePath,\n authStorePathFor,\n resolveEnvConvexUrl,\n} from \"../config.js\";\nimport { FML_DATA_DIR } from \"../dirs.js\";\nimport { Sentry } from \"../sentry.js\";\n\ninterface StoredAuth {\n accessToken: string;\n refreshToken: string;\n expiresAt: number; // Unix timestamp in ms\n user: {\n id: string;\n email: string;\n name: string;\n };\n orgSlug?: string;\n /** WorkOS client ID used to obtain these tokens (needed for refresh) */\n workosClientId?: string;\n /** Token type: \"oauth\" (default) for WorkOS JWT, \"service\" for device flow tokens */\n tokenType?: \"oauth\" | \"service\";\n}\n\nfunction storePathFor(envName?: string): string {\n return envName ? authStorePathFor(envName) : authStorePath();\n}\n\nfunction ensureDir(envName?: string): void {\n const dir = path.dirname(storePathFor(envName));\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n}\n\nexport function readTokens(envName?: string): StoredAuth | null {\n try {\n const data = fs.readFileSync(storePathFor(envName), \"utf-8\");\n return JSON.parse(data) as StoredAuth;\n } catch {\n return null;\n }\n}\n\nexport function writeTokens(auth: StoredAuth, envName?: string): void {\n ensureDir(envName);\n fs.writeFileSync(storePathFor(envName), JSON.stringify(auth, null, 2), {\n mode: 0o600,\n });\n}\n\nexport function getSelectedOrg(): string | null {\n return readTokens()?.orgSlug ?? null;\n}\n\nexport function setSelectedOrg(orgSlug: string): void {\n const stored = readTokens();\n if (!stored) return;\n writeTokens({ ...stored, orgSlug });\n}\n\n// ── Service Token Refresh (sandbox fml_srt_* → fml_st_*) ────────────────────\n\ninterface ServiceTokenCache {\n accessToken: string;\n expiresAt: number;\n}\n\n/**\n * Caches and refresh-deduplication promises are keyed by env so in-process\n * callers that read multiple envs (`getValidToken({ env: \"a\" })` followed by\n * `getValidToken({ env: \"b\" })`) don't cross-contaminate. The sentinel\n * \"__active__\" stands in for calls with no explicit env.\n */\nconst ACTIVE_KEY = \"__active__\";\nconst serviceTokenCaches = new Map<string, ServiceTokenCache>();\nconst serviceRefreshPromises = new Map<string, Promise<string | null>>();\nconst oauthRefreshPromises = new Map<string, Promise<string | null>>();\nconst cacheKey = (envName?: string): string => envName ?? ACTIVE_KEY;\n\n/** File where the current access token is written for panopticon's tokenCommand */\nconst SERVICE_ACCESS_TOKEN_PATH = path.join(FML_DATA_DIR, \"access_token\");\n\nfunction getSiteUrl(envName?: string): string {\n const explicit = process.env.CONVEX_SITE_URL;\n if (explicit) return explicit.replace(/\\/$/, \"\");\n if (envName) {\n const envConvexUrl = resolveEnvConvexUrl(envName);\n if (envConvexUrl) {\n return envConvexUrl.replace(\".convex.cloud\", \".convex.site\");\n }\n }\n return CONVEX_URL.replace(\".convex.cloud\", \".convex.site\");\n}\n\nasync function refreshServiceToken(\n refreshToken: string,\n envName?: string,\n): Promise<string | null> {\n try {\n const response = await fetch(`${getSiteUrl(envName)}/api/tokens/refresh`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${refreshToken}`,\n },\n });\n\n if (!response.ok) {\n console.error(\n `[fml] Service token refresh failed: HTTP ${response.status}`,\n );\n return null;\n }\n\n const data = (await response.json()) as {\n ok: boolean;\n accessToken: string;\n expiresAt: number;\n error?: string;\n };\n\n if (!data.ok || !data.accessToken) {\n console.error(`[fml] Service token refresh error: ${data.error}`);\n return null;\n }\n\n serviceTokenCaches.set(cacheKey(envName), {\n accessToken: data.accessToken,\n expiresAt: data.expiresAt,\n });\n\n // Write to file for panopticon's tokenCommand\n try {\n const dir = path.dirname(SERVICE_ACCESS_TOKEN_PATH);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n fs.writeFileSync(SERVICE_ACCESS_TOKEN_PATH, data.accessToken, {\n mode: 0o600,\n });\n } catch {\n // Non-fatal — in-memory cache still works for the plugin itself\n }\n\n return data.accessToken;\n } catch (err: unknown) {\n Sentry.captureException(err);\n console.error(`[fml] Service token refresh error: ${err}`);\n return null;\n }\n}\n\n// ── Token Resolution ────────────────────────────────────────────────────────\n\n/**\n * Get a valid access token, refreshing if expired.\n *\n * Three paths:\n * 1. FML_TOKEN=fml_srt_* → exchange refresh token for short-lived access token\n * 2. FML_TOKEN=fml_st_* → use directly (legacy/CI)\n * 3. No FML_TOKEN → OAuth flow (interactive user)\n */\nexport async function getValidToken(opts?: {\n env?: string;\n}): Promise<string | null> {\n const envName = opts?.env;\n const key = cacheKey(envName);\n const envToken = process.env.FML_TOKEN;\n\n // Service refresh token → exchange for short-lived access token\n if (envToken?.startsWith(\"fml_srt_\")) {\n const cached = serviceTokenCaches.get(key);\n if (cached && cached.expiresAt > Date.now() + 60_000) {\n return cached.accessToken;\n }\n\n const inflight = serviceRefreshPromises.get(key);\n if (inflight) return inflight;\n const promise = refreshServiceToken(envToken, envName);\n serviceRefreshPromises.set(key, promise);\n try {\n return await promise;\n } finally {\n serviceRefreshPromises.delete(key);\n }\n }\n\n // Static service token (legacy) or plain env override\n if (envToken) return envToken;\n\n // Stored token path (OAuth or device flow)\n const stored = readTokens(envName);\n if (!stored) {\n console.error(\n envName\n ? `[fml] Auth: no stored tokens for env \"${envName}\"`\n : \"[fml] Auth: no stored tokens\",\n );\n return null;\n }\n\n // If token expires in more than 60 seconds, use it\n if (stored.expiresAt > Date.now() + 60_000) {\n return stored.accessToken;\n }\n\n // Device flow tokens: refresh via /api/tokens/refresh (same as sandbox tokens)\n if (stored.tokenType === \"service\") {\n const inflight = serviceRefreshPromises.get(key);\n if (inflight) return inflight;\n const promise = (async () => {\n const newAccessToken = await refreshServiceToken(\n stored.refreshToken,\n envName,\n );\n const cached = serviceTokenCaches.get(key);\n if (newAccessToken && cached) {\n writeTokens(\n {\n ...stored,\n accessToken: newAccessToken,\n expiresAt: cached.expiresAt,\n },\n envName,\n );\n }\n return newAccessToken;\n })();\n serviceRefreshPromises.set(key, promise);\n try {\n return await promise;\n } finally {\n serviceRefreshPromises.delete(key);\n }\n }\n\n // OAuth tokens: refresh via WorkOS\n const inflight = oauthRefreshPromises.get(key);\n if (inflight) return inflight;\n const promise = refreshToken(stored, envName);\n oauthRefreshPromises.set(key, promise);\n try {\n return await promise;\n } finally {\n oauthRefreshPromises.delete(key);\n }\n}\n\nasync function refreshToken(\n stored: StoredAuth,\n envName?: string,\n): Promise<string | null> {\n if (!stored.workosClientId) {\n console.error(\n \"[fml] Auth: token expired, missing workosClientId — run `fml login`\",\n );\n return null;\n }\n\n try {\n const response = await fetch(\n `${WORKOS_API_URL}/user_management/authenticate`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n client_id: stored.workosClientId,\n refresh_token: stored.refreshToken,\n }),\n },\n );\n\n if (!response.ok) {\n console.error(\n `[fml] Auth: refresh failed (HTTP ${response.status}) — token preserved`,\n );\n return null;\n }\n\n const data = (await response.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n user: {\n id: string;\n email: string;\n first_name?: string;\n last_name?: string;\n };\n };\n\n const userName = [data.user.first_name, data.user.last_name]\n .filter(Boolean)\n .join(\" \");\n\n const refreshed: StoredAuth = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Date.now() + (data.expires_in ?? 300) * 1000,\n user: {\n id: data.user.id,\n email: data.user.email,\n name: userName || stored.user.name,\n },\n orgSlug: stored.orgSlug,\n workosClientId: stored.workosClientId,\n };\n\n writeTokens(refreshed, envName);\n return refreshed.accessToken;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(\n `[fml] Auth: refresh failed (network error: ${msg}) — token preserved`,\n );\n Sentry.captureException(err);\n return null;\n }\n}\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { FML_DATA_DIR } from \"./dirs.js\";\n\n// ── Default production deployment ───────────────────────────────────────────\n\nexport const DEFAULT_PROD_URL =\n \"https://trustworthy-chihuahua-382.convex.cloud\";\n\n/** Convex site URL for sync/HTTP actions — derived from DEFAULT_PROD_URL. */\nexport const DEFAULT_SYNC_URL = DEFAULT_PROD_URL.replace(\n \".convex.cloud\",\n \".convex.site\",\n);\n\n/** Default sync target name created by `fml install` */\nexport const DEFAULT_TARGET_NAME = \"fml\";\n\n// ── Persistent env selection ────────────────────────────────────────────────\n\ninterface EnvConfig {\n /** Name of the active sync target */\n active: string;\n}\n\nconst ENV_CONFIG_PATH = path.join(FML_DATA_DIR, \"env.json\");\n\nfunction readEnvConfig(): EnvConfig {\n try {\n const raw = fs.readFileSync(ENV_CONFIG_PATH, \"utf-8\");\n return JSON.parse(raw) as EnvConfig;\n } catch {\n return { active: DEFAULT_TARGET_NAME };\n }\n}\n\nexport function writeEnvConfig(config: EnvConfig): void {\n const dir = path.dirname(ENV_CONFIG_PATH);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n fs.writeFileSync(ENV_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\\n`, {\n mode: 0o600,\n });\n}\n\n/**\n * Look up a panopticon sync target by name and return its Convex deployment\n * URL (`.convex.cloud`). Returns null if panopticon isn't installed or the\n * target isn't configured.\n */\nexport function resolveEnvConvexUrl(envName: string): string | null {\n try {\n const panoDataDir =\n process.env.PANOPTICON_DATA_DIR ??\n (process.platform === \"darwin\"\n ? path.join(\n os.homedir(),\n \"Library\",\n \"Application Support\",\n \"panopticon\",\n )\n : process.platform === \"win32\"\n ? path.join(\n process.env.APPDATA ??\n path.join(os.homedir(), \"AppData\", \"Roaming\"),\n \"panopticon\",\n )\n : path.join(os.homedir(), \".local\", \"share\", \"panopticon\"));\n const raw = fs.readFileSync(path.join(panoDataDir, \"config.json\"), \"utf-8\");\n const panoConf = JSON.parse(raw) as {\n sync?: { targets?: Array<{ name: string; url: string }> };\n };\n const target = panoConf.sync?.targets?.find((t) => t.name === envName);\n if (target) {\n return target.url.replace(\".convex.site\", \".convex.cloud\");\n }\n } catch {\n // Panopticon config not available\n }\n return null;\n}\n\n/**\n * Resolve the active environment by looking up the sync target.\n * Derives the .convex.cloud URL from the target's .convex.site URL.\n * Returns null convexUrl if the target is not found (panopticon not installed).\n */\nexport function getActiveEnv(): { name: string; convexUrl: string | null } {\n const name = readEnvConfig().active;\n return { name, convexUrl: resolveEnvConvexUrl(name) };\n}\n\n/** Auth store path for a specific env name (tokens are segregated per env). */\nexport function authStorePathFor(envName: string): string {\n return path.join(FML_DATA_DIR, `auth.${envName}.json`);\n}\n\n/**\n * Env names flow into `tokenCommand` strings that panopticon shells out, so\n * refuse anything that could carry shell metacharacters. Target names are\n * also written to panopticon's config by this rule (see `fml env use`).\n */\nconst ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;\nexport function isValidEnvName(envName: string): boolean {\n return ENV_NAME_RE.test(envName);\n}\n\n/**\n * Require a resolved Convex URL. Exits with an error if the sync target\n * is not configured (panopticon not installed).\n */\nexport function requireConvexUrl(): string {\n const { name, convexUrl } = getActiveEnv();\n if (!convexUrl) {\n console.error(\n `Sync target \"${name}\" not found. Run \\`fml install\\` or \\`fml sync setup\\`.`,\n );\n process.exit(1);\n }\n return convexUrl;\n}\n\n// ── Exports ─────────────────────────────────────────────────────────────────\n\n/**\n * Convex deployment URL (switches with `fml env`).\n *\n * Resolution order:\n * 1. `FML_CONVEX_URL` env var (dev/preview overrides)\n * 2. Active panopticon sync target\n * 3. `DEFAULT_PROD_URL` — lets a fresh `npm install -g` log in against\n * prod before `fml install` has had a chance to seed a sync target.\n */\nexport const CONVEX_URL: string = (\n process.env.FML_CONVEX_URL ??\n getActiveEnv().convexUrl ??\n DEFAULT_PROD_URL\n).replace(/\\/$/, \"\");\n\n/** WorkOS API base URL */\nexport const WORKOS_API_URL = \"https://api.workos.com\";\n\n/** WorkOS authorization base URL */\nexport const WORKOS_AUTH_URL = \"https://auth.fml.inc\";\n\n/**\n * Path to the auth token store for the *currently* active env. Resolved\n * lazily so long-running processes that outlive an `fml env use` switch\n * still read the correct file on the next call.\n */\nexport function authStorePath(): string {\n return authStorePathFor(getActiveEnv().name);\n}\n\n/** Convex site URL (HTTP actions) — derived from CONVEX_URL */\nexport function getSiteUrl(): string {\n return CONVEX_URL.replace(\".convex.cloud\", \".convex.site\").replace(\n /:\\d+$/,\n \"\",\n );\n}\n","declare const __SENTRY_DSN__: string;\n\nlet initialized = false;\nlet sentry: typeof import(\"@sentry/core\") | null = null;\n\nasync function loadSentry(): Promise<typeof import(\"@sentry/core\") | null> {\n if (sentry) return sentry;\n try {\n sentry = await import(\"@sentry/core\");\n return sentry;\n } catch {\n return null;\n }\n}\n\nexport async function initSentry(): Promise<void> {\n if (initialized) return;\n initialized = true;\n\n const mod = await loadSentry();\n if (!mod) return;\n\n const client = new mod.ServerRuntimeClient({\n dsn: __SENTRY_DSN__,\n tracesSampleRate: 0,\n integrations: [],\n stackParser: mod.createStackParser(mod.nodeStackLineParser()),\n transport: (options) =>\n mod.createTransport(options, async (request) => {\n const res = await fetch(options.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-sentry-envelope\" },\n body: request.body as BodyInit,\n });\n return { statusCode: res.status };\n }),\n });\n\n mod.setCurrentClient(client);\n client.init();\n}\n\nexport const Sentry = {\n captureException: (err: unknown) => sentry?.captureException(err),\n captureMessage: (msg: string) => sentry?.captureMessage(msg),\n} as const;\n"],"mappings":"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","names":["fs","path","path","fs","getSiteUrl","refreshToken","inflight","promise"]}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Database
|
|
3
|
-
} from "./chunk-UALBZA5X.js";
|
|
4
1
|
import {
|
|
5
2
|
CONVEX_URL,
|
|
6
3
|
getSelectedOrg,
|
|
7
4
|
getValidToken
|
|
8
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CUBBYHH7.js";
|
|
9
6
|
|
|
10
7
|
// src/native-check.ts
|
|
11
8
|
import { createRequire } from "module";
|
|
@@ -45,141 +42,10 @@ function ensureNativeModules() {
|
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
// node_modules/.pnpm/@fml-inc+panopticon@0.2.2/node_modules/@fml-inc/panopticon/dist/chunk-Z4RRVQR6.js
|
|
49
|
-
import { execFileSync } from "child_process";
|
|
50
|
-
import os from "os";
|
|
51
|
-
import path from "path";
|
|
52
|
-
var SUPERSET_MARKER = `${path.sep}.superset${path.sep}`;
|
|
53
|
-
var SUPERSET_DB_PATH = path.join(os.homedir(), ".superset", "local.db");
|
|
54
|
-
var SupersetProvider = class {
|
|
55
|
-
id = "superset";
|
|
56
|
-
db = null;
|
|
57
|
-
dbFailed = false;
|
|
58
|
-
canResolve(cwd) {
|
|
59
|
-
return cwd.includes(SUPERSET_MARKER);
|
|
60
|
-
}
|
|
61
|
-
resolve(cwd) {
|
|
62
|
-
const db = this.getDb();
|
|
63
|
-
if (!db) return null;
|
|
64
|
-
try {
|
|
65
|
-
const wt = db.prepare(
|
|
66
|
-
`SELECT p.main_repo_path, w.branch, p.github_owner, p.name as project_name
|
|
67
|
-
FROM worktrees w
|
|
68
|
-
JOIN projects p ON w.project_id = p.id
|
|
69
|
-
WHERE ? LIKE w.path || '%'
|
|
70
|
-
ORDER BY LENGTH(w.path) DESC
|
|
71
|
-
LIMIT 1`
|
|
72
|
-
).get(cwd);
|
|
73
|
-
if (wt) return this.buildResult(wt);
|
|
74
|
-
const proj = db.prepare(
|
|
75
|
-
`SELECT main_repo_path, default_branch AS branch, github_owner, name as project_name
|
|
76
|
-
FROM projects
|
|
77
|
-
WHERE ? LIKE main_repo_path || '%'
|
|
78
|
-
ORDER BY LENGTH(main_repo_path) DESC
|
|
79
|
-
LIMIT 1`
|
|
80
|
-
).get(cwd);
|
|
81
|
-
if (proj) return this.buildResult(proj);
|
|
82
|
-
} catch {
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
close() {
|
|
87
|
-
if (this.db) {
|
|
88
|
-
try {
|
|
89
|
-
this.db.close();
|
|
90
|
-
} catch {
|
|
91
|
-
}
|
|
92
|
-
this.db = null;
|
|
93
|
-
}
|
|
94
|
-
this.dbFailed = false;
|
|
95
|
-
}
|
|
96
|
-
buildResult(row) {
|
|
97
|
-
if (row.github_owner) {
|
|
98
|
-
return {
|
|
99
|
-
repo: `${row.github_owner}/${row.project_name}`,
|
|
100
|
-
branch: row.branch
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return { repoDir: row.main_repo_path, branch: row.branch };
|
|
104
|
-
}
|
|
105
|
-
getDb() {
|
|
106
|
-
if (this.dbFailed) return null;
|
|
107
|
-
if (this.db) return this.db;
|
|
108
|
-
try {
|
|
109
|
-
this.db = new Database(SUPERSET_DB_PATH, {
|
|
110
|
-
readonly: true,
|
|
111
|
-
fileMustExist: true
|
|
112
|
-
});
|
|
113
|
-
return this.db;
|
|
114
|
-
} catch {
|
|
115
|
-
this.dbFailed = true;
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
var repoCache = /* @__PURE__ */ new Map();
|
|
121
|
-
var providers = [new SupersetProvider()];
|
|
122
|
-
function resolveGitRemote(dir) {
|
|
123
|
-
try {
|
|
124
|
-
const url = execFileSync(
|
|
125
|
-
"git",
|
|
126
|
-
["-C", dir, "remote", "get-url", "origin"],
|
|
127
|
-
{
|
|
128
|
-
encoding: "utf-8",
|
|
129
|
-
timeout: 5e3,
|
|
130
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
131
|
-
}
|
|
132
|
-
).trim();
|
|
133
|
-
const sshMatch = url.match(/git@[^:]+:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
134
|
-
if (sshMatch) return sshMatch[1];
|
|
135
|
-
const httpsMatch = url.match(/\/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
136
|
-
if (httpsMatch) return httpsMatch[1];
|
|
137
|
-
} catch {
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
function resolveGitBranch(dir) {
|
|
142
|
-
try {
|
|
143
|
-
return execFileSync("git", ["-C", dir, "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
144
|
-
encoding: "utf-8",
|
|
145
|
-
timeout: 5e3,
|
|
146
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
147
|
-
}).trim() || null;
|
|
148
|
-
} catch {
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function resolveRepoFromCwd(cwd) {
|
|
153
|
-
if (repoCache.has(cwd)) return repoCache.get(cwd);
|
|
154
|
-
let result = null;
|
|
155
|
-
const repo = resolveGitRemote(cwd);
|
|
156
|
-
if (repo) {
|
|
157
|
-
result = { repo, branch: resolveGitBranch(cwd) };
|
|
158
|
-
} else {
|
|
159
|
-
for (const provider of providers) {
|
|
160
|
-
if (!provider.canResolve(cwd)) continue;
|
|
161
|
-
const resolved = provider.resolve(cwd);
|
|
162
|
-
if (!resolved) continue;
|
|
163
|
-
if (resolved.repo) {
|
|
164
|
-
result = { repo: resolved.repo, branch: resolved.branch };
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
if (resolved.repoDir) {
|
|
168
|
-
const fallbackRepo = resolveGitRemote(resolved.repoDir);
|
|
169
|
-
if (fallbackRepo) {
|
|
170
|
-
result = { repo: fallbackRepo, branch: resolved.branch };
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
repoCache.set(cwd, result);
|
|
177
|
-
return result;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
45
|
// src/convex-client.ts
|
|
181
|
-
|
|
182
|
-
|
|
46
|
+
import { resolveRepoFromCwd } from "@fml-inc/panopticon/repo";
|
|
47
|
+
function ref(path) {
|
|
48
|
+
return path;
|
|
183
49
|
}
|
|
184
50
|
function createApiClient(token) {
|
|
185
51
|
const isServiceToken = token.startsWith("fml_st_");
|
|
@@ -213,7 +79,7 @@ function createApiClient(token) {
|
|
|
213
79
|
async callBackend(toolName, args, opts) {
|
|
214
80
|
try {
|
|
215
81
|
if (isServiceToken) {
|
|
216
|
-
const { readTokens } = await import("./token-store-
|
|
82
|
+
const { readTokens } = await import("./token-store-HOFFHNNN.js");
|
|
217
83
|
const userExternalId = process.env.FML_USER_EXTERNAL_ID ?? readTokens()?.user.id;
|
|
218
84
|
const res = await fetch(`${getSiteUrl()}/api/tools/execute`, {
|
|
219
85
|
method: "POST",
|
|
@@ -316,4 +182,4 @@ export {
|
|
|
316
182
|
createApiClient,
|
|
317
183
|
getAuthenticatedClient
|
|
318
184
|
};
|
|
319
|
-
//# sourceMappingURL=chunk-
|
|
185
|
+
//# sourceMappingURL=chunk-I5BHNW37.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/native-check.ts","../src/convex-client.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { execSync } from \"node:child_process\";\nimport { rmSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Verify that better-sqlite3's native addon matches the running Node version.\n * If mismatched (e.g. user upgraded Node), attempt an automatic rebuild.\n * Call this before any code that imports better-sqlite3.\n */\nexport function ensureNativeModules(): void {\n const require = createRequire(import.meta.url);\n try {\n require(\"better-sqlite3\");\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"\";\n if (!msg.includes(\"NODE_MODULE_VERSION\")) {\n throw err;\n }\n\n console.error(\n \"[fml] better-sqlite3 was compiled for a different Node.js version, rebuilding...\",\n );\n\n const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), \"..\");\n\n // Remove stale build directory to avoid EEXIST errors from node-gyp\n const sqlitePath = dirname(require.resolve(\"better-sqlite3/package.json\"));\n rmSync(join(sqlitePath, \"build\"), { recursive: true, force: true });\n\n try {\n execSync(\"npm rebuild better-sqlite3\", {\n cwd: pkgRoot,\n stdio: \"inherit\",\n timeout: 60_000,\n });\n console.error(\"[fml] Rebuild complete.\");\n } catch {\n console.error(\n \"[fml] Automatic rebuild failed. Please run manually:\\n\" +\n ` cd ${pkgRoot} && rm -rf node_modules/better-sqlite3/build && npm rebuild better-sqlite3`,\n );\n process.exit(1);\n }\n }\n}\n","import type { FunctionReference } from \"convex/server\";\nimport { resolveRepoFromCwd } from \"@fml-inc/panopticon/repo\";\nimport { getSelectedOrg, getValidToken } from \"./auth/token-store.js\";\nimport { CONVEX_URL } from \"./config.js\";\nimport type {\n RepoConfigSnapshotDetail,\n RepoConfigSnapshotSummary,\n ResolvedRepo,\n UserConfigSnapshotDetail,\n UserConfigSnapshotSummary,\n} from \"./types.js\";\n\n// ── Shared plumbing ─────────────────────────────────────────────────────────\n\nexport interface ToolResult {\n ok: boolean;\n result?: unknown;\n error?: string;\n}\n\nexport interface OrgInfo {\n _id: string;\n name: string;\n slug?: string;\n repos?: Array<{\n _id: string;\n fullName: string;\n owner: string;\n name: string;\n private: boolean;\n }>;\n}\n\nfunction ref<T extends \"query\" | \"mutation\" | \"action\">(\n path: string,\n): FunctionReference<T> {\n return path as unknown as FunctionReference<T>;\n}\n\n// ── API client factory ──────────────────────────────────────────────────────\n\nexport function createApiClient(token: string) {\n const isServiceToken = token.startsWith(\"fml_st_\");\n\n let clientPromise: Promise<{\n query: <T>(\n ref: FunctionReference<\"query\">,\n args: Record<string, unknown>,\n ) => Promise<T>;\n mutation: <T>(\n ref: FunctionReference<\"mutation\">,\n args: Record<string, unknown>,\n ) => Promise<T>;\n action: <T>(\n ref: FunctionReference<\"action\">,\n args: Record<string, unknown>,\n ) => Promise<T>;\n }> | null = null;\n\n function getClient() {\n if (!clientPromise) {\n clientPromise = import(\"convex/browser\").then(({ ConvexHttpClient }) => {\n const client = new ConvexHttpClient(CONVEX_URL);\n client.setAuth(token);\n return client;\n });\n }\n return clientPromise;\n }\n\n /**\n * Derive the Convex site URL (HTTP actions) from the cloud URL.\n * Convex uses paired domains: *.convex.cloud for client APIs,\n * *.convex.site for HTTP actions. This is a stable Convex convention.\n */\n function getSiteUrl(): string {\n return CONVEX_URL.replace(\".convex.cloud\", \".convex.site\").replace(\n /\\/$/,\n \"\",\n );\n }\n\n return {\n // ── Orgs ──────────────────────────────────────────────────────────────\n\n async queryOrgs(): Promise<OrgInfo[]> {\n if (isServiceToken) {\n // Service tokens can't call Convex actions directly — org is embedded in the token\n return [];\n }\n const client = await getClient();\n return await client.query(ref(\"user/plugin:getMyOrgsAndRepos\"), {});\n },\n\n // ── Tool gateway ─────────────────────────────────────────────────────\n\n async callBackend(\n toolName: string,\n args: Record<string, unknown>,\n opts?: { org?: string },\n ): Promise<ToolResult> {\n try {\n if (isServiceToken) {\n // Service token path: POST to HTTP endpoint (Convex can't validate non-JWT tokens)\n // Thread user identity: sandbox agents set FML_USER_EXTERNAL_ID;\n // device-flow logins fall back to the id stashed on the stored token\n // so tools can execute with the real user context even though the\n // token's actAsExternalId is the namespaced system:cli:* string.\n const { readTokens } = await import(\"./auth/token-store.js\");\n const userExternalId =\n process.env.FML_USER_EXTERNAL_ID ?? readTokens()?.user.id;\n const res = await fetch(`${getSiteUrl()}/api/tools/execute`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n toolName,\n args,\n ...(userExternalId && { userExternalId }),\n }),\n });\n const text = await res.text();\n let data: ToolResult;\n try {\n data = JSON.parse(text) as ToolResult;\n } catch {\n return {\n ok: false,\n error: `HTTP ${res.status}: ${text.slice(0, 200)}`,\n };\n }\n if (!res.ok && !data.error) {\n return { ok: false, error: `HTTP ${res.status}` };\n }\n return data;\n }\n\n // JWT path: standard Convex action\n const client = await getClient();\n const actionArgs: Record<string, unknown> = { toolName, args };\n // Explicit org > stored org selection > repo-based inference\n const org = opts?.org ?? getSelectedOrg();\n if (org) actionArgs.org = org;\n const repo = resolveRepoFromCwd(process.cwd());\n if (repo) actionArgs.repo = repo.repo;\n\n const result = await client.action(\n ref<\"action\">(\"user/tool_gateway:executeTool\"),\n actionArgs,\n );\n return { ok: true, result };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Unauthorized\") || msg.includes(\"not authenticated\")) {\n return {\n ok: false,\n error:\n \"Authentication expired. Run `fml login` to sign in again, then restart Claude Code.\",\n };\n }\n return { ok: false, error: msg };\n }\n },\n\n // ── Repo resolution ──────────────────────────────────────────────────\n\n async resolveRepo(\n orgSlug: string,\n repoFullName: string,\n ): Promise<ResolvedRepo | null> {\n const client = await getClient();\n return await client.query(ref(\"user/cli:resolveRepo\"), {\n orgSlug,\n repoFullName,\n });\n },\n\n // ── Config snapshots ─────────────────────────────────────────────────\n\n async listUserConfigSnapshots(\n orgSlug: string,\n ): Promise<UserConfigSnapshotSummary[]> {\n const client = await getClient();\n const result = await client.query<UserConfigSnapshotSummary[] | null>(\n ref(\"user/config_snapshots:listUserSnapshots\"),\n { orgSlug },\n );\n return result ?? [];\n },\n\n async getUserConfigDetail(\n orgSlug: string,\n githubUsername: string,\n ): Promise<UserConfigSnapshotDetail | null> {\n const client = await getClient();\n return await client.query(ref(\"user/config_snapshots:getUserDetail\"), {\n orgSlug,\n githubUsername,\n });\n },\n\n async listRepoConfigSnapshots(\n orgSlug: string,\n repository?: string,\n ): Promise<RepoConfigSnapshotSummary[]> {\n const client = await getClient();\n const result = await client.query<RepoConfigSnapshotSummary[] | null>(\n ref(\"user/config_snapshots:listRepoSnapshots\"),\n { orgSlug, repository },\n );\n return result ?? [];\n },\n\n async getRepoConfigDetail(\n orgSlug: string,\n repository: string,\n ): Promise<RepoConfigSnapshotDetail | null> {\n const client = await getClient();\n return await client.query(ref(\"user/config_snapshots:getRepoDetail\"), {\n orgSlug,\n repository,\n });\n },\n };\n}\n\n// ── Convenience: auto-authenticated client ──────────────────────────────────\n\n/**\n * Create an API client using the stored auth token.\n * Returns null if not authenticated.\n */\nexport async function getAuthenticatedClient() {\n const token = await getValidToken();\n if (!token) return null;\n return createApiClient(token);\n}\n"],"mappings":"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","names":["require"]}
|