@fml-inc/fml 0.8.7 → 0.8.10-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1227 +1,699 @@
1
- // src/pi/extension.ts
2
- import { randomUUID as randomUUID3 } from "node:crypto";
3
- import fs4 from "node:fs";
4
- import os2 from "node:os";
5
- import path7 from "node:path";
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
6
10
 
7
- // ../../packages/agent-targets/src/install/pi/index.ts
8
- import fs from "node:fs";
9
- import path from "node:path";
10
- import { randomUUID } from "node:crypto";
11
- var FML_PI_EXTENSION_MARKER = "__FML_PI_EXTENSION_V1__";
12
- var LEGACY_PI_EXTENSION_MARKER = "__panopticonPiExtensionTest";
13
- var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
14
- "PANOPTICON_PORT",
15
- '"/hooks"',
16
- "UserPromptSubmit",
17
- "PreToolUse",
18
- "PostToolUse",
19
- "SessionEnd"
20
- ]);
21
- function pathApiFor(platform) {
22
- return platform === "win32" ? path.win32 : path.posix;
11
+ // src/dirs.ts
12
+ import os from "node:os";
13
+ import path3 from "node:path";
14
+ function isPresent(value) {
15
+ return value !== void 0 && value.trim().length > 0;
23
16
  }
24
- function resolvePiAgentDir(context, pathApi) {
25
- const configured = context.env.PI_CODING_AGENT_DIR?.trim();
26
- if (!configured) {
27
- return pathApi.join(context.homeDir, ".pi", "agent");
17
+ function normalizeAbsolutePath(value, variableName, pathApi) {
18
+ if (!pathApi.isAbsolute(value)) {
19
+ throw new Error(`${variableName} must be an absolute path: ${value}`);
28
20
  }
29
- if (configured === "~") return context.homeDir;
30
- if (configured.startsWith("~/") || configured.startsWith("~\\")) {
31
- return pathApi.join(context.homeDir, configured.slice(2));
21
+ return pathApi.normalize(value);
22
+ }
23
+ function explicitDirectory(value, variableName, pathApi) {
24
+ if (!isPresent(value)) return void 0;
25
+ return normalizeAbsolutePath(value, variableName, pathApi);
26
+ }
27
+ function platformBaseDirectory(value, pathApi) {
28
+ if (!isPresent(value) || !pathApi.isAbsolute(value)) return void 0;
29
+ return pathApi.normalize(value);
30
+ }
31
+ function resolveFmlDirectories(options = {}) {
32
+ const platform = options.platform ?? process.platform;
33
+ const env = options.env ?? process.env;
34
+ const pathApi = platform === "win32" ? path3.win32 : path3.posix;
35
+ const homeDir = normalizeAbsolutePath(
36
+ options.homeDir ?? os.homedir(),
37
+ "home directory",
38
+ pathApi
39
+ );
40
+ const explicitDataDir = explicitDirectory(
41
+ env.FML_DATA_DIR,
42
+ "FML_DATA_DIR",
43
+ pathApi
44
+ );
45
+ const explicitStateDir = explicitDirectory(
46
+ env.FML_STATE_DIR,
47
+ "FML_STATE_DIR",
48
+ pathApi
49
+ );
50
+ const explicitLogDir = explicitDirectory(
51
+ env.FML_LOG_DIR,
52
+ "FML_LOG_DIR",
53
+ pathApi
54
+ );
55
+ let defaultDataDir;
56
+ let defaultStateDir;
57
+ let nativeLogDir;
58
+ switch (platform) {
59
+ case "darwin": {
60
+ defaultDataDir = pathApi.join(
61
+ homeDir,
62
+ "Library",
63
+ "Application Support",
64
+ "fml"
65
+ );
66
+ defaultStateDir = defaultDataDir;
67
+ nativeLogDir = pathApi.join(homeDir, "Library", "Logs", "fml");
68
+ break;
69
+ }
70
+ case "win32": {
71
+ const roamingAppData = platformBaseDirectory(env.APPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Roaming");
72
+ const localAppData = platformBaseDirectory(env.LOCALAPPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Local");
73
+ defaultDataDir = pathApi.join(roamingAppData, "fml");
74
+ defaultStateDir = pathApi.join(localAppData, "fml");
75
+ break;
76
+ }
77
+ default: {
78
+ const xdgDataHome = platformBaseDirectory(env.XDG_DATA_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "share");
79
+ const xdgStateHome = platformBaseDirectory(env.XDG_STATE_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "state");
80
+ defaultDataDir = pathApi.join(xdgDataHome, "fml");
81
+ defaultStateDir = pathApi.join(xdgStateHome, "fml");
82
+ break;
83
+ }
32
84
  }
33
- return pathApi.normalize(configured);
85
+ const dataDir = explicitDataDir ?? defaultDataDir;
86
+ const stateDir = explicitStateDir ?? explicitDataDir ?? defaultStateDir;
87
+ const defaultLogDir = nativeLogDir ?? (platform === "win32" ? pathApi.join(defaultDataDir, "logs") : pathApi.join(defaultStateDir, "log"));
88
+ const explicitObserverDir = explicitDirectory(
89
+ env.FML_OBSERVER_DIR,
90
+ "FML_OBSERVER_DIR",
91
+ pathApi
92
+ );
93
+ const legacyPanopticonHome = explicitDirectory(
94
+ env.PANOPTICON_HOME,
95
+ "PANOPTICON_HOME",
96
+ pathApi
97
+ );
98
+ const fmlHome = explicitDirectory(env.FML_HOME, "FML_HOME", pathApi);
99
+ return {
100
+ dataDir,
101
+ stateDir,
102
+ logDir: explicitLogDir ?? defaultLogDir,
103
+ observerDir: explicitObserverDir ?? legacyPanopticonHome ?? (fmlHome ? pathApi.join(fmlHome, "panopticon") : pathApi.join(homeDir, ".fml", "panopticon"))
104
+ };
34
105
  }
35
- function readFileIfPresent(filePath) {
106
+ function observerPort() {
107
+ const configured = process.env.FML_OBSERVER_PORT ?? process.env.PANOPTICON_PORT;
108
+ if (configured) {
109
+ const parsed = Number.parseInt(configured, 10);
110
+ if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
111
+ return parsed;
112
+ }
113
+ }
114
+ return 4318 + (process.getuid?.() ?? 0) % 100;
115
+ }
116
+ var DIRECTORIES, FML_DATA_DIR, FML_STATE_DIR, FML_LOG_DIR, FML_OBSERVER_DIR, FML_OBSERVER_PORT, FML_OBSERVER_STATE_FILE, FML_OBSERVER_LOCK_FILE, FML_OBSERVER_AUTH_TOKEN_FILE, FML_OBSERVER_ENDPOINT_FILE, FML_OBSERVER_LOG_FILE, FML_UPDATE_LOG_FILE, FML_INSTALLATION_ID_FILE, FML_PANOPTICON_DB_FILE, FML_PANOPTICON_DB_OBSERVER_STATE_FILE, FML_PANOPTICON_DB_OBSERVER_LOCK_FILE, FML_LEGACY_OBSERVER_DB_FILE, FML_HOOK_INBOX_DIR, FML_HOOK_CONTEXT_STATE_DIR, FML_HOOK_CAPTURE_DISABLED_FILE;
117
+ var init_dirs = __esm({
118
+ "src/dirs.ts"() {
119
+ "use strict";
120
+ DIRECTORIES = resolveFmlDirectories();
121
+ FML_DATA_DIR = DIRECTORIES.dataDir;
122
+ FML_STATE_DIR = DIRECTORIES.stateDir;
123
+ FML_LOG_DIR = DIRECTORIES.logDir;
124
+ FML_OBSERVER_DIR = DIRECTORIES.observerDir;
125
+ FML_OBSERVER_PORT = observerPort();
126
+ FML_OBSERVER_STATE_FILE = path3.join(
127
+ FML_OBSERVER_DIR,
128
+ "observer.pid.json"
129
+ );
130
+ FML_OBSERVER_LOCK_FILE = path3.join(
131
+ FML_OBSERVER_DIR,
132
+ "observer.lock"
133
+ );
134
+ FML_OBSERVER_AUTH_TOKEN_FILE = path3.join(
135
+ FML_OBSERVER_DIR,
136
+ "auth-token"
137
+ );
138
+ FML_OBSERVER_ENDPOINT_FILE = path3.join(
139
+ FML_OBSERVER_DIR,
140
+ "endpoint.json"
141
+ );
142
+ FML_OBSERVER_LOG_FILE = path3.join(FML_LOG_DIR, "observer.log");
143
+ FML_UPDATE_LOG_FILE = path3.join(FML_LOG_DIR, "update.log");
144
+ FML_INSTALLATION_ID_FILE = path3.join(
145
+ FML_STATE_DIR,
146
+ "installation_id"
147
+ );
148
+ FML_PANOPTICON_DB_FILE = path3.join(FML_STATE_DIR, "panopticon.db");
149
+ FML_PANOPTICON_DB_OBSERVER_STATE_FILE = `${FML_PANOPTICON_DB_FILE}.observer.pid.json`;
150
+ FML_PANOPTICON_DB_OBSERVER_LOCK_FILE = `${FML_PANOPTICON_DB_FILE}.observer.lock`;
151
+ FML_LEGACY_OBSERVER_DB_FILE = path3.join(
152
+ FML_OBSERVER_DIR,
153
+ "observer.db"
154
+ );
155
+ FML_HOOK_INBOX_DIR = path3.join(FML_OBSERVER_DIR, "hook-inbox");
156
+ FML_HOOK_CONTEXT_STATE_DIR = path3.join(
157
+ FML_OBSERVER_DIR,
158
+ "hook-context-state"
159
+ );
160
+ FML_HOOK_CAPTURE_DISABLED_FILE = path3.join(
161
+ FML_OBSERVER_DIR,
162
+ "hook-capture.disabled"
163
+ );
164
+ }
165
+ });
166
+
167
+ // src/config.ts
168
+ import fs2 from "node:fs";
169
+ import path4 from "node:path";
170
+ function isHostedBackendUrl(url) {
171
+ let normalized;
36
172
  try {
37
- return fs.readFileSync(filePath, "utf8");
38
- } catch (error) {
39
- if (error.code === "ENOENT") return null;
40
- throw error;
173
+ normalized = normalizeConvexUrl(url);
174
+ } catch {
175
+ return false;
41
176
  }
177
+ return normalized === DEFAULT_PROD_URL || LEGACY_HOSTED_BACKEND_URLS.includes(normalized);
42
178
  }
43
- function isOwnedExtension(source, legacy = false) {
44
- return source.includes(FML_PI_EXTENSION_MARKER) || legacy && (source.includes(LEGACY_PI_EXTENSION_MARKER) || LEGACY_PI_EXTENSION_SIGNATURES.every(
45
- (signature) => source.includes(signature)
46
- ));
179
+ function isValidEnvName(envName) {
180
+ return ENV_NAME_RE.test(envName);
47
181
  }
48
- function removeLegacyExtension(filePath) {
49
- const source = readFileIfPresent(filePath);
50
- if (source === null) return { removed: false, preserved: false };
51
- if (!isOwnedExtension(source, true)) {
52
- return { removed: false, preserved: true };
182
+ function normalizeConvexUrl(value) {
183
+ const url = new URL(value);
184
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
185
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash || url.pathname !== "" && url.pathname !== "/") {
186
+ throw new Error(
187
+ "FML environment URL must be HTTPS (HTTP is allowed only for loopback) with no path, credentials, query, or fragment"
188
+ );
53
189
  }
54
- fs.rmSync(filePath);
55
- return { removed: true, preserved: false };
190
+ return url.origin;
56
191
  }
57
- function writeExtensionAtomically(filePath, source) {
58
- fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
59
- const temporaryPath = path.join(
60
- path.dirname(filePath),
61
- `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
62
- );
63
- try {
64
- fs.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
65
- if (process.platform === "win32" && fs.existsSync(filePath)) {
66
- fs.rmSync(filePath);
67
- }
68
- fs.renameSync(temporaryPath, filePath);
69
- } finally {
70
- fs.rmSync(temporaryPath, { force: true });
71
- }
192
+ function normalizeAppUrl(value) {
193
+ return normalizeConvexUrl(value);
72
194
  }
73
- var PI_EXTENSION_INSTALLER = Object.freeze({
74
- resolvePaths(context) {
75
- const pathApi = pathApiFor(context.platform);
76
- const extensionsDir = pathApi.join(
77
- resolvePiAgentDir(context, pathApi),
78
- "extensions"
195
+ function readEnvConfig() {
196
+ try {
197
+ const parsed = JSON.parse(
198
+ fs2.readFileSync(ENV_CONFIG_PATH, "utf8")
79
199
  );
80
- return {
81
- sourcePath: pathApi.join(
82
- context.packageRoot,
83
- "dist",
84
- "pi",
85
- "extension.js"
86
- ),
87
- extensionsDir,
88
- destinationPath: pathApi.join(extensionsDir, "fml.js"),
89
- legacyDestinationPath: pathApi.join(extensionsDir, "panopticon.js")
90
- };
91
- },
92
- install(paths) {
93
- const source = fs.readFileSync(paths.sourcePath, "utf8");
94
- if (!source.includes(FML_PI_EXTENSION_MARKER)) {
95
- throw new Error(
96
- `Packaged Pi extension is invalid: ${paths.sourcePath} does not contain the FML ownership marker`
97
- );
200
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
201
+ throw new Error("invalid environment config");
98
202
  }
99
- const existing = readFileIfPresent(paths.destinationPath);
100
- if (existing !== null && !isOwnedExtension(existing)) {
101
- const legacy2 = removeLegacyExtension(paths.legacyDestinationPath);
102
- return {
103
- status: "preserved",
104
- destinationPath: paths.destinationPath,
105
- reason: "an extension not managed by FML already exists at this path",
106
- legacyRemoved: legacy2.removed,
107
- legacyPreserved: legacy2.preserved
108
- };
203
+ const config = parsed;
204
+ if (typeof config.active !== "string" || !isValidEnvName(config.active)) {
205
+ throw new Error("invalid active environment");
109
206
  }
110
- const status = existing === null ? "installed" : existing === source ? "unchanged" : "updated";
111
- if (status !== "unchanged") {
112
- writeExtensionAtomically(paths.destinationPath, source);
207
+ const environments = {};
208
+ if (config.environments && typeof config.environments === "object" && !Array.isArray(config.environments)) {
209
+ for (const [name, environment] of Object.entries(
210
+ config.environments
211
+ )) {
212
+ if (!isValidEnvName(name) || !environment || typeof environment !== "object" || Array.isArray(environment)) {
213
+ continue;
214
+ }
215
+ const entry = environment;
216
+ if (typeof entry.convexUrl !== "string") continue;
217
+ try {
218
+ const appUrl = typeof entry.appUrl === "string" ? normalizeAppUrl(entry.appUrl) : void 0;
219
+ environments[name] = {
220
+ convexUrl: normalizeConvexUrl(entry.convexUrl),
221
+ ...appUrl ? { appUrl } : {}
222
+ };
223
+ } catch {
224
+ }
225
+ }
113
226
  }
114
- const legacy = removeLegacyExtension(paths.legacyDestinationPath);
227
+ environments[DEFAULT_TARGET_NAME] = { convexUrl: DEFAULT_PROD_URL };
115
228
  return {
116
- status,
117
- destinationPath: paths.destinationPath,
118
- legacyRemoved: legacy.removed,
119
- legacyPreserved: legacy.preserved
229
+ schemaVersion: 1,
230
+ active: config.active,
231
+ environments
120
232
  };
121
- },
122
- uninstall(paths) {
123
- const existing = readFileIfPresent(paths.destinationPath);
124
- const legacy = removeLegacyExtension(paths.legacyDestinationPath);
125
- if (existing === null) {
126
- return {
127
- status: "absent",
128
- destinationPath: paths.destinationPath,
129
- legacyRemoved: legacy.removed,
130
- legacyPreserved: legacy.preserved
131
- };
132
- }
133
- if (!isOwnedExtension(existing)) {
134
- return {
135
- status: "preserved",
136
- destinationPath: paths.destinationPath,
137
- reason: "the existing extension is not managed by FML",
138
- legacyRemoved: legacy.removed,
139
- legacyPreserved: legacy.preserved
140
- };
141
- }
142
- fs.rmSync(paths.destinationPath);
233
+ } catch {
143
234
  return {
144
- status: "removed",
145
- destinationPath: paths.destinationPath,
146
- legacyRemoved: legacy.removed,
147
- legacyPreserved: legacy.preserved
235
+ schemaVersion: 1,
236
+ active: DEFAULT_TARGET_NAME,
237
+ environments: {
238
+ [DEFAULT_TARGET_NAME]: { convexUrl: DEFAULT_PROD_URL }
239
+ }
148
240
  };
149
241
  }
242
+ }
243
+ function resolveEnvConvexUrl(envName) {
244
+ const configured = readEnvConfig().environments?.[envName]?.convexUrl;
245
+ if (configured) return configured;
246
+ if (envName === DEFAULT_TARGET_NAME) return DEFAULT_PROD_URL;
247
+ return null;
248
+ }
249
+ function getActiveEnv() {
250
+ const config = readEnvConfig();
251
+ const name = config.active;
252
+ const stored = config.environments?.[name];
253
+ const override = process.env.FML_CONVEX_URL;
254
+ const convexUrl = override ? normalizeConvexUrl(override) : resolveEnvConvexUrl(name);
255
+ const appUrl = stored?.appUrl && stored.convexUrl === convexUrl ? stored.appUrl : null;
256
+ return { name, convexUrl, appUrl };
257
+ }
258
+ function authStorePathFor(envName) {
259
+ return path4.join(FML_DATA_DIR, `auth.${envName}.json`);
260
+ }
261
+ function getSiteUrlForConvexUrl(convexUrl) {
262
+ const explicit = process.env.CONVEX_SITE_URL;
263
+ if (explicit) return explicit.replace(/\/$/, "");
264
+ return (convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
265
+ }
266
+ var DEFAULT_PROD_URL, DEFAULT_SYNC_URL, DEFAULT_TARGET_NAME, LEGACY_HOSTED_BACKEND_URLS, ENV_CONFIG_PATH, ENV_NAME_RE, CONVEX_URL, WORKOS_API_URL;
267
+ var init_config = __esm({
268
+ "src/config.ts"() {
269
+ "use strict";
270
+ init_dirs();
271
+ DEFAULT_PROD_URL = "https://api.fml.inc";
272
+ DEFAULT_SYNC_URL = DEFAULT_PROD_URL;
273
+ DEFAULT_TARGET_NAME = "fml";
274
+ LEGACY_HOSTED_BACKEND_URLS = [
275
+ "https://trustworthy-chihuahua-382.convex.site"
276
+ ];
277
+ ENV_CONFIG_PATH = path4.join(FML_DATA_DIR, "env.json");
278
+ ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;
279
+ CONVEX_URL = (process.env.FML_CONVEX_URL ?? getActiveEnv().convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
280
+ WORKOS_API_URL = "https://api.workos.com";
281
+ }
150
282
  });
151
283
 
152
- // src/hooks/context.ts
153
- import { createHash as createHash3 } from "node:crypto";
154
- import path6 from "node:path";
155
-
156
- // ../../packages/agent-targets/src/hooks/normalize.ts
157
- import { createHash } from "node:crypto";
158
-
159
- // ../../packages/agent-targets/src/hooks/types.ts
160
- var HOOK_TARGETS = Object.freeze([
161
- "claude",
162
- "codex",
163
- "gemini",
164
- "grok",
165
- "pi"
166
- ]);
167
-
168
- // ../../packages/agent-targets/src/catalog/hook-events.json
169
- var hook_events_default = {
170
- claude: [
171
- "SessionStart",
172
- "Setup",
173
- "InstructionsLoaded",
174
- "UserPromptSubmit",
175
- "UserPromptExpansion",
176
- "PreToolUse",
177
- "PermissionRequest",
178
- "PostToolUse",
179
- "PostToolUseFailure",
180
- "PostToolBatch",
181
- "PermissionDenied",
182
- "Notification",
183
- "SubagentStart",
184
- "SubagentStop",
185
- "TaskCreated",
186
- "TaskCompleted",
187
- "Stop",
188
- "StopFailure",
189
- "TeammateIdle",
190
- "ConfigChange",
191
- "CwdChanged",
192
- "PreCompact",
193
- "PostCompact",
194
- "SessionEnd",
195
- "Elicitation",
196
- "ElicitationResult"
197
- ],
198
- codex: [
199
- "SessionStart",
200
- "UserPromptSubmit",
201
- "PreToolUse",
202
- "PermissionRequest",
203
- "PostToolUse",
204
- "Stop"
205
- ],
206
- gemini: [
207
- "SessionStart",
208
- "BeforeAgent",
209
- "BeforeTool",
210
- "AfterTool",
211
- "AfterAgent"
212
- ],
213
- grok: [
214
- "SessionStart",
215
- "UserPromptSubmit",
216
- "PreToolUse",
217
- "PostToolUse",
218
- "PostToolUseFailure",
219
- "PermissionDenied",
220
- "Stop",
221
- "StopFailure",
222
- "Notification",
223
- "SubagentStart",
224
- "SubagentStop",
225
- "PreCompact",
226
- "PostCompact",
227
- "SessionEnd"
228
- ],
229
- pi: [
230
- "SessionStart",
231
- "UserPromptSubmit",
232
- "TurnStart",
233
- "PreToolUse",
234
- "PostToolUse",
235
- "PostToolUseFailure",
236
- "Stop",
237
- "Notification",
238
- "ConfigChange",
239
- "PreCompact",
240
- "PostCompact",
241
- "SessionEnd"
242
- ]
243
- };
244
-
245
- // ../../packages/agent-targets/src/catalog/hook-events.ts
246
- var CLAUDE_HOOK_EVENTS = Object.freeze([
247
- ...hook_events_default.claude
248
- ]);
249
- var CODEX_HOOK_EVENTS = Object.freeze([
250
- ...hook_events_default.codex
251
- ]);
252
- var GEMINI_HOOK_EVENTS = Object.freeze([
253
- ...hook_events_default.gemini
254
- ]);
255
- var GROK_HOOK_EVENTS = Object.freeze([
256
- ...hook_events_default.grok
257
- ]);
258
- var PI_HOOK_EVENTS = Object.freeze([...hook_events_default.pi]);
284
+ // src/sentry.ts
285
+ var sentry, client, Sentry;
286
+ var init_sentry = __esm({
287
+ "src/sentry.ts"() {
288
+ "use strict";
289
+ sentry = null;
290
+ client = null;
291
+ Sentry = {
292
+ captureException: (err) => sentry?.captureException(err),
293
+ captureMessage: (msg) => sentry?.captureMessage(msg),
294
+ flush: async (timeoutMs) => {
295
+ try {
296
+ await client?.flush(timeoutMs);
297
+ } catch {
298
+ }
299
+ }
300
+ };
301
+ }
302
+ });
259
303
 
260
- // ../../packages/agent-targets/src/hooks/read-command.ts
261
- import path2 from "node:path";
262
- var SHELL_TOOL_NAMES = /* @__PURE__ */ new Set([
263
- "bash",
264
- "exec_command",
265
- "shell"
266
- ]);
267
- var COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
268
- ";",
269
- "&",
270
- "&&",
271
- "|",
272
- "||",
273
- "<",
274
- "<<",
275
- ">",
276
- ">>",
277
- ">&1",
278
- ">&2",
279
- "1>",
280
- "1>>",
281
- "1>&1",
282
- "1>&2",
283
- "2>",
284
- "2>>",
285
- "2>&1",
286
- "2>&2"
287
- ]);
288
- function tokenizeShellLikeCommand(command) {
289
- const tokens = [];
290
- let current = "";
291
- let quote = null;
292
- let escaping = false;
293
- for (let index = 0; index < command.length; index += 1) {
294
- const character = command[index];
295
- if (escaping) {
296
- current += character;
297
- escaping = false;
298
- continue;
299
- }
300
- if (character === "\\") {
301
- escaping = true;
302
- continue;
304
+ // src/telemetry-config.ts
305
+ var telemetry_config_exports = {};
306
+ __export(telemetry_config_exports, {
307
+ refreshCliTelemetryConfig: () => refreshCliTelemetryConfig,
308
+ resolveCliSentryConfig: () => resolveCliSentryConfig
309
+ });
310
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
311
+ import fs3 from "node:fs";
312
+ import path5 from "node:path";
313
+ function parseSentryConfig(value) {
314
+ if (value === null) return null;
315
+ if (!value || typeof value !== "object")
316
+ throw new Error("Invalid Sentry config");
317
+ const config = value;
318
+ if (typeof config.dsn !== "string") throw new Error("Invalid Sentry DSN");
319
+ const url = new URL(config.dsn);
320
+ if (url.protocol !== "https:" || !url.username || url.password || url.search || url.hash || !/^\/(?:.*\/)?\d+$/.test(url.pathname)) {
321
+ throw new Error("Invalid Sentry DSN");
322
+ }
323
+ if (config.environment !== void 0 && (typeof config.environment !== "string" || !config.environment.trim())) {
324
+ throw new Error("Invalid Sentry environment");
325
+ }
326
+ return {
327
+ dsn: config.dsn,
328
+ ...typeof config.environment === "string" ? { environment: config.environment } : {}
329
+ };
330
+ }
331
+ function cachePathFor(siteUrl) {
332
+ const key = isHostedBackendUrl(siteUrl) ? DEFAULT_SYNC_URL : siteUrl;
333
+ return path5.join(
334
+ FML_DATA_DIR,
335
+ "telemetry-config",
336
+ `${createHash2("sha256").update(key).digest("hex")}.json`
337
+ );
338
+ }
339
+ function readCache(siteUrl) {
340
+ try {
341
+ const value = JSON.parse(
342
+ fs3.readFileSync(cachePathFor(siteUrl), "utf8")
343
+ );
344
+ if (!value || typeof value !== "object") return void 0;
345
+ const entry = value;
346
+ if (typeof entry.siteUrl !== "string" || entry.siteUrl !== siteUrl && !(isHostedBackendUrl(entry.siteUrl) && isHostedBackendUrl(siteUrl)) || typeof entry.fetchedAt !== "number" || !Number.isFinite(entry.fetchedAt))
347
+ return void 0;
348
+ return {
349
+ siteUrl: entry.siteUrl,
350
+ fetchedAt: entry.fetchedAt,
351
+ ...entry.sentry === void 0 ? {} : { sentry: parseSentryConfig(entry.sentry) }
352
+ };
353
+ } catch {
354
+ return void 0;
355
+ }
356
+ }
357
+ function writeCache(entry) {
358
+ const cachePath = cachePathFor(entry.siteUrl);
359
+ const temporaryPath = `${cachePath}.${randomUUID2()}.tmp`;
360
+ try {
361
+ fs3.mkdirSync(path5.dirname(cachePath), { recursive: true, mode: 448 });
362
+ fs3.writeFileSync(temporaryPath, JSON.stringify(entry), { mode: 384 });
363
+ fs3.renameSync(temporaryPath, cachePath);
364
+ } catch {
365
+ } finally {
366
+ try {
367
+ fs3.rmSync(temporaryPath, { force: true });
368
+ } catch {
303
369
  }
304
- if (quote) {
305
- if (character === quote) quote = null;
306
- else current += character;
307
- continue;
370
+ }
371
+ }
372
+ async function refreshCliTelemetryConfig(opts) {
373
+ try {
374
+ if (opts.signal?.aborted) return;
375
+ const siteUrl = opts.siteUrl ?? getSiteUrlForConvexUrl(resolveEnvConvexUrl(opts.envName));
376
+ if (!opts.siteUrl && !isHostedBackendUrl(siteUrl)) return;
377
+ if (!opts.force) {
378
+ const age = Date.now() - (readCache(siteUrl)?.fetchedAt ?? Number.NaN);
379
+ if (age >= 0 && age < CONFIG_TTL_MS) return;
380
+ }
381
+ const key = cachePathFor(siteUrl);
382
+ let refresh = refreshesInFlight.get(key);
383
+ if (!refresh) {
384
+ refresh = fetchAndCache(siteUrl, opts.accessToken, opts.signal).finally(
385
+ () => refreshesInFlight.delete(key)
386
+ );
387
+ refreshesInFlight.set(key, refresh);
308
388
  }
309
- if (character === "'" || character === '"') {
310
- quote = character;
311
- continue;
312
- }
313
- if (current === "" && (character === "1" || character === "2") && command[index + 1] === ">") {
314
- if (command[index + 2] === ">") {
315
- tokens.push(`${character}>>`);
316
- index += 2;
317
- } else if (command[index + 2] === "&" && (command[index + 3] === "1" || command[index + 3] === "2")) {
318
- tokens.push(`${character}>&${command[index + 3]}`);
319
- index += 3;
320
- } else {
321
- tokens.push(`${character}>`);
322
- index += 1;
323
- }
324
- continue;
325
- }
326
- if (/[;&|<>]/.test(character)) {
327
- if (current) {
328
- tokens.push(current);
329
- current = "";
330
- }
331
- const next = command[index + 1];
332
- if (character === ">" && next === "&" && (command[index + 2] === "1" || command[index + 2] === "2")) {
333
- tokens.push(`>&${command[index + 2]}`);
334
- index += 2;
335
- continue;
336
- }
337
- if (character === "&" && next === "&" || character === "|" && next === "|" || character === "<" && next === "<" || character === ">" && next === ">") {
338
- tokens.push(`${character}${next}`);
339
- index += 1;
340
- continue;
341
- }
342
- tokens.push(character);
343
- continue;
344
- }
345
- if (/\s/.test(character)) {
346
- if (current) {
347
- tokens.push(current);
348
- current = "";
349
- }
350
- continue;
351
- }
352
- current += character;
389
+ await refresh;
390
+ } catch {
353
391
  }
354
- if (quote || escaping) return [];
355
- if (current) tokens.push(current);
356
- return tokens;
357
392
  }
358
- function splitCommandSegments(tokens) {
359
- const segments = [];
360
- let current = [];
361
- for (let index = 0; index < tokens.length; index += 1) {
362
- const token = tokens[index];
363
- if (COMMAND_SEPARATORS.has(token)) {
364
- if (current.length > 0) {
365
- segments.push(current);
366
- current = [];
367
- }
368
- if (token === "<" || token === "<<") index += 1;
369
- continue;
370
- }
371
- current.push(token);
372
- }
373
- if (current.length > 0) segments.push(current);
374
- return segments;
393
+ async function fetchAndCache(siteUrl, accessToken, signal) {
394
+ const timeout = AbortSignal.timeout(CONFIG_TIMEOUT_MS);
395
+ const response = await fetch(`${siteUrl}/api/telemetry/config`, {
396
+ headers: { Authorization: `Bearer ${accessToken}` },
397
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
398
+ redirect: "error"
399
+ });
400
+ if (!response.ok) throw new Error("Config fetch failed");
401
+ const data = await response.json();
402
+ if (!data || typeof data !== "object" || data.ok !== true)
403
+ throw new Error("Invalid telemetry config");
404
+ const sentry2 = data.sentry;
405
+ writeCache({
406
+ siteUrl,
407
+ fetchedAt: Date.now(),
408
+ ...sentry2 === void 0 ? {} : { sentry: parseSentryConfig(sentry2) }
409
+ });
375
410
  }
376
- function stripSimpleOptions(args, flagsWithValues = /* @__PURE__ */ new Set(), patternFlags = /* @__PURE__ */ new Set()) {
377
- const remaining = [];
378
- let consumedPatternFlag = false;
379
- for (let index = 0; index < args.length; index += 1) {
380
- const argument = args[index];
381
- if (argument === "--") {
382
- remaining.push(...args.slice(index + 1));
383
- break;
384
- }
385
- if (argument.startsWith("-") && argument.length > 1) {
386
- if (flagsWithValues.has(argument)) {
387
- if (patternFlags.has(argument)) consumedPatternFlag = true;
388
- if (!argument.includes("=")) index += 1;
389
- }
390
- continue;
391
- }
392
- remaining.push(argument);
411
+ async function resolveCliSentryConfig(defaultDsn, forceRefresh = false) {
412
+ if (process.env.FML_SENTRY_DSN !== void 0) {
413
+ return process.env.FML_SENTRY_DSN ? {
414
+ dsn: process.env.FML_SENTRY_DSN,
415
+ environment: process.env.FML_SENTRY_ENVIRONMENT
416
+ } : null;
417
+ }
418
+ const active = getActiveEnv();
419
+ const siteUrl = getSiteUrlForConvexUrl(active.convexUrl);
420
+ const hosted = active.convexUrl !== null && isHostedBackendUrl(active.convexUrl) && isHostedBackendUrl(siteUrl);
421
+ const fallback = hosted && defaultDsn ? {
422
+ dsn: defaultDsn,
423
+ environment: process.env.DEPLOYMENT_ENV ?? process.env.NODE_ENV ?? "production"
424
+ } : null;
425
+ const auth = readTokens(active.name);
426
+ if (!auth || auth.refreshInvalidAt || (auth.siteUrl ? auth.siteUrl !== siteUrl && !(hosted && isHostedBackendUrl(auth.siteUrl)) : !hosted))
427
+ return fallback;
428
+ if (forceRefresh && auth.expiresAt > Date.now()) {
429
+ await refreshCliTelemetryConfig({
430
+ envName: active.name,
431
+ accessToken: auth.accessToken,
432
+ siteUrl: auth.siteUrl,
433
+ force: true
434
+ });
393
435
  }
394
- return { args: remaining, consumedPatternFlag };
395
- }
396
- function readCommandFileArgs(args, flagsWithValues, patternFlags = flagsWithValues) {
397
- const stripped = stripSimpleOptions(args, flagsWithValues, patternFlags);
398
- return stripped.consumedPatternFlag ? stripped.args : stripped.args.slice(1);
436
+ const cached = readCache(siteUrl);
437
+ return cached?.sentry === void 0 ? fallback : cached.sentry;
399
438
  }
400
- function isPlausibleReadPath(argument) {
401
- if (!argument || argument === "-" || argument.startsWith("-")) return false;
402
- if (COMMAND_SEPARATORS.has(argument)) return false;
403
- if (/[\0*?[{}`$]/.test(argument)) return false;
404
- if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(argument)) return false;
405
- return path2.posix.isAbsolute(argument) || path2.win32.isAbsolute(argument) || argument.startsWith("~/") || argument.startsWith("./") || argument.startsWith("../") || argument.includes("/") || /\.[A-Za-z0-9][A-Za-z0-9_-]*$/.test(argument) || /^[A-Z][A-Za-z0-9_.-]*$/.test(argument);
406
- }
407
- function filePathFromSegment(tokens) {
408
- const commandTokens = [...tokens];
409
- while (commandTokens[0]?.includes("=") && /^[A-Za-z_][A-Za-z0-9_]*=/.test(commandTokens[0])) {
410
- commandTokens.shift();
411
- }
412
- const commandName = commandTokens.shift()?.split("/").pop();
413
- if (!commandName) return null;
414
- let args;
415
- switch (commandName) {
416
- case "bat":
417
- case "batcat":
418
- case "cat":
419
- case "file":
420
- case "less":
421
- case "more":
422
- case "nl":
423
- case "stat":
424
- case "wc":
425
- args = stripSimpleOptions(commandTokens).args;
426
- break;
427
- case "head":
428
- case "tail":
429
- args = stripSimpleOptions(commandTokens, /* @__PURE__ */ new Set(["-n", "-c"])).args;
430
- break;
431
- case "sed":
432
- if (commandTokens.some(
433
- (argument) => argument.startsWith("-i") || argument === "--in-place" || argument.startsWith("--in-place=")
434
- )) {
435
- return null;
436
- }
437
- args = readCommandFileArgs(commandTokens, /* @__PURE__ */ new Set(["-e", "-f"]));
438
- break;
439
- case "awk":
440
- args = readCommandFileArgs(
441
- commandTokens,
442
- /* @__PURE__ */ new Set(["-f", "-v"]),
443
- /* @__PURE__ */ new Set(["-f"])
444
- );
445
- break;
446
- case "grep":
447
- case "rg":
448
- args = readCommandFileArgs(
449
- commandTokens,
450
- /* @__PURE__ */ new Set(["-A", "-B", "-C", "-e", "-f", "-g", "-m", "-t"]),
451
- /* @__PURE__ */ new Set(["-e", "-f"])
452
- );
453
- break;
454
- default:
455
- return null;
439
+ var CONFIG_TTL_MS, CONFIG_TIMEOUT_MS, refreshesInFlight;
440
+ var init_telemetry_config = __esm({
441
+ "src/telemetry-config.ts"() {
442
+ "use strict";
443
+ init_dirs();
444
+ init_config();
445
+ init_token_store();
446
+ CONFIG_TTL_MS = 60 * 60 * 1e3;
447
+ CONFIG_TIMEOUT_MS = 1500;
448
+ refreshesInFlight = /* @__PURE__ */ new Map();
456
449
  }
457
- return args.find(isPlausibleReadPath) ?? null;
458
- }
459
- function extractReadFilePathFromCommand(command) {
460
- const tokens = tokenizeShellLikeCommand(command);
461
- if (tokens.length === 0) return null;
462
- for (let index = 0; index < tokens.length - 1; index += 1) {
463
- if (tokens[index] === "<" && isPlausibleReadPath(tokens[index + 1])) {
464
- return tokens[index + 1];
465
- }
450
+ });
451
+
452
+ // src/auth/token-store.ts
453
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
454
+ import { execFileSync } from "node:child_process";
455
+ import fs4 from "node:fs";
456
+ import path6 from "node:path";
457
+ function getCurrentProcessIdentity() {
458
+ let cache = processGlobals[PROCESS_IDENTITY_KEY];
459
+ if (!cache) {
460
+ cache = { resolved: false };
461
+ processGlobals[PROCESS_IDENTITY_KEY] = cache;
466
462
  }
467
- for (const segment of splitCommandSegments(tokens)) {
468
- const filePath = filePathFromSegment(segment);
469
- if (filePath) return filePath;
463
+ if (!cache.resolved) {
464
+ cache.value = getProcessIdentity(process.pid) ?? void 0;
465
+ cache.resolved = true;
470
466
  }
471
- return null;
467
+ return cache.value;
472
468
  }
473
-
474
- // ../../packages/agent-targets/src/hooks/normalize.ts
475
- var MAX_EXTRACTED_TEXT_BYTES = 64 * 1024;
476
- function isRecord(value) {
477
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
469
+ function storePathFor(envName) {
470
+ return authStorePathFor(envName ?? getActiveEnv().name);
478
471
  }
479
- function firstString(record, keys) {
480
- for (const key of keys) {
481
- const value = record[key];
482
- if (typeof value === "string" && value.length > 0) return value;
483
- }
484
- return void 0;
472
+ function resolveEnvName(envName) {
473
+ return envName ?? getActiveEnv().name;
485
474
  }
486
- function extractedText(value) {
487
- if (typeof value === "string") return value;
488
- if (value === void 0 || value === null) return void 0;
475
+ function ensureDir(envName) {
476
+ const dir = path6.dirname(storePathFor(envName));
477
+ fs4.mkdirSync(dir, { recursive: true, mode: 448 });
489
478
  try {
490
- return JSON.stringify(value);
479
+ fs4.chmodSync(dir, 448);
491
480
  } catch {
492
- return void 0;
493
481
  }
494
482
  }
495
- function firstText(record, keys) {
496
- for (const key of keys) {
497
- const value = extractedText(record[key]);
498
- if (value !== void 0 && value.length > 0) return value;
483
+ function writeSecretFile(filePath, data) {
484
+ const temporaryFile = `${filePath}.${process.pid}.${randomUUID3()}.tmp`;
485
+ let descriptor = null;
486
+ try {
487
+ descriptor = fs4.openSync(temporaryFile, "wx", 384);
488
+ fs4.writeFileSync(descriptor, data, { encoding: "utf8" });
489
+ fs4.fsyncSync(descriptor);
490
+ fs4.closeSync(descriptor);
491
+ descriptor = null;
492
+ fs4.renameSync(temporaryFile, filePath);
493
+ try {
494
+ fs4.chmodSync(filePath, 384);
495
+ } catch {
496
+ }
497
+ } finally {
498
+ if (descriptor !== null) {
499
+ try {
500
+ fs4.closeSync(descriptor);
501
+ } catch {
502
+ }
503
+ }
504
+ try {
505
+ fs4.unlinkSync(temporaryFile);
506
+ } catch {
507
+ }
499
508
  }
500
- return void 0;
501
509
  }
502
- function utf8Prefix(value, maxBytes) {
503
- let low = 0;
504
- let high = value.length;
505
- while (low < high) {
506
- const middle = Math.ceil((low + high) / 2);
507
- if (Buffer.byteLength(value.slice(0, middle), "utf8") <= maxBytes) {
508
- low = middle;
509
- } else {
510
- high = middle - 1;
511
- }
510
+ function readTokens(envName) {
511
+ let raw;
512
+ try {
513
+ raw = fs4.readFileSync(storePathFor(envName), "utf-8");
514
+ } catch {
515
+ return null;
512
516
  }
513
- let end = low;
514
- if (end > 0 && end < value.length && value.charCodeAt(end - 1) >= 55296 && value.charCodeAt(end - 1) <= 56319 && value.charCodeAt(end) >= 56320 && value.charCodeAt(end) <= 57343) {
515
- end -= 1;
517
+ try {
518
+ return parseStoredAuthShape(JSON.parse(raw));
519
+ } catch {
520
+ return null;
516
521
  }
517
- return value.slice(0, end);
518
- }
519
- function boundedExtractedText(value, field) {
520
- if (value === void 0) return void 0;
521
- const originalBytes = Buffer.byteLength(value, "utf8");
522
- if (originalBytes <= MAX_EXTRACTED_TEXT_BYTES) return value;
523
- const digest = createHash("sha256").update(value).digest("hex");
524
- const marker = `
525
- [fml:${field}:truncated originalBytes=${originalBytes} sha256=${digest}]`;
526
- return `${utf8Prefix(
527
- value,
528
- MAX_EXTRACTED_TEXT_BYTES - Buffer.byteLength(marker, "utf8")
529
- )}${marker}`;
530
522
  }
531
- function nestedRecord(record, keys) {
532
- for (const key of keys) {
533
- const value = record[key];
534
- if (isRecord(value)) return value;
523
+ function parseStoredAuthShape(parsed) {
524
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
525
+ return null;
535
526
  }
536
- return void 0;
537
- }
538
- function timestampMs(payload, fallback) {
539
- for (const key of ["timestamp_ms", "timestampMs", "timestamp"]) {
540
- const value = payload[key];
541
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
542
- const milliseconds = Math.round(
543
- value < 1e10 ? value * 1e3 : value
544
- );
545
- if (Number.isSafeInteger(milliseconds) && milliseconds > 0) {
546
- return milliseconds;
547
- }
548
- }
549
- if (typeof value === "string") {
550
- const parsed = Date.parse(value);
551
- if (Number.isSafeInteger(parsed) && parsed >= 0) return parsed;
552
- }
527
+ const auth = parsed;
528
+ const user = auth.user;
529
+ if (typeof auth.accessToken !== "string" || typeof auth.refreshToken !== "string" || typeof auth.expiresAt !== "number" || !Number.isFinite(auth.expiresAt) || !user || typeof user.id !== "string" || typeof user.email !== "string" || typeof user.name !== "string" || auth.orgId !== void 0 && typeof auth.orgId !== "string" || auth.orgSlug !== void 0 && typeof auth.orgSlug !== "string" || auth.workosClientId !== void 0 && typeof auth.workosClientId !== "string" || auth.siteUrl !== void 0 && typeof auth.siteUrl !== "string" || auth.tokenType !== void 0 && auth.tokenType !== "oauth" && auth.tokenType !== "service" || auth.refreshInvalidAt !== void 0 && (typeof auth.refreshInvalidAt !== "number" || !Number.isFinite(auth.refreshInvalidAt))) {
530
+ return null;
553
531
  }
554
- return fallback;
532
+ return auth;
555
533
  }
556
- function geminiEventType(eventType) {
557
- switch (eventType) {
558
- case "BeforeAgent":
559
- case "BeforeModel":
560
- return "UserPromptSubmit";
561
- case "BeforeTool":
562
- return "PreToolUse";
563
- case "AfterTool":
564
- return "PostToolUse";
565
- case "AfterAgent":
566
- return "Stop";
567
- default:
568
- return eventType;
534
+ function readTokensForReconciliation(envName) {
535
+ let raw;
536
+ try {
537
+ raw = fs4.readFileSync(storePathFor(envName), "utf8");
538
+ } catch (error) {
539
+ if (error.code === "ENOENT") return null;
540
+ throw error;
569
541
  }
570
- }
571
- var GROK_EVENT_TYPES = Object.freeze({
572
- session_start: "SessionStart",
573
- user_prompt_submit: "UserPromptSubmit",
574
- pre_tool_use: "PreToolUse",
575
- post_tool_use: "PostToolUse",
576
- post_tool_use_failure: "PostToolUseFailure",
577
- permission_denied: "PermissionDenied",
578
- stop: "Stop",
579
- stop_failure: "StopFailure",
580
- notification: "Notification",
581
- subagent_start: "SubagentStart",
582
- subagent_stop: "SubagentStop",
583
- pre_compact: "PreCompact",
584
- post_compact: "PostCompact",
585
- session_end: "SessionEnd"
586
- });
587
- function grokEventType(eventType) {
588
- return Object.hasOwn(GROK_EVENT_TYPES, eventType) ? GROK_EVENT_TYPES[eventType] ?? eventType : eventType;
589
- }
590
- function geminiUserPrompt(payload) {
591
- const request = payload.llm_request;
592
- if (!isRecord(request) || !Array.isArray(request.messages)) return void 0;
593
- const userMessage = [...request.messages].reverse().find(
594
- (message) => isRecord(message) && message.role === "user"
595
- );
596
- if (!userMessage) return void 0;
597
- if (typeof userMessage.content === "string") {
598
- return userMessage.content || void 0;
542
+ let parsed;
543
+ try {
544
+ parsed = JSON.parse(raw);
545
+ } catch (error) {
546
+ throw new CorruptStoredCredentialsError(
547
+ "Stored credentials are not valid JSON",
548
+ { cause: error }
549
+ );
599
550
  }
600
- if (!Array.isArray(userMessage.content)) return void 0;
601
- const text = userMessage.content.filter(
602
- (part) => isRecord(part) && part.type === "text" && typeof part.text === "string"
603
- ).map((part) => part.text).join("\n");
604
- return text || void 0;
551
+ const auth = parseStoredAuthShape(parsed);
552
+ if (!auth) {
553
+ throw new CorruptStoredCredentialsError(
554
+ "Stored credentials have an invalid shape"
555
+ );
556
+ }
557
+ return auth;
605
558
  }
606
- function normalizedEvent(target, payload, context) {
607
- const sessionId = firstString(payload, [
608
- "session_id",
609
- "sessionId",
610
- "conversation_id",
611
- "conversationId"
612
- ]);
613
- const rawEventType = firstString(payload, [
614
- "hook_event_name",
615
- "hookEventName",
616
- "event_name",
617
- "eventName",
618
- "event"
619
- ]);
620
- if (!sessionId || !rawEventType) return null;
621
- const eventType = target === "gemini" ? geminiEventType(rawEventType) : target === "grok" ? grokEventType(rawEventType) : rawEventType;
622
- const toolInput = nestedRecord(payload, ["tool_input", "toolInput"]);
623
- const cwd = firstString(payload, [
624
- "cwd",
625
- "working_directory",
626
- "workingDirectory",
627
- "workspaceRoot"
628
- ]) ?? context.cwd;
629
- const command = boundedExtractedText(
630
- firstText(payload, ["command"]) ?? (toolInput ? firstText(toolInput, ["command", "cmd"]) : void 0),
631
- "command"
632
- );
633
- const toolName = firstString(payload, ["tool_name", "toolName", "name"]);
634
- const filePath = firstString(payload, ["file_path", "filePath", "path"]) ?? (toolInput ? firstString(toolInput, ["file_path", "filePath", "path"]) : void 0) ?? (command && toolName && SHELL_TOOL_NAMES.has(toolName.toLowerCase()) ? extractReadFilePathFromCommand(command) ?? void 0 : void 0);
635
- const userPrompt = boundedExtractedText(
636
- firstText(payload, ["prompt", "user_prompt", "userPrompt"]) ?? (target === "gemini" ? geminiUserPrompt(payload) : void 0),
637
- "user_prompt"
638
- );
639
- const plan = boundedExtractedText(firstText(payload, ["plan"]), "plan");
640
- const allowedPrompts = boundedExtractedText(
641
- firstText(payload, ["allowed_prompts", "allowedPrompts"]),
642
- "allowed_prompts"
643
- );
644
- const toolResult = boundedExtractedText(
645
- firstText(payload, [
646
- "tool_result",
647
- "toolResult",
648
- "tool_response",
649
- "toolResponse"
650
- ]),
651
- "tool_result"
652
- );
653
- const nativeEventId = firstString(payload, [
654
- "hook_id",
655
- "hookId",
656
- "event_id",
657
- "eventId",
658
- "tool_use_id",
659
- "toolUseId",
660
- "tool_call_id",
661
- "toolCallId",
662
- "prompt_id",
663
- "promptId"
664
- ]) ?? (eventType === "UserPromptSubmit" || eventType === "Stop" ? firstString(payload, ["turn_id", "turnId"]) : void 0);
665
- return {
666
- sessionId,
667
- eventType,
668
- timestampMs: timestampMs(payload, context.nowMs),
669
- ...nativeEventId ? { nativeEventId } : {},
670
- ...cwd ? { cwd } : {},
671
- ...toolName ? { toolName } : {},
672
- ...userPrompt ? { userPrompt } : {},
673
- ...filePath ? { filePath } : {},
674
- ...command ? { command } : {},
675
- ...plan ? { plan } : {},
676
- ...allowedPrompts ? { allowedPrompts } : {},
677
- ...toolResult ? { toolResult } : {}
678
- };
559
+ function writeTokensUnlocked(auth, envName) {
560
+ ensureDir(envName);
561
+ writeSecretFile(storePathFor(envName), JSON.stringify(auth, null, 2));
679
562
  }
680
- function capability(targetId, nativeEvents) {
681
- return Object.freeze({
682
- targetId,
683
- nativeEvents,
684
- normalize: (payload, context) => normalizedEvent(targetId, payload, context)
685
- });
563
+ function credentialHash(value) {
564
+ return createHash3("sha256").update(value).digest("hex");
686
565
  }
687
- var HOOK_NORMALIZATION_CAPABILITIES = Object.freeze([
688
- capability("claude", CLAUDE_HOOK_EVENTS),
689
- capability("codex", CODEX_HOOK_EVENTS),
690
- capability("gemini", GEMINI_HOOK_EVENTS),
691
- capability("grok", GROK_HOOK_EVENTS),
692
- capability("pi", PI_HOOK_EVENTS)
693
- ]);
694
-
695
- // src/auth/token-store.ts
696
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
697
- import { execFileSync } from "node:child_process";
698
- import fs3 from "node:fs";
699
- import path5 from "node:path";
700
-
701
- // src/config.ts
702
- import fs2 from "node:fs";
703
- import path4 from "node:path";
704
-
705
- // src/dirs.ts
706
- import os from "node:os";
707
- import path3 from "node:path";
708
- function isPresent(value) {
709
- return value !== void 0 && value.trim().length > 0;
566
+ function describeError(error) {
567
+ return error instanceof Error ? error.message : String(error);
710
568
  }
711
- function normalizeAbsolutePath(value, variableName, pathApi) {
712
- if (!pathApi.isAbsolute(value)) {
713
- throw new Error(`${variableName} must be an absolute path: ${value}`);
569
+ function getSyncDestinationAuthBinding(envName) {
570
+ const resolvedEnvName = resolveEnvName(envName);
571
+ const envToken = process.env.FML_TOKEN;
572
+ if (envToken?.startsWith("fml_srt_")) {
573
+ return {
574
+ destinationIdentity: `service-refresh:${credentialHash(envToken)}`
575
+ };
714
576
  }
715
- return pathApi.normalize(value);
716
- }
717
- function explicitDirectory(value, variableName, pathApi) {
718
- if (!isPresent(value)) return void 0;
719
- return normalizeAbsolutePath(value, variableName, pathApi);
577
+ if (envToken?.startsWith("fml_st_")) {
578
+ return {
579
+ destinationIdentity: `service-access:${credentialHash(envToken)}`
580
+ };
581
+ }
582
+ const stored = readTokens(resolvedEnvName);
583
+ if (!stored) return null;
584
+ if (stored.tokenType === "service") {
585
+ return {
586
+ destinationIdentity: `service-refresh:${credentialHash(stored.refreshToken)}`
587
+ };
588
+ }
589
+ const destinationIdentity = `oauth-user:${stored.user.id}`;
590
+ if (stored.orgId && stored.orgSlug) {
591
+ return {
592
+ destinationIdentity,
593
+ organizationId: stored.orgId,
594
+ organizationSlug: stored.orgSlug
595
+ };
596
+ }
597
+ return { destinationIdentity, organizationSelectionRequired: true };
720
598
  }
721
- function platformBaseDirectory(value, pathApi) {
722
- if (!isPresent(value) || !pathApi.isAbsolute(value)) return void 0;
723
- return pathApi.normalize(value);
599
+ function getSyncDestinationIdentity(envName) {
600
+ return getSyncDestinationAuthBinding(envName)?.destinationIdentity ?? null;
724
601
  }
725
- function resolveFmlDirectories(options = {}) {
726
- const platform = options.platform ?? process.platform;
727
- const env = options.env ?? process.env;
728
- const pathApi = platform === "win32" ? path3.win32 : path3.posix;
729
- const homeDir = normalizeAbsolutePath(
730
- options.homeDir ?? os.homedir(),
731
- "home directory",
732
- pathApi
733
- );
734
- const explicitDataDir = explicitDirectory(
735
- env.FML_DATA_DIR,
736
- "FML_DATA_DIR",
737
- pathApi
738
- );
739
- const explicitStateDir = explicitDirectory(
740
- env.FML_STATE_DIR,
741
- "FML_STATE_DIR",
742
- pathApi
743
- );
744
- const explicitLogDir = explicitDirectory(
745
- env.FML_LOG_DIR,
746
- "FML_LOG_DIR",
747
- pathApi
748
- );
749
- let defaultDataDir;
750
- let defaultStateDir;
751
- let nativeLogDir;
752
- switch (platform) {
753
- case "darwin": {
754
- defaultDataDir = pathApi.join(
755
- homeDir,
756
- "Library",
757
- "Application Support",
758
- "fml"
759
- );
760
- defaultStateDir = defaultDataDir;
761
- nativeLogDir = pathApi.join(homeDir, "Library", "Logs", "fml");
762
- break;
763
- }
764
- case "win32": {
765
- const roamingAppData = platformBaseDirectory(env.APPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Roaming");
766
- const localAppData = platformBaseDirectory(env.LOCALAPPDATA, pathApi) ?? pathApi.join(homeDir, "AppData", "Local");
767
- defaultDataDir = pathApi.join(roamingAppData, "fml");
768
- defaultStateDir = pathApi.join(localAppData, "fml");
769
- break;
770
- }
771
- default: {
772
- const xdgDataHome = platformBaseDirectory(env.XDG_DATA_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "share");
773
- const xdgStateHome = platformBaseDirectory(env.XDG_STATE_HOME, pathApi) ?? pathApi.join(homeDir, ".local", "state");
774
- defaultDataDir = pathApi.join(xdgDataHome, "fml");
775
- defaultStateDir = pathApi.join(xdgStateHome, "fml");
776
- break;
777
- }
778
- }
779
- const dataDir = explicitDataDir ?? defaultDataDir;
780
- const stateDir = explicitStateDir ?? explicitDataDir ?? defaultStateDir;
781
- const defaultLogDir = nativeLogDir ?? (platform === "win32" ? pathApi.join(defaultDataDir, "logs") : pathApi.join(defaultStateDir, "log"));
782
- const explicitObserverDir = explicitDirectory(
783
- env.FML_OBSERVER_DIR,
784
- "FML_OBSERVER_DIR",
785
- pathApi
786
- );
787
- const legacyPanopticonHome = explicitDirectory(
788
- env.PANOPTICON_HOME,
789
- "PANOPTICON_HOME",
790
- pathApi
791
- );
792
- const fmlHome = explicitDirectory(env.FML_HOME, "FML_HOME", pathApi);
793
- return {
794
- dataDir,
795
- stateDir,
796
- logDir: explicitLogDir ?? defaultLogDir,
797
- observerDir: explicitObserverDir ?? legacyPanopticonHome ?? (fmlHome ? pathApi.join(fmlHome, "panopticon") : pathApi.join(homeDir, ".fml", "panopticon"))
798
- };
602
+ function waitForTokenRefresh(promise, signal) {
603
+ if (!signal) return promise;
604
+ if (signal.aborted) return Promise.resolve(null);
605
+ return new Promise((resolve, reject) => {
606
+ const onAbort = () => {
607
+ cleanup();
608
+ resolve(null);
609
+ };
610
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
611
+ signal.addEventListener("abort", onAbort, { once: true });
612
+ void promise.then(
613
+ (value) => {
614
+ cleanup();
615
+ resolve(value);
616
+ },
617
+ (error) => {
618
+ cleanup();
619
+ reject(error);
620
+ }
621
+ );
622
+ });
799
623
  }
800
- function observerPort() {
801
- const configured = process.env.FML_OBSERVER_PORT ?? process.env.PANOPTICON_PORT;
802
- if (configured) {
803
- const parsed = Number.parseInt(configured, 10);
804
- if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
805
- return parsed;
806
- }
624
+ function getOrStartTokenRefresh(flights, key, forceRefresh, refresh, signal) {
625
+ const existing = flights.get(key);
626
+ if (existing && (!forceRefresh || existing.forceRefresh)) {
627
+ return waitForTokenRefresh(existing.promise, signal);
807
628
  }
808
- return 4318 + (process.getuid?.() ?? 0) % 100;
809
- }
810
- var DIRECTORIES = resolveFmlDirectories();
811
- var FML_DATA_DIR = DIRECTORIES.dataDir;
812
- var FML_STATE_DIR = DIRECTORIES.stateDir;
813
- var FML_LOG_DIR = DIRECTORIES.logDir;
814
- var FML_OBSERVER_DIR = DIRECTORIES.observerDir;
815
- var FML_OBSERVER_PORT = observerPort();
816
- var FML_OBSERVER_STATE_FILE = path3.join(
817
- FML_OBSERVER_DIR,
818
- "observer.pid.json"
819
- );
820
- var FML_OBSERVER_LOCK_FILE = path3.join(
821
- FML_OBSERVER_DIR,
822
- "observer.lock"
823
- );
824
- var FML_OBSERVER_AUTH_TOKEN_FILE = path3.join(
825
- FML_OBSERVER_DIR,
826
- "auth-token"
827
- );
828
- var FML_OBSERVER_ENDPOINT_FILE = path3.join(
829
- FML_OBSERVER_DIR,
830
- "endpoint.json"
831
- );
832
- var FML_OBSERVER_LOG_FILE = path3.join(FML_LOG_DIR, "observer.log");
833
- var FML_UPDATE_LOG_FILE = path3.join(FML_LOG_DIR, "update.log");
834
- var FML_INSTALLATION_ID_FILE = path3.join(
835
- FML_STATE_DIR,
836
- "installation_id"
837
- );
838
- var FML_PANOPTICON_DB_FILE = path3.join(FML_STATE_DIR, "panopticon.db");
839
- var FML_PANOPTICON_DB_OBSERVER_STATE_FILE = `${FML_PANOPTICON_DB_FILE}.observer.pid.json`;
840
- var FML_PANOPTICON_DB_OBSERVER_LOCK_FILE = `${FML_PANOPTICON_DB_FILE}.observer.lock`;
841
- var FML_LEGACY_OBSERVER_DB_FILE = path3.join(
842
- FML_OBSERVER_DIR,
843
- "observer.db"
844
- );
845
- var FML_HOOK_INBOX_DIR = path3.join(FML_OBSERVER_DIR, "hook-inbox");
846
- var FML_HOOK_CONTEXT_STATE_DIR = path3.join(
847
- FML_OBSERVER_DIR,
848
- "hook-context-state"
849
- );
850
- var FML_HOOK_CAPTURE_DISABLED_FILE = path3.join(
851
- FML_OBSERVER_DIR,
852
- "hook-capture.disabled"
853
- );
854
-
855
- // src/config.ts
856
- var DEFAULT_PROD_URL = "https://api.fml.inc";
857
- var DEFAULT_TARGET_NAME = "fml";
858
- var ENV_CONFIG_PATH = path4.join(FML_DATA_DIR, "env.json");
859
- var ENV_NAME_RE = /^[A-Za-z0-9_-]+$/;
860
- function isValidEnvName(envName) {
861
- return ENV_NAME_RE.test(envName);
629
+ const operation = existing ? existing.promise.then(refresh, refresh) : refresh();
630
+ let flight;
631
+ const promise = operation.finally(() => {
632
+ if (flights.get(key) === flight) flights.delete(key);
633
+ });
634
+ flight = { forceRefresh, promise };
635
+ flights.set(key, flight);
636
+ return waitForTokenRefresh(promise, signal);
862
637
  }
863
- function normalizeConvexUrl(value) {
864
- const url = new URL(value);
865
- const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
866
- if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username || url.password || url.search || url.hash || url.pathname !== "" && url.pathname !== "/") {
867
- throw new Error(
868
- "FML environment URL must be HTTPS (HTTP is allowed only for loopback) with no path, credentials, query, or fragment"
869
- );
870
- }
871
- return url.origin;
638
+ function refreshLockPath(envName) {
639
+ return `${storePathFor(envName)}.refresh.lock`;
872
640
  }
873
- function normalizeAppUrl(value) {
874
- return normalizeConvexUrl(value);
641
+ function pendingOAuthRefreshPrefix(envName) {
642
+ return `${path6.basename(storePathFor(envName))}.refresh-result.`;
875
643
  }
876
- function readEnvConfig() {
644
+ function parsePendingOAuthRefresh(raw) {
877
645
  try {
878
- const parsed = JSON.parse(
879
- fs2.readFileSync(ENV_CONFIG_PATH, "utf8")
880
- );
881
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
882
- throw new Error("invalid environment config");
883
- }
884
- const config = parsed;
885
- if (typeof config.active !== "string" || !isValidEnvName(config.active)) {
886
- throw new Error("invalid active environment");
887
- }
888
- const environments = {};
889
- if (config.environments && typeof config.environments === "object" && !Array.isArray(config.environments)) {
890
- for (const [name, environment] of Object.entries(
891
- config.environments
892
- )) {
893
- if (!isValidEnvName(name) || !environment || typeof environment !== "object" || Array.isArray(environment)) {
894
- continue;
895
- }
896
- const entry = environment;
897
- if (typeof entry.convexUrl !== "string") continue;
898
- try {
899
- const appUrl = typeof entry.appUrl === "string" ? normalizeAppUrl(entry.appUrl) : void 0;
900
- environments[name] = {
901
- convexUrl: normalizeConvexUrl(entry.convexUrl),
902
- ...appUrl ? { appUrl } : {}
903
- };
904
- } catch {
905
- }
906
- }
646
+ const parsed = JSON.parse(raw);
647
+ const refreshed = parsed.refreshed;
648
+ if (parsed.version !== 1 || typeof parsed.previousRefreshTokenHash !== "string" || !/^[a-f0-9]{64}$/.test(parsed.previousRefreshTokenHash) || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs) || !refreshed || typeof refreshed.accessToken !== "string" || typeof refreshed.refreshToken !== "string" || typeof refreshed.expiresAt !== "number" || !Number.isFinite(refreshed.expiresAt) || !refreshed.user || typeof refreshed.user.id !== "string" || typeof refreshed.user.email !== "string" || typeof refreshed.user.name !== "string") {
649
+ return null;
907
650
  }
908
- environments[DEFAULT_TARGET_NAME] = { convexUrl: DEFAULT_PROD_URL };
909
- return {
910
- schemaVersion: 1,
911
- active: config.active,
912
- environments
913
- };
651
+ return parsed;
914
652
  } catch {
915
- return {
916
- schemaVersion: 1,
917
- active: DEFAULT_TARGET_NAME,
918
- environments: {
919
- [DEFAULT_TARGET_NAME]: { convexUrl: DEFAULT_PROD_URL }
920
- }
921
- };
653
+ return null;
922
654
  }
923
655
  }
924
- function resolveEnvConvexUrl(envName) {
925
- const configured = readEnvConfig().environments?.[envName]?.convexUrl;
926
- if (configured) return configured;
927
- if (envName === DEFAULT_TARGET_NAME) return DEFAULT_PROD_URL;
928
- return null;
929
- }
930
- function getActiveEnv() {
931
- const config = readEnvConfig();
932
- const name = config.active;
933
- const stored = config.environments?.[name];
934
- const override = process.env.FML_CONVEX_URL;
935
- const convexUrl = override ? normalizeConvexUrl(override) : resolveEnvConvexUrl(name);
936
- const appUrl = stored?.appUrl && stored.convexUrl === convexUrl ? stored.appUrl : null;
937
- return { name, convexUrl, appUrl };
938
- }
939
- function authStorePathFor(envName) {
940
- return path4.join(FML_DATA_DIR, `auth.${envName}.json`);
941
- }
942
- var CONVEX_URL = (process.env.FML_CONVEX_URL ?? getActiveEnv().convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
943
- var WORKOS_API_URL = "https://api.workos.com";
944
- function getSiteUrlForConvexUrl(convexUrl) {
945
- const explicit = process.env.CONVEX_SITE_URL;
946
- if (explicit) return explicit.replace(/\/$/, "");
947
- return (convexUrl ?? DEFAULT_PROD_URL).replace(/\/$/, "");
656
+ function stagePendingOAuthRefresh(previousRefreshToken, refreshed, envName) {
657
+ ensureDir(envName);
658
+ const resultPath = `${storePathFor(envName)}.refresh-result.${Date.now()}.${randomUUID3()}.json`;
659
+ const pending = {
660
+ version: 1,
661
+ previousRefreshTokenHash: credentialHash(previousRefreshToken),
662
+ refreshed,
663
+ createdAtMs: Date.now()
664
+ };
665
+ writeSecretFile(resultPath, `${JSON.stringify(pending)}
666
+ `);
667
+ return resultPath;
948
668
  }
949
-
950
- // src/sentry.ts
951
- var sentry = null;
952
- var client = null;
953
- var Sentry = {
954
- captureException: (err) => sentry?.captureException(err),
955
- captureMessage: (msg) => sentry?.captureMessage(msg),
956
- flush: async (timeoutMs) => {
957
- try {
958
- await client?.flush(timeoutMs);
959
- } catch {
669
+ function removePendingOAuthRefresh(resultPath) {
670
+ try {
671
+ fs4.unlinkSync(resultPath);
672
+ } catch (error) {
673
+ if (error.code !== "ENOENT") {
674
+ console.error(
675
+ `[fml] Auth: unable to remove a committed refresh result: ${error instanceof Error ? error.message : String(error)}`
676
+ );
960
677
  }
961
678
  }
962
- };
963
-
964
- // src/auth/token-store.ts
965
- var TOKEN_REFRESH_WINDOW_MS = 6e4;
966
- var OAUTH_REFRESH_LOCK_POLL_MS = 50;
967
- var OAUTH_REFRESH_LOCK_STALE_MS = 45e3;
968
- var OAUTH_REFRESH_RECOVERY_STALE_MS = 45e3;
969
- var OAUTH_REFRESH_LOCK_WAIT_MS = 6e4;
970
- var OAUTH_REFRESH_REQUEST_TIMEOUT_MS = 3e4;
971
- var PROCESS_IDENTITY_STALE_MS = 45e3;
972
- var PROCESS_IDENTITY_RECHECK_MS = 5e3;
973
- var PROCESS_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-instance");
974
- var PROCESS_IDENTITY_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-identity");
975
- var processGlobals = globalThis;
976
- var existingProcessInstanceId = processGlobals[PROCESS_INSTANCE_KEY];
977
- var CURRENT_PROCESS_INSTANCE_ID = typeof existingProcessInstanceId === "string" ? existingProcessInstanceId : randomUUID2();
978
- processGlobals[PROCESS_INSTANCE_KEY] = CURRENT_PROCESS_INSTANCE_ID;
979
- function getCurrentProcessIdentity() {
980
- let cache = processGlobals[PROCESS_IDENTITY_KEY];
981
- if (!cache) {
982
- cache = { resolved: false };
983
- processGlobals[PROCESS_IDENTITY_KEY] = cache;
984
- }
985
- if (!cache.resolved) {
986
- cache.value = getProcessIdentity(process.pid) ?? void 0;
987
- cache.resolved = true;
988
- }
989
- return cache.value;
990
- }
991
- function storePathFor(envName) {
992
- return authStorePathFor(envName ?? getActiveEnv().name);
993
- }
994
- function resolveEnvName(envName) {
995
- return envName ?? getActiveEnv().name;
996
679
  }
997
- function ensureDir(envName) {
998
- const dir = path5.dirname(storePathFor(envName));
999
- fs3.mkdirSync(dir, { recursive: true, mode: 448 });
680
+ function reconcilePendingOAuthRefreshes(envName, unreadableStorePolicy = "preserve-staged") {
681
+ const storePath = storePathFor(envName);
682
+ const directory = path6.dirname(storePath);
683
+ const prefix = pendingOAuthRefreshPrefix(envName);
684
+ let resultPaths;
1000
685
  try {
1001
- fs3.chmodSync(dir, 448);
1002
- } catch {
686
+ resultPaths = fs4.readdirSync(directory).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".json")).map((entry) => path6.join(directory, entry));
687
+ } catch (error) {
688
+ if (error.code === "ENOENT") return;
689
+ throw error;
1003
690
  }
1004
- }
1005
- function writeSecretFile(filePath, data) {
1006
- const temporaryFile = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
1007
- let descriptor = null;
1008
- try {
1009
- descriptor = fs3.openSync(temporaryFile, "wx", 384);
1010
- fs3.writeFileSync(descriptor, data, { encoding: "utf8" });
1011
- fs3.fsyncSync(descriptor);
1012
- fs3.closeSync(descriptor);
1013
- descriptor = null;
1014
- fs3.renameSync(temporaryFile, filePath);
691
+ const pendingResults = resultPaths.map((resultPath) => {
692
+ let pending = null;
1015
693
  try {
1016
- fs3.chmodSync(filePath, 384);
1017
- } catch {
1018
- }
1019
- } finally {
1020
- if (descriptor !== null) {
1021
- try {
1022
- fs3.closeSync(descriptor);
1023
- } catch {
1024
- }
1025
- }
1026
- try {
1027
- fs3.unlinkSync(temporaryFile);
1028
- } catch {
1029
- }
1030
- }
1031
- }
1032
- function readTokens(envName) {
1033
- let raw;
1034
- try {
1035
- raw = fs3.readFileSync(storePathFor(envName), "utf-8");
1036
- } catch {
1037
- return null;
1038
- }
1039
- try {
1040
- return parseStoredAuthShape(JSON.parse(raw));
1041
- } catch {
1042
- return null;
1043
- }
1044
- }
1045
- var CorruptStoredCredentialsError = class extends Error {
1046
- };
1047
- function parseStoredAuthShape(parsed) {
1048
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1049
- return null;
1050
- }
1051
- const auth = parsed;
1052
- const user = auth.user;
1053
- if (typeof auth.accessToken !== "string" || typeof auth.refreshToken !== "string" || typeof auth.expiresAt !== "number" || !Number.isFinite(auth.expiresAt) || !user || typeof user.id !== "string" || typeof user.email !== "string" || typeof user.name !== "string" || auth.orgId !== void 0 && typeof auth.orgId !== "string" || auth.orgSlug !== void 0 && typeof auth.orgSlug !== "string" || auth.workosClientId !== void 0 && typeof auth.workosClientId !== "string" || auth.siteUrl !== void 0 && typeof auth.siteUrl !== "string" || auth.tokenType !== void 0 && auth.tokenType !== "oauth" && auth.tokenType !== "service" || auth.refreshInvalidAt !== void 0 && (typeof auth.refreshInvalidAt !== "number" || !Number.isFinite(auth.refreshInvalidAt))) {
1054
- return null;
1055
- }
1056
- return auth;
1057
- }
1058
- function readTokensForReconciliation(envName) {
1059
- let raw;
1060
- try {
1061
- raw = fs3.readFileSync(storePathFor(envName), "utf8");
1062
- } catch (error) {
1063
- if (error.code === "ENOENT") return null;
1064
- throw error;
1065
- }
1066
- let parsed;
1067
- try {
1068
- parsed = JSON.parse(raw);
1069
- } catch (error) {
1070
- throw new CorruptStoredCredentialsError(
1071
- "Stored credentials are not valid JSON",
1072
- { cause: error }
1073
- );
1074
- }
1075
- const auth = parseStoredAuthShape(parsed);
1076
- if (!auth) {
1077
- throw new CorruptStoredCredentialsError(
1078
- "Stored credentials have an invalid shape"
1079
- );
1080
- }
1081
- return auth;
1082
- }
1083
- function writeTokensUnlocked(auth, envName) {
1084
- ensureDir(envName);
1085
- writeSecretFile(storePathFor(envName), JSON.stringify(auth, null, 2));
1086
- }
1087
- function credentialHash(value) {
1088
- return createHash2("sha256").update(value).digest("hex");
1089
- }
1090
- function describeError(error) {
1091
- return error instanceof Error ? error.message : String(error);
1092
- }
1093
- function getSyncDestinationAuthBinding(envName) {
1094
- const resolvedEnvName = resolveEnvName(envName);
1095
- const envToken = process.env.FML_TOKEN;
1096
- if (envToken?.startsWith("fml_srt_")) {
1097
- return {
1098
- destinationIdentity: `service-refresh:${credentialHash(envToken)}`
1099
- };
1100
- }
1101
- if (envToken?.startsWith("fml_st_")) {
1102
- return {
1103
- destinationIdentity: `service-access:${credentialHash(envToken)}`
1104
- };
1105
- }
1106
- const stored = readTokens(resolvedEnvName);
1107
- if (!stored) return null;
1108
- if (stored.tokenType === "service") {
1109
- return {
1110
- destinationIdentity: `service-refresh:${credentialHash(stored.refreshToken)}`
1111
- };
1112
- }
1113
- const destinationIdentity = `oauth-user:${stored.user.id}`;
1114
- if (stored.orgId && stored.orgSlug) {
1115
- return {
1116
- destinationIdentity,
1117
- organizationId: stored.orgId,
1118
- organizationSlug: stored.orgSlug
1119
- };
1120
- }
1121
- return { destinationIdentity, organizationSelectionRequired: true };
1122
- }
1123
- function getSyncDestinationIdentity(envName) {
1124
- return getSyncDestinationAuthBinding(envName)?.destinationIdentity ?? null;
1125
- }
1126
- var serviceTokenCaches = /* @__PURE__ */ new Map();
1127
- var serviceRefreshPromises = /* @__PURE__ */ new Map();
1128
- var oauthRefreshPromises = /* @__PURE__ */ new Map();
1129
- var cacheKey = (envName) => envName;
1130
- function waitForTokenRefresh(promise, signal) {
1131
- if (!signal) return promise;
1132
- if (signal.aborted) return Promise.resolve(null);
1133
- return new Promise((resolve, reject) => {
1134
- const onAbort = () => {
1135
- cleanup();
1136
- resolve(null);
1137
- };
1138
- const cleanup = () => signal.removeEventListener("abort", onAbort);
1139
- signal.addEventListener("abort", onAbort, { once: true });
1140
- void promise.then(
1141
- (value) => {
1142
- cleanup();
1143
- resolve(value);
1144
- },
1145
- (error) => {
1146
- cleanup();
1147
- reject(error);
1148
- }
1149
- );
1150
- });
1151
- }
1152
- function getOrStartTokenRefresh(flights, key, forceRefresh, refresh, signal) {
1153
- const existing = flights.get(key);
1154
- if (existing && (!forceRefresh || existing.forceRefresh)) {
1155
- return waitForTokenRefresh(existing.promise, signal);
1156
- }
1157
- const operation = existing ? existing.promise.then(refresh, refresh) : refresh();
1158
- let flight;
1159
- const promise = operation.finally(() => {
1160
- if (flights.get(key) === flight) flights.delete(key);
1161
- });
1162
- flight = { forceRefresh, promise };
1163
- flights.set(key, flight);
1164
- return waitForTokenRefresh(promise, signal);
1165
- }
1166
- function refreshLockPath(envName) {
1167
- return `${storePathFor(envName)}.refresh.lock`;
1168
- }
1169
- function pendingOAuthRefreshPrefix(envName) {
1170
- return `${path5.basename(storePathFor(envName))}.refresh-result.`;
1171
- }
1172
- function parsePendingOAuthRefresh(raw) {
1173
- try {
1174
- const parsed = JSON.parse(raw);
1175
- const refreshed = parsed.refreshed;
1176
- if (parsed.version !== 1 || typeof parsed.previousRefreshTokenHash !== "string" || !/^[a-f0-9]{64}$/.test(parsed.previousRefreshTokenHash) || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs) || !refreshed || typeof refreshed.accessToken !== "string" || typeof refreshed.refreshToken !== "string" || typeof refreshed.expiresAt !== "number" || !Number.isFinite(refreshed.expiresAt) || !refreshed.user || typeof refreshed.user.id !== "string" || typeof refreshed.user.email !== "string" || typeof refreshed.user.name !== "string") {
1177
- return null;
1178
- }
1179
- return parsed;
1180
- } catch {
1181
- return null;
1182
- }
1183
- }
1184
- function stagePendingOAuthRefresh(previousRefreshToken, refreshed, envName) {
1185
- ensureDir(envName);
1186
- const resultPath = `${storePathFor(envName)}.refresh-result.${Date.now()}.${randomUUID2()}.json`;
1187
- const pending = {
1188
- version: 1,
1189
- previousRefreshTokenHash: credentialHash(previousRefreshToken),
1190
- refreshed,
1191
- createdAtMs: Date.now()
1192
- };
1193
- writeSecretFile(resultPath, `${JSON.stringify(pending)}
1194
- `);
1195
- return resultPath;
1196
- }
1197
- function removePendingOAuthRefresh(resultPath) {
1198
- try {
1199
- fs3.unlinkSync(resultPath);
1200
- } catch (error) {
1201
- if (error.code !== "ENOENT") {
1202
- console.error(
1203
- `[fml] Auth: unable to remove a committed refresh result: ${error instanceof Error ? error.message : String(error)}`
1204
- );
1205
- }
1206
- }
1207
- }
1208
- function reconcilePendingOAuthRefreshes(envName, unreadableStorePolicy = "preserve-staged") {
1209
- const storePath = storePathFor(envName);
1210
- const directory = path5.dirname(storePath);
1211
- const prefix = pendingOAuthRefreshPrefix(envName);
1212
- let resultPaths;
1213
- try {
1214
- resultPaths = fs3.readdirSync(directory).filter((entry) => entry.startsWith(prefix) && entry.endsWith(".json")).map((entry) => path5.join(directory, entry));
1215
- } catch (error) {
1216
- if (error.code === "ENOENT") return;
1217
- throw error;
1218
- }
1219
- const pendingResults = resultPaths.map((resultPath) => {
1220
- let pending = null;
1221
- try {
1222
- pending = parsePendingOAuthRefresh(fs3.readFileSync(resultPath, "utf8"));
1223
- } catch (error) {
1224
- if (error.code !== "ENOENT") throw error;
694
+ pending = parsePendingOAuthRefresh(fs4.readFileSync(resultPath, "utf8"));
695
+ } catch (error) {
696
+ if (error.code !== "ENOENT") throw error;
1225
697
  }
1226
698
  return { resultPath, pending };
1227
699
  }).sort(
@@ -1274,7 +746,7 @@ function parseRefreshLockOwner(raw) {
1274
746
  function getProcessIdentity(pid) {
1275
747
  try {
1276
748
  if (process.platform === "linux") {
1277
- const stat = fs3.readFileSync(`/proc/${pid}/stat`, "utf8");
749
+ const stat = fs4.readFileSync(`/proc/${pid}/stat`, "utf8");
1278
750
  const fieldsAfterCommand = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
1279
751
  const startTimeTicks = fieldsAfterCommand[19];
1280
752
  return startTimeTicks ? `linux:${startTimeTicks}` : null;
@@ -1326,7 +798,7 @@ function parseRecoveryMarkerOwner(raw) {
1326
798
  function snapshotLeaseFile(filePath) {
1327
799
  let stat;
1328
800
  try {
1329
- stat = fs3.lstatSync(filePath);
801
+ stat = fs4.lstatSync(filePath);
1330
802
  } catch (error) {
1331
803
  if (error.code === "ENOENT") return null;
1332
804
  throw error;
@@ -1335,7 +807,7 @@ function snapshotLeaseFile(filePath) {
1335
807
  try {
1336
808
  return {
1337
809
  stat,
1338
- raw: fs3.readFileSync(path5.join(filePath, "owner.json"), "utf8")
810
+ raw: fs4.readFileSync(path6.join(filePath, "owner.json"), "utf8")
1339
811
  };
1340
812
  } catch (error) {
1341
813
  if (error.code === "ENOENT") {
@@ -1345,7 +817,7 @@ function snapshotLeaseFile(filePath) {
1345
817
  }
1346
818
  }
1347
819
  try {
1348
- return { stat, raw: fs3.readFileSync(filePath, "utf8") };
820
+ return { stat, raw: fs4.readFileSync(filePath, "utf8") };
1349
821
  } catch (error) {
1350
822
  if (error.code === "ENOENT") return null;
1351
823
  throw error;
@@ -1357,21 +829,21 @@ function snapshotsMatch(observed, current) {
1357
829
  function restoreMovedLease(movedPath, originalPath) {
1358
830
  let movedStat;
1359
831
  try {
1360
- movedStat = fs3.lstatSync(movedPath);
832
+ movedStat = fs4.lstatSync(movedPath);
1361
833
  } catch (error) {
1362
834
  if (error.code === "ENOENT") return;
1363
835
  throw error;
1364
836
  }
1365
837
  if (!movedStat.isFile()) return;
1366
838
  try {
1367
- fs3.linkSync(movedPath, originalPath);
839
+ fs4.linkSync(movedPath, originalPath);
1368
840
  } catch (error) {
1369
841
  const code = error.code;
1370
842
  if (code === "EEXIST" || code === "ENOENT") return;
1371
843
  throw error;
1372
844
  }
1373
845
  try {
1374
- fs3.unlinkSync(movedPath);
846
+ fs4.unlinkSync(movedPath);
1375
847
  } catch (error) {
1376
848
  if (error.code !== "ENOENT") throw error;
1377
849
  }
@@ -1385,27 +857,27 @@ function pathMatchesClaim(filePath, claimedStat, ownerId, parseOwner) {
1385
857
  function markClaimReleased(filePath, claimedStat, owner, parseOwner) {
1386
858
  let descriptor;
1387
859
  try {
1388
- descriptor = fs3.openSync(filePath, "r+");
860
+ descriptor = fs4.openSync(filePath, "r+");
1389
861
  } catch (error) {
1390
862
  if (error.code === "ENOENT") return false;
1391
863
  throw error;
1392
864
  }
1393
865
  try {
1394
- const currentStat = fs3.fstatSync(descriptor);
866
+ const currentStat = fs4.fstatSync(descriptor);
1395
867
  if (currentStat.dev !== claimedStat.dev || currentStat.ino !== claimedStat.ino) {
1396
868
  return false;
1397
869
  }
1398
- const currentOwner = parseOwner(fs3.readFileSync(descriptor, "utf8"));
870
+ const currentOwner = parseOwner(fs4.readFileSync(descriptor, "utf8"));
1399
871
  if (currentOwner?.ownerId !== owner.ownerId) return false;
1400
872
  const serialized = Buffer.from(
1401
873
  `${JSON.stringify({ ...owner, releasedAtMs: Date.now() })}
1402
874
  `,
1403
875
  "utf8"
1404
876
  );
1405
- fs3.ftruncateSync(descriptor, 0);
877
+ fs4.ftruncateSync(descriptor, 0);
1406
878
  let offset = 0;
1407
879
  while (offset < serialized.length) {
1408
- offset += fs3.writeSync(
880
+ offset += fs4.writeSync(
1409
881
  descriptor,
1410
882
  serialized,
1411
883
  offset,
@@ -1413,19 +885,19 @@ function markClaimReleased(filePath, claimedStat, owner, parseOwner) {
1413
885
  offset
1414
886
  );
1415
887
  }
1416
- fs3.fsyncSync(descriptor);
888
+ fs4.fsyncSync(descriptor);
1417
889
  return true;
1418
890
  } finally {
1419
- fs3.closeSync(descriptor);
891
+ fs4.closeSync(descriptor);
1420
892
  }
1421
893
  }
1422
894
  function releaseOwnedLease(filePath, claimedStat, owner, parseOwner, description) {
1423
- const releasedPath = `${filePath}.released.${randomUUID2()}`;
895
+ const releasedPath = `${filePath}.released.${randomUUID3()}`;
1424
896
  let moved = false;
1425
897
  try {
1426
898
  if (!markClaimReleased(filePath, claimedStat, owner, parseOwner)) return;
1427
899
  try {
1428
- fs3.renameSync(filePath, releasedPath);
900
+ fs4.renameSync(filePath, releasedPath);
1429
901
  moved = true;
1430
902
  } catch (error) {
1431
903
  if (error.code === "ENOENT") return;
@@ -1438,7 +910,7 @@ function releaseOwnedLease(filePath, claimedStat, owner, parseOwner, description
1438
910
  moved = false;
1439
911
  return;
1440
912
  }
1441
- fs3.rmSync(releasedPath, {
913
+ fs4.rmSync(releasedPath, {
1442
914
  recursive: released.stat.isDirectory(),
1443
915
  force: true
1444
916
  });
@@ -1490,7 +962,6 @@ function isProcessAlive(pid) {
1490
962
  return error.code === "EPERM";
1491
963
  }
1492
964
  }
1493
- var processIdentityChecks = /* @__PURE__ */ new Map();
1494
965
  function ownerPidWasReused(owner, lockMtimeMs) {
1495
966
  const now = Date.now();
1496
967
  if (now - Math.max(owner.createdAtMs, lockMtimeMs) < PROCESS_IDENTITY_STALE_MS)
@@ -1513,9 +984,9 @@ function ownerPidWasReused(owner, lockMtimeMs) {
1513
984
  return currentIdentity !== null && currentIdentity !== owner.processIdentity;
1514
985
  }
1515
986
  function writeClaimToDescriptor(descriptor, serializedOwner) {
1516
- const claimedStat = fs3.fstatSync(descriptor);
1517
- fs3.writeFileSync(descriptor, serializedOwner, { encoding: "utf8" });
1518
- fs3.fsyncSync(descriptor);
987
+ const claimedStat = fs4.fstatSync(descriptor);
988
+ fs4.writeFileSync(descriptor, serializedOwner, { encoding: "utf8" });
989
+ fs4.fsyncSync(descriptor);
1519
990
  return claimedStat;
1520
991
  }
1521
992
  function recoverAbandonedRecoveryMarker(recoveryPath) {
@@ -1525,15 +996,15 @@ function recoverAbandonedRecoveryMarker(recoveryPath) {
1525
996
  const ownerAlive = owner ? isProcessAlive(owner.pid) : false;
1526
997
  const reclaimable = owner ? owner.releasedAtMs !== void 0 || !ownerAlive || ownerAlive && ownerPidWasReused(owner, observed.stat.mtimeMs) : Date.now() - observed.stat.mtimeMs > OAUTH_REFRESH_RECOVERY_STALE_MS;
1527
998
  if (!reclaimable) return;
1528
- const abandonedPath = `${recoveryPath}.stale.${randomUUID2()}`;
999
+ const abandonedPath = `${recoveryPath}.stale.${randomUUID3()}`;
1529
1000
  try {
1530
- fs3.renameSync(recoveryPath, abandonedPath);
1001
+ fs4.renameSync(recoveryPath, abandonedPath);
1531
1002
  const moved = snapshotLeaseFile(abandonedPath);
1532
1003
  if (!moved || !snapshotsMatch(observed, moved)) {
1533
1004
  restoreMovedLease(abandonedPath, recoveryPath);
1534
1005
  return;
1535
1006
  }
1536
- fs3.rmSync(abandonedPath, {
1007
+ fs4.rmSync(abandonedPath, {
1537
1008
  recursive: moved.stat.isDirectory(),
1538
1009
  force: true
1539
1010
  });
@@ -1542,7 +1013,7 @@ function recoverAbandonedRecoveryMarker(recoveryPath) {
1542
1013
  }
1543
1014
  }
1544
1015
  function acquireRecoveryMarker(recoveryPath) {
1545
- const ownerId = randomUUID2();
1016
+ const ownerId = randomUUID3();
1546
1017
  const owner = {
1547
1018
  version: 1,
1548
1019
  ownerId,
@@ -1553,7 +1024,7 @@ function acquireRecoveryMarker(recoveryPath) {
1553
1024
  };
1554
1025
  let descriptor;
1555
1026
  try {
1556
- descriptor = fs3.openSync(recoveryPath, "wx", 384);
1027
+ descriptor = fs4.openSync(recoveryPath, "wx", 384);
1557
1028
  } catch (error) {
1558
1029
  if (error.code === "EEXIST") return null;
1559
1030
  throw error;
@@ -1566,7 +1037,7 @@ function acquireRecoveryMarker(recoveryPath) {
1566
1037
  `
1567
1038
  );
1568
1039
  } finally {
1569
- fs3.closeSync(descriptor);
1040
+ fs4.closeSync(descriptor);
1570
1041
  }
1571
1042
  const isOwned = () => pathMatchesClaim(
1572
1043
  recoveryPath,
@@ -1610,15 +1081,15 @@ function recoverStaleRefreshLock(lockPath) {
1610
1081
  }
1611
1082
  if (!reclaimable) return;
1612
1083
  if (!recovery.isOwned()) return;
1613
- const abandonedPath = `${lockPath}.stale.${randomUUID2()}`;
1084
+ const abandonedPath = `${lockPath}.stale.${randomUUID3()}`;
1614
1085
  try {
1615
- fs3.renameSync(lockPath, abandonedPath);
1086
+ fs4.renameSync(lockPath, abandonedPath);
1616
1087
  const moved = snapshotLeaseFile(abandonedPath);
1617
1088
  if (!moved || !snapshotsMatch(observed, moved)) {
1618
1089
  restoreMovedLease(abandonedPath, lockPath);
1619
1090
  return;
1620
1091
  }
1621
- fs3.rmSync(abandonedPath, {
1092
+ fs4.rmSync(abandonedPath, {
1622
1093
  recursive: moved.stat.isDirectory(),
1623
1094
  force: true
1624
1095
  });
@@ -1645,7 +1116,7 @@ async function acquireRefreshLock(envName, signal) {
1645
1116
  if (signal?.aborted) {
1646
1117
  throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
1647
1118
  }
1648
- if (fs3.existsSync(recoveryPath)) {
1119
+ if (fs4.existsSync(recoveryPath)) {
1649
1120
  try {
1650
1121
  recoverAbandonedRecoveryMarker(recoveryPath);
1651
1122
  } catch (recoveryError) {
@@ -1659,7 +1130,7 @@ async function acquireRefreshLock(envName, signal) {
1659
1130
  }
1660
1131
  let descriptor;
1661
1132
  try {
1662
- descriptor = fs3.openSync(lockPath, "wx", 384);
1133
+ descriptor = fs4.openSync(lockPath, "wx", 384);
1663
1134
  } catch (error) {
1664
1135
  if (error.code !== "EEXIST") {
1665
1136
  console.error(
@@ -1678,7 +1149,7 @@ async function acquireRefreshLock(envName, signal) {
1678
1149
  await sleep(OAUTH_REFRESH_LOCK_POLL_MS, signal);
1679
1150
  continue;
1680
1151
  }
1681
- const ownerId = randomUUID2();
1152
+ const ownerId = randomUUID3();
1682
1153
  const owner = {
1683
1154
  version: 1,
1684
1155
  ownerId,
@@ -1695,13 +1166,13 @@ async function acquireRefreshLock(envName, signal) {
1695
1166
  `
1696
1167
  );
1697
1168
  } catch (error) {
1698
- fs3.closeSync(descriptor);
1169
+ fs4.closeSync(descriptor);
1699
1170
  console.error(
1700
1171
  `[fml] Auth: unable to initialize refresh lock: ${error instanceof Error ? error.message : String(error)}`
1701
1172
  );
1702
1173
  return null;
1703
1174
  }
1704
- fs3.closeSync(descriptor);
1175
+ fs4.closeSync(descriptor);
1705
1176
  const isOwned = () => pathMatchesClaim(lockPath, claimedStat, ownerId, parseRefreshLockOwner);
1706
1177
  const releaseClaim = () => releaseOwnedLease(
1707
1178
  lockPath,
@@ -1716,7 +1187,7 @@ async function acquireRefreshLock(envName, signal) {
1716
1187
  }
1717
1188
  let entryBlocked;
1718
1189
  try {
1719
- entryBlocked = fs3.existsSync(recoveryPath) || !isOwned();
1190
+ entryBlocked = fs4.existsSync(recoveryPath) || !isOwned();
1720
1191
  } catch (error) {
1721
1192
  console.error(
1722
1193
  `[fml] Auth: unable to verify refresh lock ownership: ${describeError(error)}`
@@ -1882,7 +1353,7 @@ async function getValidToken(opts) {
1882
1353
  return stored.accessToken;
1883
1354
  }
1884
1355
  if (stored.tokenType === "service") {
1885
- return await getOrStartTokenRefresh(
1356
+ const accessToken2 = await getOrStartTokenRefresh(
1886
1357
  serviceRefreshPromises,
1887
1358
  key,
1888
1359
  forceRefresh,
@@ -1928,6 +1399,12 @@ async function getValidToken(opts) {
1928
1399
  }),
1929
1400
  opts?.signal
1930
1401
  );
1402
+ await refreshTelemetryConfigAfterTokenRefresh(
1403
+ envName,
1404
+ accessToken2,
1405
+ opts?.signal
1406
+ );
1407
+ return accessToken2;
1931
1408
  }
1932
1409
  if (stored.refreshInvalidAt) {
1933
1410
  console.error(
@@ -1935,13 +1412,34 @@ async function getValidToken(opts) {
1935
1412
  );
1936
1413
  return null;
1937
1414
  }
1938
- return await getOrStartTokenRefresh(
1415
+ const accessToken = await getOrStartTokenRefresh(
1939
1416
  oauthRefreshPromises,
1940
1417
  key,
1941
1418
  forceRefresh,
1942
1419
  () => refreshTokenWithLock(envName, forceRefresh, opts?.signal),
1943
1420
  opts?.signal
1944
1421
  );
1422
+ await refreshTelemetryConfigAfterTokenRefresh(
1423
+ envName,
1424
+ accessToken,
1425
+ opts?.signal
1426
+ );
1427
+ return accessToken;
1428
+ }
1429
+ async function refreshTelemetryConfigAfterTokenRefresh(envName, accessToken, signal) {
1430
+ if (!accessToken || signal?.aborted) return;
1431
+ try {
1432
+ const current = readTokens(envName);
1433
+ if (current?.accessToken !== accessToken) return;
1434
+ const { refreshCliTelemetryConfig: refreshCliTelemetryConfig2 } = await Promise.resolve().then(() => (init_telemetry_config(), telemetry_config_exports));
1435
+ await refreshCliTelemetryConfig2({
1436
+ envName,
1437
+ accessToken,
1438
+ siteUrl: current.siteUrl,
1439
+ signal
1440
+ });
1441
+ } catch {
1442
+ }
1945
1443
  }
1946
1444
  async function refreshTokenWithLock(envName, forceRefresh = false, signal) {
1947
1445
  const lock = await acquireRefreshLock(envName, signal);
@@ -2047,7 +1545,9 @@ async function refreshToken(stored, lock, envName, signal) {
2047
1545
  },
2048
1546
  orgId: stored.orgId,
2049
1547
  orgSlug: stored.orgSlug,
2050
- workosClientId: stored.workosClientId
1548
+ workosClientId: stored.workosClientId,
1549
+ // The backend this login belongs to does not change on rotation.
1550
+ ...stored.siteUrl ? { siteUrl: stored.siteUrl } : {}
2051
1551
  };
2052
1552
  const pendingPath = stagePendingOAuthRefresh(
2053
1553
  stored.refreshToken,
@@ -2082,6 +1582,733 @@ async function refreshToken(stored, lock, envName, signal) {
2082
1582
  return null;
2083
1583
  }
2084
1584
  }
1585
+ var TOKEN_REFRESH_WINDOW_MS, OAUTH_REFRESH_LOCK_POLL_MS, OAUTH_REFRESH_LOCK_STALE_MS, OAUTH_REFRESH_RECOVERY_STALE_MS, OAUTH_REFRESH_LOCK_WAIT_MS, OAUTH_REFRESH_REQUEST_TIMEOUT_MS, PROCESS_IDENTITY_STALE_MS, PROCESS_IDENTITY_RECHECK_MS, PROCESS_INSTANCE_KEY, PROCESS_IDENTITY_KEY, processGlobals, existingProcessInstanceId, CURRENT_PROCESS_INSTANCE_ID, CorruptStoredCredentialsError, serviceTokenCaches, serviceRefreshPromises, oauthRefreshPromises, cacheKey, processIdentityChecks;
1586
+ var init_token_store = __esm({
1587
+ "src/auth/token-store.ts"() {
1588
+ "use strict";
1589
+ init_config();
1590
+ init_sentry();
1591
+ TOKEN_REFRESH_WINDOW_MS = 6e4;
1592
+ OAUTH_REFRESH_LOCK_POLL_MS = 50;
1593
+ OAUTH_REFRESH_LOCK_STALE_MS = 45e3;
1594
+ OAUTH_REFRESH_RECOVERY_STALE_MS = 45e3;
1595
+ OAUTH_REFRESH_LOCK_WAIT_MS = 6e4;
1596
+ OAUTH_REFRESH_REQUEST_TIMEOUT_MS = 3e4;
1597
+ PROCESS_IDENTITY_STALE_MS = 45e3;
1598
+ PROCESS_IDENTITY_RECHECK_MS = 5e3;
1599
+ PROCESS_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-instance");
1600
+ PROCESS_IDENTITY_KEY = /* @__PURE__ */ Symbol.for("@fml-inc/fml:auth-process-identity");
1601
+ processGlobals = globalThis;
1602
+ existingProcessInstanceId = processGlobals[PROCESS_INSTANCE_KEY];
1603
+ CURRENT_PROCESS_INSTANCE_ID = typeof existingProcessInstanceId === "string" ? existingProcessInstanceId : randomUUID3();
1604
+ processGlobals[PROCESS_INSTANCE_KEY] = CURRENT_PROCESS_INSTANCE_ID;
1605
+ CorruptStoredCredentialsError = class extends Error {
1606
+ };
1607
+ serviceTokenCaches = /* @__PURE__ */ new Map();
1608
+ serviceRefreshPromises = /* @__PURE__ */ new Map();
1609
+ oauthRefreshPromises = /* @__PURE__ */ new Map();
1610
+ cacheKey = (envName) => envName;
1611
+ processIdentityChecks = /* @__PURE__ */ new Map();
1612
+ }
1613
+ });
1614
+
1615
+ // src/pi/extension.ts
1616
+ import { randomUUID as randomUUID4 } from "node:crypto";
1617
+ import fs5 from "node:fs";
1618
+ import os2 from "node:os";
1619
+ import path8 from "node:path";
1620
+
1621
+ // ../../packages/agent-targets/src/install/pi/index.ts
1622
+ import fs from "node:fs";
1623
+ import path from "node:path";
1624
+ import { randomUUID } from "node:crypto";
1625
+ var FML_PI_EXTENSION_MARKER = "__FML_PI_EXTENSION_V1__";
1626
+ var LEGACY_PI_EXTENSION_MARKER = "__panopticonPiExtensionTest";
1627
+ var LEGACY_PI_EXTENSION_SIGNATURES = Object.freeze([
1628
+ "PANOPTICON_PORT",
1629
+ '"/hooks"',
1630
+ "UserPromptSubmit",
1631
+ "PreToolUse",
1632
+ "PostToolUse",
1633
+ "SessionEnd"
1634
+ ]);
1635
+ function pathApiFor(platform) {
1636
+ return platform === "win32" ? path.win32 : path.posix;
1637
+ }
1638
+ function resolvePiAgentDir(context, pathApi) {
1639
+ const configured = context.env.PI_CODING_AGENT_DIR?.trim();
1640
+ if (!configured) {
1641
+ return pathApi.join(context.homeDir, ".pi", "agent");
1642
+ }
1643
+ if (configured === "~") return context.homeDir;
1644
+ if (configured.startsWith("~/") || configured.startsWith("~\\")) {
1645
+ return pathApi.join(context.homeDir, configured.slice(2));
1646
+ }
1647
+ return pathApi.normalize(configured);
1648
+ }
1649
+ function readFileIfPresent(filePath) {
1650
+ try {
1651
+ return fs.readFileSync(filePath, "utf8");
1652
+ } catch (error) {
1653
+ if (error.code === "ENOENT") return null;
1654
+ throw error;
1655
+ }
1656
+ }
1657
+ function isOwnedExtension(source, legacy = false) {
1658
+ return source.includes(FML_PI_EXTENSION_MARKER) || legacy && (source.includes(LEGACY_PI_EXTENSION_MARKER) || LEGACY_PI_EXTENSION_SIGNATURES.every(
1659
+ (signature) => source.includes(signature)
1660
+ ));
1661
+ }
1662
+ function removeLegacyExtension(filePath) {
1663
+ const source = readFileIfPresent(filePath);
1664
+ if (source === null) return { removed: false, preserved: false };
1665
+ if (!isOwnedExtension(source, true)) {
1666
+ return { removed: false, preserved: true };
1667
+ }
1668
+ fs.rmSync(filePath);
1669
+ return { removed: true, preserved: false };
1670
+ }
1671
+ function writeExtensionAtomically(filePath, source) {
1672
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
1673
+ const temporaryPath = path.join(
1674
+ path.dirname(filePath),
1675
+ `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`
1676
+ );
1677
+ try {
1678
+ fs.writeFileSync(temporaryPath, source, { encoding: "utf8", mode: 384 });
1679
+ if (process.platform === "win32" && fs.existsSync(filePath)) {
1680
+ fs.rmSync(filePath);
1681
+ }
1682
+ fs.renameSync(temporaryPath, filePath);
1683
+ } finally {
1684
+ fs.rmSync(temporaryPath, { force: true });
1685
+ }
1686
+ }
1687
+ var PI_EXTENSION_INSTALLER = Object.freeze({
1688
+ resolvePaths(context) {
1689
+ const pathApi = pathApiFor(context.platform);
1690
+ const extensionsDir = pathApi.join(
1691
+ resolvePiAgentDir(context, pathApi),
1692
+ "extensions"
1693
+ );
1694
+ return {
1695
+ sourcePath: pathApi.join(
1696
+ context.packageRoot,
1697
+ "dist",
1698
+ "pi",
1699
+ "extension.js"
1700
+ ),
1701
+ extensionsDir,
1702
+ destinationPath: pathApi.join(extensionsDir, "fml.js"),
1703
+ legacyDestinationPath: pathApi.join(extensionsDir, "panopticon.js")
1704
+ };
1705
+ },
1706
+ install(paths) {
1707
+ const source = fs.readFileSync(paths.sourcePath, "utf8");
1708
+ if (!source.includes(FML_PI_EXTENSION_MARKER)) {
1709
+ throw new Error(
1710
+ `Packaged Pi extension is invalid: ${paths.sourcePath} does not contain the FML ownership marker`
1711
+ );
1712
+ }
1713
+ const existing = readFileIfPresent(paths.destinationPath);
1714
+ if (existing !== null && !isOwnedExtension(existing)) {
1715
+ const legacy2 = removeLegacyExtension(paths.legacyDestinationPath);
1716
+ return {
1717
+ status: "preserved",
1718
+ destinationPath: paths.destinationPath,
1719
+ reason: "an extension not managed by FML already exists at this path",
1720
+ legacyRemoved: legacy2.removed,
1721
+ legacyPreserved: legacy2.preserved
1722
+ };
1723
+ }
1724
+ const status = existing === null ? "installed" : existing === source ? "unchanged" : "updated";
1725
+ if (status !== "unchanged") {
1726
+ writeExtensionAtomically(paths.destinationPath, source);
1727
+ }
1728
+ const legacy = removeLegacyExtension(paths.legacyDestinationPath);
1729
+ return {
1730
+ status,
1731
+ destinationPath: paths.destinationPath,
1732
+ legacyRemoved: legacy.removed,
1733
+ legacyPreserved: legacy.preserved
1734
+ };
1735
+ },
1736
+ uninstall(paths) {
1737
+ const existing = readFileIfPresent(paths.destinationPath);
1738
+ const legacy = removeLegacyExtension(paths.legacyDestinationPath);
1739
+ if (existing === null) {
1740
+ return {
1741
+ status: "absent",
1742
+ destinationPath: paths.destinationPath,
1743
+ legacyRemoved: legacy.removed,
1744
+ legacyPreserved: legacy.preserved
1745
+ };
1746
+ }
1747
+ if (!isOwnedExtension(existing)) {
1748
+ return {
1749
+ status: "preserved",
1750
+ destinationPath: paths.destinationPath,
1751
+ reason: "the existing extension is not managed by FML",
1752
+ legacyRemoved: legacy.removed,
1753
+ legacyPreserved: legacy.preserved
1754
+ };
1755
+ }
1756
+ fs.rmSync(paths.destinationPath);
1757
+ return {
1758
+ status: "removed",
1759
+ destinationPath: paths.destinationPath,
1760
+ legacyRemoved: legacy.removed,
1761
+ legacyPreserved: legacy.preserved
1762
+ };
1763
+ }
1764
+ });
1765
+
1766
+ // src/hooks/context.ts
1767
+ import { createHash as createHash4 } from "node:crypto";
1768
+ import path7 from "node:path";
1769
+
1770
+ // ../../packages/agent-targets/src/hooks/normalize.ts
1771
+ import { createHash } from "node:crypto";
1772
+
1773
+ // ../../packages/agent-targets/src/hooks/types.ts
1774
+ var HOOK_TARGETS = Object.freeze([
1775
+ "claude",
1776
+ "codex",
1777
+ "gemini",
1778
+ "grok",
1779
+ "pi"
1780
+ ]);
1781
+
1782
+ // ../../packages/agent-targets/src/catalog/hook-events.json
1783
+ var hook_events_default = {
1784
+ claude: [
1785
+ "SessionStart",
1786
+ "Setup",
1787
+ "InstructionsLoaded",
1788
+ "UserPromptSubmit",
1789
+ "UserPromptExpansion",
1790
+ "PreToolUse",
1791
+ "PermissionRequest",
1792
+ "PostToolUse",
1793
+ "PostToolUseFailure",
1794
+ "PostToolBatch",
1795
+ "PermissionDenied",
1796
+ "Notification",
1797
+ "SubagentStart",
1798
+ "SubagentStop",
1799
+ "TaskCreated",
1800
+ "TaskCompleted",
1801
+ "Stop",
1802
+ "StopFailure",
1803
+ "TeammateIdle",
1804
+ "ConfigChange",
1805
+ "CwdChanged",
1806
+ "PreCompact",
1807
+ "PostCompact",
1808
+ "SessionEnd",
1809
+ "Elicitation",
1810
+ "ElicitationResult"
1811
+ ],
1812
+ codex: [
1813
+ "SessionStart",
1814
+ "UserPromptSubmit",
1815
+ "PreToolUse",
1816
+ "PermissionRequest",
1817
+ "PostToolUse",
1818
+ "Stop"
1819
+ ],
1820
+ gemini: [
1821
+ "SessionStart",
1822
+ "BeforeAgent",
1823
+ "BeforeTool",
1824
+ "AfterTool",
1825
+ "AfterAgent"
1826
+ ],
1827
+ grok: [
1828
+ "SessionStart",
1829
+ "UserPromptSubmit",
1830
+ "PreToolUse",
1831
+ "PostToolUse",
1832
+ "PostToolUseFailure",
1833
+ "PermissionDenied",
1834
+ "Stop",
1835
+ "StopFailure",
1836
+ "Notification",
1837
+ "SubagentStart",
1838
+ "SubagentStop",
1839
+ "PreCompact",
1840
+ "PostCompact",
1841
+ "SessionEnd"
1842
+ ],
1843
+ pi: [
1844
+ "SessionStart",
1845
+ "UserPromptSubmit",
1846
+ "TurnStart",
1847
+ "PreToolUse",
1848
+ "PostToolUse",
1849
+ "PostToolUseFailure",
1850
+ "Stop",
1851
+ "Notification",
1852
+ "ConfigChange",
1853
+ "PreCompact",
1854
+ "PostCompact",
1855
+ "SessionEnd"
1856
+ ]
1857
+ };
1858
+
1859
+ // ../../packages/agent-targets/src/catalog/hook-events.ts
1860
+ var CLAUDE_HOOK_EVENTS = Object.freeze([
1861
+ ...hook_events_default.claude
1862
+ ]);
1863
+ var CODEX_HOOK_EVENTS = Object.freeze([
1864
+ ...hook_events_default.codex
1865
+ ]);
1866
+ var GEMINI_HOOK_EVENTS = Object.freeze([
1867
+ ...hook_events_default.gemini
1868
+ ]);
1869
+ var GROK_HOOK_EVENTS = Object.freeze([
1870
+ ...hook_events_default.grok
1871
+ ]);
1872
+ var PI_HOOK_EVENTS = Object.freeze([...hook_events_default.pi]);
1873
+
1874
+ // ../../packages/agent-targets/src/hooks/read-command.ts
1875
+ import path2 from "node:path";
1876
+ var SHELL_TOOL_NAMES = /* @__PURE__ */ new Set([
1877
+ "bash",
1878
+ "exec_command",
1879
+ "shell"
1880
+ ]);
1881
+ var COMMAND_SEPARATORS = /* @__PURE__ */ new Set([
1882
+ ";",
1883
+ "&",
1884
+ "&&",
1885
+ "|",
1886
+ "||",
1887
+ "<",
1888
+ "<<",
1889
+ ">",
1890
+ ">>",
1891
+ ">&1",
1892
+ ">&2",
1893
+ "1>",
1894
+ "1>>",
1895
+ "1>&1",
1896
+ "1>&2",
1897
+ "2>",
1898
+ "2>>",
1899
+ "2>&1",
1900
+ "2>&2"
1901
+ ]);
1902
+ function tokenizeShellLikeCommand(command) {
1903
+ const tokens = [];
1904
+ let current = "";
1905
+ let quote = null;
1906
+ let escaping = false;
1907
+ for (let index = 0; index < command.length; index += 1) {
1908
+ const character = command[index];
1909
+ if (escaping) {
1910
+ current += character;
1911
+ escaping = false;
1912
+ continue;
1913
+ }
1914
+ if (character === "\\") {
1915
+ escaping = true;
1916
+ continue;
1917
+ }
1918
+ if (quote) {
1919
+ if (character === quote) quote = null;
1920
+ else current += character;
1921
+ continue;
1922
+ }
1923
+ if (character === "'" || character === '"') {
1924
+ quote = character;
1925
+ continue;
1926
+ }
1927
+ if (current === "" && (character === "1" || character === "2") && command[index + 1] === ">") {
1928
+ if (command[index + 2] === ">") {
1929
+ tokens.push(`${character}>>`);
1930
+ index += 2;
1931
+ } else if (command[index + 2] === "&" && (command[index + 3] === "1" || command[index + 3] === "2")) {
1932
+ tokens.push(`${character}>&${command[index + 3]}`);
1933
+ index += 3;
1934
+ } else {
1935
+ tokens.push(`${character}>`);
1936
+ index += 1;
1937
+ }
1938
+ continue;
1939
+ }
1940
+ if (/[;&|<>]/.test(character)) {
1941
+ if (current) {
1942
+ tokens.push(current);
1943
+ current = "";
1944
+ }
1945
+ const next = command[index + 1];
1946
+ if (character === ">" && next === "&" && (command[index + 2] === "1" || command[index + 2] === "2")) {
1947
+ tokens.push(`>&${command[index + 2]}`);
1948
+ index += 2;
1949
+ continue;
1950
+ }
1951
+ if (character === "&" && next === "&" || character === "|" && next === "|" || character === "<" && next === "<" || character === ">" && next === ">") {
1952
+ tokens.push(`${character}${next}`);
1953
+ index += 1;
1954
+ continue;
1955
+ }
1956
+ tokens.push(character);
1957
+ continue;
1958
+ }
1959
+ if (/\s/.test(character)) {
1960
+ if (current) {
1961
+ tokens.push(current);
1962
+ current = "";
1963
+ }
1964
+ continue;
1965
+ }
1966
+ current += character;
1967
+ }
1968
+ if (quote || escaping) return [];
1969
+ if (current) tokens.push(current);
1970
+ return tokens;
1971
+ }
1972
+ function splitCommandSegments(tokens) {
1973
+ const segments = [];
1974
+ let current = [];
1975
+ for (let index = 0; index < tokens.length; index += 1) {
1976
+ const token = tokens[index];
1977
+ if (COMMAND_SEPARATORS.has(token)) {
1978
+ if (current.length > 0) {
1979
+ segments.push(current);
1980
+ current = [];
1981
+ }
1982
+ if (token === "<" || token === "<<") index += 1;
1983
+ continue;
1984
+ }
1985
+ current.push(token);
1986
+ }
1987
+ if (current.length > 0) segments.push(current);
1988
+ return segments;
1989
+ }
1990
+ function stripSimpleOptions(args, flagsWithValues = /* @__PURE__ */ new Set(), patternFlags = /* @__PURE__ */ new Set()) {
1991
+ const remaining = [];
1992
+ let consumedPatternFlag = false;
1993
+ for (let index = 0; index < args.length; index += 1) {
1994
+ const argument = args[index];
1995
+ if (argument === "--") {
1996
+ remaining.push(...args.slice(index + 1));
1997
+ break;
1998
+ }
1999
+ if (argument.startsWith("-") && argument.length > 1) {
2000
+ if (flagsWithValues.has(argument)) {
2001
+ if (patternFlags.has(argument)) consumedPatternFlag = true;
2002
+ if (!argument.includes("=")) index += 1;
2003
+ }
2004
+ continue;
2005
+ }
2006
+ remaining.push(argument);
2007
+ }
2008
+ return { args: remaining, consumedPatternFlag };
2009
+ }
2010
+ function readCommandFileArgs(args, flagsWithValues, patternFlags = flagsWithValues) {
2011
+ const stripped = stripSimpleOptions(args, flagsWithValues, patternFlags);
2012
+ return stripped.consumedPatternFlag ? stripped.args : stripped.args.slice(1);
2013
+ }
2014
+ function isPlausibleReadPath(argument) {
2015
+ if (!argument || argument === "-" || argument.startsWith("-")) return false;
2016
+ if (COMMAND_SEPARATORS.has(argument)) return false;
2017
+ if (/[\0*?[{}`$]/.test(argument)) return false;
2018
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(argument)) return false;
2019
+ return path2.posix.isAbsolute(argument) || path2.win32.isAbsolute(argument) || argument.startsWith("~/") || argument.startsWith("./") || argument.startsWith("../") || argument.includes("/") || /\.[A-Za-z0-9][A-Za-z0-9_-]*$/.test(argument) || /^[A-Z][A-Za-z0-9_.-]*$/.test(argument);
2020
+ }
2021
+ function filePathFromSegment(tokens) {
2022
+ const commandTokens = [...tokens];
2023
+ while (commandTokens[0]?.includes("=") && /^[A-Za-z_][A-Za-z0-9_]*=/.test(commandTokens[0])) {
2024
+ commandTokens.shift();
2025
+ }
2026
+ const commandName = commandTokens.shift()?.split("/").pop();
2027
+ if (!commandName) return null;
2028
+ let args;
2029
+ switch (commandName) {
2030
+ case "bat":
2031
+ case "batcat":
2032
+ case "cat":
2033
+ case "file":
2034
+ case "less":
2035
+ case "more":
2036
+ case "nl":
2037
+ case "stat":
2038
+ case "wc":
2039
+ args = stripSimpleOptions(commandTokens).args;
2040
+ break;
2041
+ case "head":
2042
+ case "tail":
2043
+ args = stripSimpleOptions(commandTokens, /* @__PURE__ */ new Set(["-n", "-c"])).args;
2044
+ break;
2045
+ case "sed":
2046
+ if (commandTokens.some(
2047
+ (argument) => argument.startsWith("-i") || argument === "--in-place" || argument.startsWith("--in-place=")
2048
+ )) {
2049
+ return null;
2050
+ }
2051
+ args = readCommandFileArgs(commandTokens, /* @__PURE__ */ new Set(["-e", "-f"]));
2052
+ break;
2053
+ case "awk":
2054
+ args = readCommandFileArgs(
2055
+ commandTokens,
2056
+ /* @__PURE__ */ new Set(["-f", "-v"]),
2057
+ /* @__PURE__ */ new Set(["-f"])
2058
+ );
2059
+ break;
2060
+ case "grep":
2061
+ case "rg":
2062
+ args = readCommandFileArgs(
2063
+ commandTokens,
2064
+ /* @__PURE__ */ new Set(["-A", "-B", "-C", "-e", "-f", "-g", "-m", "-t"]),
2065
+ /* @__PURE__ */ new Set(["-e", "-f"])
2066
+ );
2067
+ break;
2068
+ default:
2069
+ return null;
2070
+ }
2071
+ return args.find(isPlausibleReadPath) ?? null;
2072
+ }
2073
+ function extractReadFilePathFromCommand(command) {
2074
+ const tokens = tokenizeShellLikeCommand(command);
2075
+ if (tokens.length === 0) return null;
2076
+ for (let index = 0; index < tokens.length - 1; index += 1) {
2077
+ if (tokens[index] === "<" && isPlausibleReadPath(tokens[index + 1])) {
2078
+ return tokens[index + 1];
2079
+ }
2080
+ }
2081
+ for (const segment of splitCommandSegments(tokens)) {
2082
+ const filePath = filePathFromSegment(segment);
2083
+ if (filePath) return filePath;
2084
+ }
2085
+ return null;
2086
+ }
2087
+
2088
+ // ../../packages/agent-targets/src/hooks/normalize.ts
2089
+ var MAX_EXTRACTED_TEXT_BYTES = 64 * 1024;
2090
+ function isRecord(value) {
2091
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2092
+ }
2093
+ function firstString(record, keys) {
2094
+ for (const key of keys) {
2095
+ const value = record[key];
2096
+ if (typeof value === "string" && value.length > 0) return value;
2097
+ }
2098
+ return void 0;
2099
+ }
2100
+ function extractedText(value) {
2101
+ if (typeof value === "string") return value;
2102
+ if (value === void 0 || value === null) return void 0;
2103
+ try {
2104
+ return JSON.stringify(value);
2105
+ } catch {
2106
+ return void 0;
2107
+ }
2108
+ }
2109
+ function firstText(record, keys) {
2110
+ for (const key of keys) {
2111
+ const value = extractedText(record[key]);
2112
+ if (value !== void 0 && value.length > 0) return value;
2113
+ }
2114
+ return void 0;
2115
+ }
2116
+ function utf8Prefix(value, maxBytes) {
2117
+ let low = 0;
2118
+ let high = value.length;
2119
+ while (low < high) {
2120
+ const middle = Math.ceil((low + high) / 2);
2121
+ if (Buffer.byteLength(value.slice(0, middle), "utf8") <= maxBytes) {
2122
+ low = middle;
2123
+ } else {
2124
+ high = middle - 1;
2125
+ }
2126
+ }
2127
+ let end = low;
2128
+ if (end > 0 && end < value.length && value.charCodeAt(end - 1) >= 55296 && value.charCodeAt(end - 1) <= 56319 && value.charCodeAt(end) >= 56320 && value.charCodeAt(end) <= 57343) {
2129
+ end -= 1;
2130
+ }
2131
+ return value.slice(0, end);
2132
+ }
2133
+ function boundedExtractedText(value, field) {
2134
+ if (value === void 0) return void 0;
2135
+ const originalBytes = Buffer.byteLength(value, "utf8");
2136
+ if (originalBytes <= MAX_EXTRACTED_TEXT_BYTES) return value;
2137
+ const digest = createHash("sha256").update(value).digest("hex");
2138
+ const marker = `
2139
+ [fml:${field}:truncated originalBytes=${originalBytes} sha256=${digest}]`;
2140
+ return `${utf8Prefix(
2141
+ value,
2142
+ MAX_EXTRACTED_TEXT_BYTES - Buffer.byteLength(marker, "utf8")
2143
+ )}${marker}`;
2144
+ }
2145
+ function nestedRecord(record, keys) {
2146
+ for (const key of keys) {
2147
+ const value = record[key];
2148
+ if (isRecord(value)) return value;
2149
+ }
2150
+ return void 0;
2151
+ }
2152
+ function timestampMs(payload, fallback) {
2153
+ for (const key of ["timestamp_ms", "timestampMs", "timestamp"]) {
2154
+ const value = payload[key];
2155
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
2156
+ const milliseconds = Math.round(
2157
+ value < 1e10 ? value * 1e3 : value
2158
+ );
2159
+ if (Number.isSafeInteger(milliseconds) && milliseconds > 0) {
2160
+ return milliseconds;
2161
+ }
2162
+ }
2163
+ if (typeof value === "string") {
2164
+ const parsed = Date.parse(value);
2165
+ if (Number.isSafeInteger(parsed) && parsed >= 0) return parsed;
2166
+ }
2167
+ }
2168
+ return fallback;
2169
+ }
2170
+ function geminiEventType(eventType) {
2171
+ switch (eventType) {
2172
+ case "BeforeAgent":
2173
+ case "BeforeModel":
2174
+ return "UserPromptSubmit";
2175
+ case "BeforeTool":
2176
+ return "PreToolUse";
2177
+ case "AfterTool":
2178
+ return "PostToolUse";
2179
+ case "AfterAgent":
2180
+ return "Stop";
2181
+ default:
2182
+ return eventType;
2183
+ }
2184
+ }
2185
+ var GROK_EVENT_TYPES = Object.freeze({
2186
+ session_start: "SessionStart",
2187
+ user_prompt_submit: "UserPromptSubmit",
2188
+ pre_tool_use: "PreToolUse",
2189
+ post_tool_use: "PostToolUse",
2190
+ post_tool_use_failure: "PostToolUseFailure",
2191
+ permission_denied: "PermissionDenied",
2192
+ stop: "Stop",
2193
+ stop_failure: "StopFailure",
2194
+ notification: "Notification",
2195
+ subagent_start: "SubagentStart",
2196
+ subagent_stop: "SubagentStop",
2197
+ pre_compact: "PreCompact",
2198
+ post_compact: "PostCompact",
2199
+ session_end: "SessionEnd"
2200
+ });
2201
+ function grokEventType(eventType) {
2202
+ return Object.hasOwn(GROK_EVENT_TYPES, eventType) ? GROK_EVENT_TYPES[eventType] ?? eventType : eventType;
2203
+ }
2204
+ function geminiUserPrompt(payload) {
2205
+ const request = payload.llm_request;
2206
+ if (!isRecord(request) || !Array.isArray(request.messages)) return void 0;
2207
+ const userMessage = [...request.messages].reverse().find(
2208
+ (message) => isRecord(message) && message.role === "user"
2209
+ );
2210
+ if (!userMessage) return void 0;
2211
+ if (typeof userMessage.content === "string") {
2212
+ return userMessage.content || void 0;
2213
+ }
2214
+ if (!Array.isArray(userMessage.content)) return void 0;
2215
+ const text = userMessage.content.filter(
2216
+ (part) => isRecord(part) && part.type === "text" && typeof part.text === "string"
2217
+ ).map((part) => part.text).join("\n");
2218
+ return text || void 0;
2219
+ }
2220
+ function normalizedEvent(target, payload, context) {
2221
+ const sessionId = firstString(payload, [
2222
+ "session_id",
2223
+ "sessionId",
2224
+ "conversation_id",
2225
+ "conversationId"
2226
+ ]);
2227
+ const rawEventType = firstString(payload, [
2228
+ "hook_event_name",
2229
+ "hookEventName",
2230
+ "event_name",
2231
+ "eventName",
2232
+ "event"
2233
+ ]);
2234
+ if (!sessionId || !rawEventType) return null;
2235
+ const eventType = target === "gemini" ? geminiEventType(rawEventType) : target === "grok" ? grokEventType(rawEventType) : rawEventType;
2236
+ const toolInput = nestedRecord(payload, ["tool_input", "toolInput"]);
2237
+ const cwd = firstString(payload, [
2238
+ "cwd",
2239
+ "working_directory",
2240
+ "workingDirectory",
2241
+ "workspaceRoot"
2242
+ ]) ?? context.cwd;
2243
+ const command = boundedExtractedText(
2244
+ firstText(payload, ["command"]) ?? (toolInput ? firstText(toolInput, ["command", "cmd"]) : void 0),
2245
+ "command"
2246
+ );
2247
+ const toolName = firstString(payload, ["tool_name", "toolName", "name"]);
2248
+ const filePath = firstString(payload, ["file_path", "filePath", "path"]) ?? (toolInput ? firstString(toolInput, ["file_path", "filePath", "path"]) : void 0) ?? (command && toolName && SHELL_TOOL_NAMES.has(toolName.toLowerCase()) ? extractReadFilePathFromCommand(command) ?? void 0 : void 0);
2249
+ const userPrompt = boundedExtractedText(
2250
+ firstText(payload, ["prompt", "user_prompt", "userPrompt"]) ?? (target === "gemini" ? geminiUserPrompt(payload) : void 0),
2251
+ "user_prompt"
2252
+ );
2253
+ const plan = boundedExtractedText(firstText(payload, ["plan"]), "plan");
2254
+ const allowedPrompts = boundedExtractedText(
2255
+ firstText(payload, ["allowed_prompts", "allowedPrompts"]),
2256
+ "allowed_prompts"
2257
+ );
2258
+ const toolResult = boundedExtractedText(
2259
+ firstText(payload, [
2260
+ "tool_result",
2261
+ "toolResult",
2262
+ "tool_response",
2263
+ "toolResponse"
2264
+ ]),
2265
+ "tool_result"
2266
+ );
2267
+ const nativeEventId = firstString(payload, [
2268
+ "hook_id",
2269
+ "hookId",
2270
+ "event_id",
2271
+ "eventId",
2272
+ "tool_use_id",
2273
+ "toolUseId",
2274
+ "tool_call_id",
2275
+ "toolCallId",
2276
+ "prompt_id",
2277
+ "promptId"
2278
+ ]) ?? (eventType === "UserPromptSubmit" || eventType === "Stop" ? firstString(payload, ["turn_id", "turnId"]) : void 0);
2279
+ return {
2280
+ sessionId,
2281
+ eventType,
2282
+ timestampMs: timestampMs(payload, context.nowMs),
2283
+ ...nativeEventId ? { nativeEventId } : {},
2284
+ ...cwd ? { cwd } : {},
2285
+ ...toolName ? { toolName } : {},
2286
+ ...userPrompt ? { userPrompt } : {},
2287
+ ...filePath ? { filePath } : {},
2288
+ ...command ? { command } : {},
2289
+ ...plan ? { plan } : {},
2290
+ ...allowedPrompts ? { allowedPrompts } : {},
2291
+ ...toolResult ? { toolResult } : {}
2292
+ };
2293
+ }
2294
+ function capability(targetId, nativeEvents) {
2295
+ return Object.freeze({
2296
+ targetId,
2297
+ nativeEvents,
2298
+ normalize: (payload, context) => normalizedEvent(targetId, payload, context)
2299
+ });
2300
+ }
2301
+ var HOOK_NORMALIZATION_CAPABILITIES = Object.freeze([
2302
+ capability("claude", CLAUDE_HOOK_EVENTS),
2303
+ capability("codex", CODEX_HOOK_EVENTS),
2304
+ capability("gemini", GEMINI_HOOK_EVENTS),
2305
+ capability("grok", GROK_HOOK_EVENTS),
2306
+ capability("pi", PI_HOOK_EVENTS)
2307
+ ]);
2308
+
2309
+ // src/hooks/context.ts
2310
+ init_token_store();
2311
+ init_dirs();
2085
2312
 
2086
2313
  // src/repo.ts
2087
2314
  import { execFile, execFileSync as execFileSync2 } from "node:child_process";
@@ -2132,6 +2359,8 @@ function resolveRepositoryRootFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
2132
2359
  }
2133
2360
 
2134
2361
  // src/hooks/sync-loop.ts
2362
+ init_token_store();
2363
+ init_config();
2135
2364
  var MAX_RETRY_INTERVAL_MS = 5 * 6e4;
2136
2365
  var MAX_RETRY_AFTER_MS = 24 * 60 * 6e4;
2137
2366
  var MAX_HOOK_EVENT_ROW_JSON_BYTES = 2 * 1024 * 1024;
@@ -2211,14 +2440,14 @@ function sameDestination(left, right) {
2211
2440
  return left.environment === right.environment && left.targetUrl === right.targetUrl && left.destinationIdentity === right.destinationIdentity;
2212
2441
  }
2213
2442
  function hookContextInjectionId(target, syncId) {
2214
- const digest = createHash3("sha256").update(`${INJECTION_ID_VERSION}\0${target}\0${syncId}`).digest("base64url");
2443
+ const digest = createHash4("sha256").update(`${INJECTION_ID_VERSION}\0${target}\0${syncId}`).digest("base64url");
2215
2444
  return `${INJECTION_ID_VERSION}_${digest}`;
2216
2445
  }
2217
2446
  function canonicalHookContextFilePath(filePath, cwd, repositoryRoot) {
2218
2447
  const windows = [filePath, cwd, repositoryRoot].some(
2219
2448
  (value) => value !== void 0 && value !== null && /^[A-Za-z]:[\\/]/.test(value)
2220
2449
  );
2221
- const pathApi = windows ? path6.win32 : path6.posix;
2450
+ const pathApi = windows ? path7.win32 : path7.posix;
2222
2451
  const normalized = pathApi.normalize(filePath);
2223
2452
  const resolved = pathApi.isAbsolute(normalized) ? normalized : cwd && pathApi.isAbsolute(cwd) ? pathApi.resolve(cwd, normalized) : normalized;
2224
2453
  if (repositoryRoot && pathApi.isAbsolute(resolved)) {
@@ -2540,17 +2769,17 @@ function absoluteEnvPath(value) {
2540
2769
  const trimmed = value.trim();
2541
2770
  if (trimmed === "~") return os2.homedir();
2542
2771
  if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
2543
- return path7.join(os2.homedir(), trimmed.slice(2));
2772
+ return path8.join(os2.homedir(), trimmed.slice(2));
2544
2773
  }
2545
- return path7.isAbsolute(trimmed) ? path7.normalize(trimmed) : void 0;
2774
+ return path8.isAbsolute(trimmed) ? path8.normalize(trimmed) : void 0;
2546
2775
  }
2547
2776
  function observerDirectory() {
2548
2777
  const fmlHome = absoluteEnvPath(process.env.FML_HOME);
2549
- return absoluteEnvPath(process.env.FML_OBSERVER_DIR) ?? absoluteEnvPath(process.env.PANOPTICON_HOME) ?? (fmlHome ? path7.join(fmlHome, "panopticon") : path7.join(os2.homedir(), ".fml", "panopticon"));
2778
+ return absoluteEnvPath(process.env.FML_OBSERVER_DIR) ?? absoluteEnvPath(process.env.PANOPTICON_HOME) ?? (fmlHome ? path8.join(fmlHome, "panopticon") : path8.join(os2.homedir(), ".fml", "panopticon"));
2550
2779
  }
2551
2780
  function readObserverEndpoint(filePath) {
2552
2781
  try {
2553
- const parsed = JSON.parse(fs4.readFileSync(filePath, "utf8"));
2782
+ const parsed = JSON.parse(fs5.readFileSync(filePath, "utf8"));
2554
2783
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2555
2784
  return null;
2556
2785
  }
@@ -2565,15 +2794,15 @@ function readObserverEndpoint(filePath) {
2565
2794
  }
2566
2795
  function readObserverAuthToken(filePath) {
2567
2796
  try {
2568
- return fs4.readFileSync(filePath, "utf8").trim() || null;
2797
+ return fs5.readFileSync(filePath, "utf8").trim() || null;
2569
2798
  } catch {
2570
2799
  return null;
2571
2800
  }
2572
2801
  }
2573
2802
  async function deliver(event) {
2574
2803
  const directory = observerDirectory();
2575
- const endpoint = readObserverEndpoint(path7.join(directory, "endpoint.json"));
2576
- const authToken = readObserverAuthToken(path7.join(directory, "auth-token"));
2804
+ const endpoint = readObserverEndpoint(path8.join(directory, "endpoint.json"));
2805
+ const authToken = readObserverAuthToken(path8.join(directory, "auth-token"));
2577
2806
  if (!endpoint || !authToken) return;
2578
2807
  const host = endpoint.host === "::1" ? "[::1]" : endpoint.host;
2579
2808
  try {
@@ -2715,7 +2944,7 @@ var PiReceiptComponent = class {
2715
2944
  }
2716
2945
  };
2717
2946
  function fmlPiExtension(pi) {
2718
- let sessionId = randomUUID3();
2947
+ let sessionId = randomUUID4();
2719
2948
  let initializedSessionId = null;
2720
2949
  let cwd;
2721
2950
  let repository = null;